PageSourceSearch

https://catalog.lindahall.org/discovery/lib/bower_components/video.js/dist/video.js

js lindahall.org collected 2026-10-01 10:17:52 UTC 2,253,191 bytes, 64,617 lines download raw bytes

vendor: 4,677 bytes, lines 1-164
1/**
2 * @license
3 * Video.js 8.9.0 <http://videojs.com/>
4 * Copyright Brightcove, Inc. <https://www.brightcove.com/>
5 * Available under Apache License Version 2.0
6 * <https://github.com/videojs/video.js/blob/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(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 = "8.9.0";
20
21  /**
22   * An Object that contains lifecycle hooks as keys which point to an array
23   * of functions that are run when a lifecycle is triggered
24   *
25   * @private
26   */
27  const hooks_ = {};
28
29  /**
30   * Get a list of hooks for a specific lifecycle
31   *
32   * @param  {string} type
33   *         the lifecycle to get hooks from
34   *
35   * @param  {Function|Function[]} [fn]
36   *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
37   *
38   * @return {Array}
39   *         an array of hooks, or an empty array if there are none.
40   */
41  const hooks = function (type, fn) {
42    hooks_[type] = hooks_[type] || [];
43    if (fn) {
44      hooks_[type] = hooks_[type].concat(fn);
45    }
46    return hooks_[type];
47  };
48
49  /**
50   * Add a function hook to a specific videojs lifecycle.
51   *
52   * @param {string} type
53   *        the lifecycle to hook the function to.
54   *
55   * @param {Function|Function[]}
56   *        The function or array of functions to attach.
57   */
58  const hook = function (type, fn) {
59    hooks(type, fn);
60  };
61
62  /**
63   * Remove a hook from a specific videojs lifecycle.
64   *
65   * @param  {string} type
66   *         the lifecycle that the function hooked to
67   *
68   * @param  {Function} fn
69   *         The hooked function to remove
70   *
71   * @return {boolean}
72   *         The function that was removed or undef
73   */
74  const removeHook = function (type, fn) {
75    const index = hooks(type).indexOf(fn);
76    if (index <= -1) {
77      return false;
78    }
79    hooks_[type] = hooks_[type].slice();
80    hooks_[type].splice(index, 1);
81    return true;
82  };
83
84  /**
85   * Add a function hook that will only run once to a specific videojs lifecycle.
86   *
87   * @param {string} type
88   *        the lifecycle to hook the function to.
89   *
90   * @param {Function|Function[]}
91   *        The function or array of functions to attach.
92   */
93  const hookOnce = function (type, fn) {
94    hooks(type, [].concat(fn).map(original => {
95      const wrapper = (...args) => {
96        removeHook(type, wrapper);
97        return original(...args);
98      };
99      return wrapper;
100    }));
101  };
102
103  /**
104   * @file fullscreen-api.js
105   * @module fullscreen-api
106   */
107
108  /**
109   * Store the browser-specific methods for the fullscreen API.
110   *
111   * @type {Object}
112   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
113   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
114   */
115  const FullscreenApi = {
116    prefixed: true
117  };
118
119  // browser API methods
120  const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'],
121  // WebKit
122  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen']];
123  const specApi = apiMap[0];
124  let browserApi;
125
126  // determine the supported set of functions
127  for (let i = 0; i < apiMap.length; i++) {
128    // check for exitFullscreen function
129    if (apiMap[i][1] in document) {
130      browserApi = apiMap[i];
131      break;
132    }
133  }
134
135  // map the browser API names to the spec API names
136  if (browserApi) {
137    for (let i = 0; i < browserApi.length; i++) {
138      FullscreenApi[specApi[i]] = browserApi[i];
139    }
140    FullscreenApi.prefixed = browserApi[0] !== specApi[0];
141  }
142
143  /**
144   * @file create-logger.js
145   * @module create-logger
146   */
147
148  // This is the private tracking variable for the logging history.
149  let history = [];
150
151  /**
152   * Log messages to the console and history based on the type of message
153   *
154   * @private
155   * @param  {string} name
156   *         The name of the console method to use.
157   *
158   * @param  {Object} log
159   *         The arguments to be passed to the matching console method.
160   *
161   * @param {string} [styles]
162   *        styles for name
163   */
164  const LogByTypeFactory = (name, log, styles) =>
vendor: 2,797 bytes, lines 164-250
164 (type, level, args) => {
165    const lvl = log.levels[level];
166    const lvlRegExp = new RegExp(`^(${lvl})$`);
167    let resultName = name;
168    if (type !== 'log') {
169      // Add the type to the front of the message when it's not "log".
170      args.unshift(type.toUpperCase() + ':');
171    }
172    if (styles) {
173      resultName = `%c${name}`;
174      args.unshift(styles);
175    }
176
177    // Add console prefix after adding to history.
178    args.unshift(resultName + ':');
179
180    // Add a clone of the args at this point to history.
181    if (history) {
182      history.push([].concat(args));
183
184      // only store 1000 history entries
185      const splice = history.length - 1000;
186      history.splice(0, splice > 0 ? splice : 0);
187    }
188
189    // If there's no console then don't try to output messages, but they will
190    // still be stored in history.
191    if (!window.console) {
192      return;
193    }
194
195    // Was setting these once outside of this function, but containing them
196    // in the function makes it easier to test cases where console doesn't exist
197    // when the module is executed.
198    let fn = window.console[type];
199    if (!fn && type === 'debug') {
200      // Certain browsers don't have support for console.debug. For those, we
201      // should default to the closest comparable log.
202      fn = window.console.info || window.console.log;
203    }
204
205    // Bail out if there's no console or if this type is not allowed by the
206    // current logging level.
207    if (!fn || !lvl || !lvlRegExp.test(type)) {
208      return;
209    }
210    fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
211  };
212  function createLogger$1(name, delimiter = ':', styles = '') {
213    // This is the private tracking variable for logging level.
214    let level = 'info';
215
216    // the curried logByType bound to the specific log and history
217    let logByType;
218
219    /**
220     * Logs plain debug messages. Similar to `console.log`.
221     *
222     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
223     * of our JSDoc template, we cannot properly document this as both a function
224     * and a namespace, so its function signature is documented here.
225     *
226     * #### Arguments
227     * ##### *args
228     * *[]
229     *
230     * Any combination of values that could be passed to `console.log()`.
231     *
232     * #### Return Value
233     *
234     * `undefined`
235     *
236     * @namespace
237     * @param    {...*} args
238     *           One or more messages or objects that should be logged.
239     */
240    const log = function (...args) {
241      logByType('log', level, args);
242    };
243
244    // This is the logByType helper that the logging methods below use
245    logByType = LogByTypeFactory(name, log, styles);
246
247    /**
248     * Create a new subLogger which chains the old name to the new name.
249     *
250     * For example, doing `
vendor: 12,595 bytes, lines 250-725
250videojs.log.createLogger('player')` and then using that logger will log the following:
251     * ```js
252     *  mylogger('foo');
253     *  // > VIDEOJS: player: foo
254     * ```
255     *
256     * @param {string} subName
257     *        The name to add call the new logger
258     * @param {string} [subDelimiter]
259     *        Optional delimiter
260     * @param {string} [subStyles]
261     *        Optional styles
262     * @return {Object}
263     */
264    log.createLogger = (subName, subDelimiter, subStyles) => {
265      const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter;
266      const resultStyles = subStyles !== undefined ? subStyles : styles;
267      const resultName = `${name} ${resultDelimiter} ${subName}`;
268      return createLogger$1(resultName, resultDelimiter, resultStyles);
269    };
270
271    /**
272     * Create a new logger.
273     *
274     * @param {string} newName
275     *        The name for the new logger
276     * @param {string} [newDelimiter]
277     *        Optional delimiter
278     * @param {string} [newStyles]
279     *        Optional styles
280     * @return {Object}
281     */
282    log.createNewLogger = (newName, newDelimiter, newStyles) => {
283      return createLogger$1(newName, newDelimiter, newStyles);
284    };
285
286    /**
287     * Enumeration of available logging levels, where the keys are the level names
288     * and the values are `|`-separated strings containing logging methods allowed
289     * in that logging level. These strings are used to create a regular expression
290     * matching the function name being called.
291     *
292     * Levels provided by Video.js are:
293     *
294     * - `off`: Matches no calls. Any value that can be cast to `false` will have
295     *   this effect. The most restrictive.
296     * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
297     *   `log.warn`, and `log.error`).
298     * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
299     * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
300     * - `warn`: Matches `log.warn` and `log.error` calls.
301     * - `error`: Matches only `log.error` calls.
302     *
303     * @type {Object}
304     */
305    log.levels = {
306      all: 'debug|log|warn|error',
307      off: '',
308      debug: 'debug|log|warn|error',
309      info: 'log|warn|error',
310      warn: 'warn|error',
311      error: 'error',
312      DEFAULT: level
313    };
314
315    /**
316     * Get or set the current logging level.
317     *
318     * If a string matching a key from {@link module:log.levels} is provided, acts
319     * as a setter.
320     *
321     * @param  {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl]
322     *         Pass a valid level to set a new logging level.
323     *
324     * @return {string}
325     *         The current logging level.
326     */
327    log.level = lvl => {
328      if (typeof lvl === 'string') {
329        if (!log.levels.hasOwnProperty(lvl)) {
330          throw new Error(`"${lvl}" in not a valid log level`);
331        }
332        level = lvl;
333      }
334      return level;
335    };
336
337    /**
338     * Returns an array containing everything that has been logged to the history.
339     *
340     * This array is a shallow clone of the internal history record. However, its
341     * contents are _not_ cloned; so, mutating objects inside this array will
342     * mutate them in history.
343     *
344     * @return {Array}
345     */
346    log.history = () => history ? [].concat(history) : [];
347
348    /**
349     * Allows you to filter the history by the given logger name
350     *
351     * @param {string} fname
352     *        The name to filter by
353     *
354     * @return {Array}
355     *         The filtered list to return
356     */
357    log.history.filter = fname => {
358      return (history || []).filter(historyItem => {
359        // if the first item in each historyItem includes `fname`, then it's a match
360        return new RegExp(`.*${fname}.*`).test(historyItem[0]);
361      });
362    };
363
364    /**
365     * Clears the internal history tracking, but does not prevent further history
366     * tracking.
367     */
368    log.history.clear = () => {
369      if (history) {
370        history.length = 0;
371      }
372    };
373
374    /**
375     * Disable history tracking if it is currently enabled.
376     */
377    log.history.disable = () => {
378      if (history !== null) {
379        history.length = 0;
380        history = null;
381      }
382    };
383
384    /**
385     * Enable history tracking if it is currently disabled.
386     */
387    log.history.enable = () => {
388      if (history === null) {
389        history = [];
390      }
391    };
392
393    /**
394     * Logs error messages. Similar to `console.error`.
395     *
396     * @param {...*} args
397     *        One or more messages or objects that should be logged as an error
398     */
399    log.error = (...args) => logByType('error', level, args);
400
401    /**
402     * Logs warning messages. Similar to `console.warn`.
403     *
404     * @param {...*} args
405     *        One or more messages or objects that should be logged as a warning.
406     */
407    log.warn = (...args) => logByType('warn', level, args);
408
409    /**
410     * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
411     * log if `console.debug` is not available
412     *
413     * @param {...*} args
414     *        One or more messages or objects that should be logged as debug.
415     */
416    log.debug = (...args) => logByType('debug', level, args);
417    return log;
418  }
419
420  /**
421   * @file log.js
422   * @module log
423   */
424  const log$1 = createLogger$1('VIDEOJS');
425  const createLogger = log$1.createLogger;
426
427  /**
428   * @file obj.js
429   * @module obj
430   */
431
432  /**
433   * @callback obj:EachCallback
434   *
435   * @param {*} value
436   *        The current key for the object that is being iterated over.
437   *
438   * @param {string} key
439   *        The current key-value for object that is being iterated over
440   */
441
442  /**
443   * @callback obj:ReduceCallback
444   *
445   * @param {*} accum
446   *        The value that is accumulating over the reduce loop.
447   *
448   * @param {*} value
449   *        The current key for the object that is being iterated over.
450   *
451   * @param {string} key
452   *        The current key-value for object that is being iterated over
453   *
454   * @return {*}
455   *         The new accumulated value.
456   */
457  const toString$1 = Object.prototype.toString;
458
459  /**
460   * Get the keys of an Object
461   *
462   * @param {Object}
463   *        The Object to get the keys from
464   *
465   * @return {string[]}
466   *         An array of the keys from the object. Returns an empty array if the
467   *         object passed in was invalid or had no keys.
468   *
469   * @private
470   */
471  const keys = function (object) {
472    return isObject$1(object) ? Object.keys(object) : [];
473  };
474
475  /**
476   * Array-like iteration for objects.
477   *
478   * @param {Object} object
479   *        The object to iterate over
480   *
481   * @param {obj:EachCallback} fn
482   *        The callback function which is called for each key in the object.
483   */
484  function each(object, fn) {
485    keys(object).forEach(key => fn(object[key], key));
486  }
487
488  /**
489   * Array-like reduce for objects.
490   *
491   * @param {Object} object
492   *        The Object that you want to reduce.
493   *
494   * @param {Function} fn
495   *         A callback function which is called for each key in the object. It
496   *         receives the accumulated value and the per-iteration value and key
497   *         as arguments.
498   *
499   * @param {*} [initial = 0]
500   *        Starting value
501   *
502   * @return {*}
503   *         The final accumulated value.
504   */
505  function reduce(object, fn, initial = 0) {
506    return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
507  }
508
509  /**
510   * Returns whether a value is an object of any kind - including DOM nodes,
511   * arrays, regular expressions, etc. Not functions, though.
512   *
513   * This avoids the gotcha where using `typeof` on a `null` value
514   * results in `'object'`.
515   *
516   * @param  {Object} value
517   * @return {boolean}
518   */
519  function isObject$1(value) {
520    return !!value && typeof value === 'object';
521  }
522
523  /**
524   * Returns whether an object appears to be a "plain" object - that is, a
525   * direct instance of `Object`.
526   *
527   * @param  {Object} value
528   * @return {boolean}
529   */
530  function isPlain(value) {
531    return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object;
532  }
533
534  /**
535   * Merge two objects recursively.
536   *
537   * Performs a deep merge like
538   * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
539   * plain objects (not arrays, elements, or anything else).
540   *
541   * Non-plain object values will be copied directly from the right-most
542   * argument.
543   *
544   * @param   {Object[]} sources
545   *          One or more objects to merge into a new object.
546   *
547   * @return {Object}
548   *          A new object that is the merged result of all sources.
549   */
550  function merge$2(...sources) {
551    const result = {};
552    sources.forEach(source => {
553      if (!source) {
554        return;
555      }
556      each(source, (value, key) => {
557        if (!isPlain(value)) {
558          result[key] = value;
559          return;
560        }
561        if (!isPlain(result[key])) {
562          result[key] = {};
563        }
564        result[key] = merge$2(result[key], value);
565      });
566    });
567    return result;
568  }
569
570  /**
571   * Returns an array of values for a given object
572   *
573   * @param  {Object} source - target object
574   * @return {Array<unknown>} - object values
575   */
576  function values$1(source = {}) {
577    const result = [];
578    for (const key in source) {
579      if (source.hasOwnProperty(key)) {
580        const value = source[key];
581        result.push(value);
582      }
583    }
584    return result;
585  }
586
587  /**
588   * Object.defineProperty but "lazy", which means that the value is only set after
589   * it is retrieved the first time, rather than being set right away.
590   *
591   * @param {Object} obj the object to set the property on
592   * @param {string} key the key for the property to set
593   * @param {Function} getValue the function used to get the value when it is needed.
594   * @param {boolean} setter whether a setter should be allowed or not
595   */
596  function defineLazyProperty(obj, key, getValue, setter = true) {
597    const set = value => Object.defineProperty(obj, key, {
598      value,
599      enumerable: true,
600      writable: true
601    });
602    const options = {
603      configurable: true,
604      enumerable: true,
605      get() {
606        const value = getValue();
607        set(value);
608        return value;
609      }
610    };
611    if (setter) {
612      options.set = set;
613    }
614    return Object.defineProperty(obj, key, options);
615  }
616
617  var Obj = /*#__PURE__*/Object.freeze({
618    __proto__: null,
619    each: each,
620    reduce: reduce,
621    isObject: isObject$1,
622    isPlain: isPlain,
623    merge: merge$2,
624    values: values$1,
625    defineLazyProperty: defineLazyProperty
626  });
627
628  /**
629   * @file browser.js
630   * @module browser
631   */
632
633  /**
634   * Whether or not this device is an iPod.
635   *
636   * @static
637   * @type {Boolean}
638   */
639  let IS_IPOD = false;
640
641  /**
642   * The detected iOS version - or `null`.
643   *
644   * @static
645   * @type {string|null}
646   */
647  let IOS_VERSION = null;
648
649  /**
650   * Whether or not this is an Android device.
651   *
652   * @static
653   * @type {Boolean}
654   */
655  let IS_ANDROID = false;
656
657  /**
658   * The detected Android version - or `null` if not Android or indeterminable.
659   *
660   * @static
661   * @type {number|string|null}
662   */
663  let ANDROID_VERSION;
664
665  /**
666   * Whether or not this is Mozilla Firefox.
667   *
668   * @static
669   * @type {Boolean}
670   */
671  let IS_FIREFOX = false;
672
673  /**
674   * Whether or not this is Microsoft Edge.
675   *
676   * @static
677   * @type {Boolean}
678   */
679  let IS_EDGE = false;
680
681  /**
682   * Whether or not this is any Chromium Browser
683   *
684   * @static
685   * @type {Boolean}
686   */
687  let IS_CHROMIUM = false;
688
689  /**
690   * Whether or not this is any Chromium browser that is not Edge.
691   *
692   * This will also be `true` for Chrome on iOS, which will have different support
693   * as it is actually Safari under the hood.
694   *
695   * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
696   * IS_CHROMIUM should be used instead.
697   * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
698   *
699   * @static
700   * @deprecated
701   * @type {Boolean}
702   */
703  let IS_CHROME = false;
704
705  /**
706   * The detected Chromium version - or `null`.
707   *
708   * @static
709   * @type {number|null}
710   */
711  let CHROMIUM_VERSION = null;
712
713  /**
714   * The detected Google Chrome version - or `null`.
715   * This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
716   * Deprecated, use CHROMIUM_VERSION instead.
717   *
718   * @static
719   * @deprecated
720   * @type {number|null}
721   */
722  let CHROME_VERSION = null;
723
724  /**
725   * 
725The detected Internet Explorer version - or `null`.
726   *
727   * @static
728   * @deprecated
729   * @type {number|null}
730   */
731  let IE_VERSION = null;
732
733  /**
734   * Whether or not this is desktop Safari.
735   *
736   * @static
737   * @type {Boolean}
738   */
739  let IS_SAFARI = false;
vendor: 5,042 bytes, lines 740-880
740
741  /**
742   * Whether or not this is a Windows machine.
743   *
744   * @static
745   * @type {Boolean}
746   */
747  let IS_WINDOWS = false;
748
749  /**
750   * Whether or not this device is an iPad.
751   *
752   * @static
753   * @type {Boolean}
754   */
755  let IS_IPAD = false;
756
757  /**
758   * Whether or not this device is an iPhone.
759   *
760   * @static
761   * @type {Boolean}
762   */
763  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
764  // to identify iPhones, we need to exclude iPads.
765  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
766  let IS_IPHONE = false;
767
768  /**
769   * Whether or not this device is touch-enabled.
770   *
771   * @static
772   * @const
773   * @type {Boolean}
774   */
775  const TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window || window.navigator.maxTouchPoints || window.DocumentTouch && window.document instanceof window.DocumentTouch));
776  const UAD = window.navigator && window.navigator.userAgentData;
777  if (UAD && UAD.platform && UAD.brands) {
778    // If userAgentData is present, use it instead of userAgent to avoid warnings
779    // Currently only implemented on Chromium
780    // userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
781
782    IS_ANDROID = UAD.platform === 'Android';
783    IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge'));
784    IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium'));
785    IS_CHROME = !IS_EDGE && IS_CHROMIUM;
786    CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null;
787    IS_WINDOWS = UAD.platform === 'Windows';
788  }
789
790  // If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
791  //  or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
792  // the checks need to be made agiainst the regular userAgent string.
793  if (!IS_CHROMIUM) {
794    const USER_AGENT = window.navigator && window.navigator.userAgent || '';
795    IS_IPOD = /iPod/i.test(USER_AGENT);
796    IOS_VERSION = function () {
797      const match = USER_AGENT.match(/OS (\d+)_/i);
798      if (match && match[1]) {
799        return match[1];
800      }
801      return null;
802    }();
803    IS_ANDROID = /Android/i.test(USER_AGENT);
804    ANDROID_VERSION = function () {
805      // This matches Android Major.Minor.Patch versions
806      // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
807      const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
808      if (!match) {
809        return null;
810      }
811      const major = match[1] && parseFloat(match[1]);
812      const minor = match[2] && parseFloat(match[2]);
813      if (major && minor) {
814        return parseFloat(match[1] + '.' + match[2]);
815      } else if (major) {
816        return major;
817      }
818      return null;
819    }();
820    IS_FIREFOX = /Firefox/i.test(USER_AGENT);
821    IS_EDGE = /Edg/i.test(USER_AGENT);
822    IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
823    IS_CHROME = !IS_EDGE && IS_CHROMIUM;
824    CHROMIUM_VERSION = CHROME_VERSION = function () {
825      const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
826      if (match && match[2]) {
827        return parseFloat(match[2]);
828      }
829      return null;
830    }();
831    IE_VERSION = function () {
832      const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
833      let version = result && parseFloat(result[1]);
834      if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
835        // IE 11 has a different user agent string than other IE versions
836        version = 11.0;
837      }
838      return version;
839    }();
840    IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
841    IS_WINDOWS = /Windows/i.test(USER_AGENT);
842    IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
843    IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
844  }
845
846  /**
847   * Whether or not this is an iOS device.
848   *
849   * @static
850   * @const
851   * @type {Boolean}
852   */
853  const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
854
855  /**
856   * Whether or not this is any flavor of Safari - including iOS.
857   *
858   * @static
859   * @const
860   * @type {Boolean}
861   */
862  const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
863
864  var browser = /*#__PURE__*/Object.freeze({
865    __proto__: null,
866    get IS_IPOD () { return IS_IPOD; },
867    get IOS_VERSION () { return IOS_VERSION; },
868    get IS_ANDROID () { return IS_ANDROID; },
869    get ANDROID_VERSION () { return ANDROID_VERSION; },
870    get IS_FIREFOX () { return IS_FIREFOX; },
871    get IS_EDGE () { return IS_EDGE; },
872    get IS_CHROMIUM () { return IS_CHROMIUM; },
873    get IS_CHROME () { return IS_CHROME; },
874    get CHROMIUM_VERSION () { return CHROMIUM_VERSION; },
875    get CHROME_VERSION () { return CHROME_VERSION; },
876    get IE_VERSION () { return IE_VERSION; },
877    get IS_SAFARI () { return IS_SAFARI; },
878    get IS_WINDOWS () { return IS_WINDOWS; },
879    get IS_IPAD () { return IS_IPAD; },
880    get IS_IPHONE () { return IS_IPHONE
vendor: 7,050 bytes, lines 880-1133
880; },
881    TOUCH_ENABLED: TOUCH_ENABLED,
882    IS_IOS: IS_IOS,
883    IS_ANY_SAFARI: IS_ANY_SAFARI
884  });
885
886  /**
887   * @file dom.js
888   * @module dom
889   */
890
891  /**
892   * Detect if a value is a string with any non-whitespace characters.
893   *
894   * @private
895   * @param  {string} str
896   *         The string to check
897   *
898   * @return {boolean}
899   *         Will be `true` if the string is non-blank, `false` otherwise.
900   *
901   */
902  function isNonBlankString(str) {
903    // we use str.trim as it will trim any whitespace characters
904    // from the front or back of non-whitespace characters. aka
905    // Any string that contains non-whitespace characters will
906    // still contain them after `trim` but whitespace only strings
907    // will have a length of 0, failing this check.
908    return typeof str === 'string' && Boolean(str.trim());
909  }
910
911  /**
912   * Throws an error if the passed string has whitespace. This is used by
913   * class methods to be relatively consistent with the classList API.
914   *
915   * @private
916   * @param  {string} str
917   *         The string to check for whitespace.
918   *
919   * @throws {Error}
920   *         Throws an error if there is whitespace in the string.
921   */
922  function throwIfWhitespace(str) {
923    // str.indexOf instead of regex because str.indexOf is faster performance wise.
924    if (str.indexOf(' ') >= 0) {
925      throw new Error('class has illegal whitespace characters');
926    }
927  }
928
929  /**
930   * Whether the current DOM interface appears to be real (i.e. not simulated).
931   *
932   * @return {boolean}
933   *         Will be `true` if the DOM appears to be real, `false` otherwise.
934   */
935  function isReal() {
936    // Both document and window will never be undefined thanks to `global`.
937    return document === window.document;
938  }
939
940  /**
941   * Determines, via duck typing, whether or not a value is a DOM element.
942   *
943   * @param  {*} value
944   *         The value to check.
945   *
946   * @return {boolean}
947   *         Will be `true` if the value is a DOM element, `false` otherwise.
948   */
949  function isEl(value) {
950    return isObject$1(value) && value.nodeType === 1;
951  }
952
953  /**
954   * Determines if the current DOM is embedded in an iframe.
955   *
956   * @return {boolean}
957   *         Will be `true` if the DOM is embedded in an iframe, `false`
958   *         otherwise.
959   */
960  function isInFrame() {
961    // We need a try/catch here because Safari will throw errors when attempting
962    // to get either `parent` or `self`
963    try {
964      return window.parent !== window.self;
965    } catch (x) {
966      return true;
967    }
968  }
969
970  /**
971   * Creates functions to query the DOM using a given method.
972   *
973   * @private
974   * @param   {string} method
975   *          The method to create the query with.
976   *
977   * @return  {Function}
978   *          The query method
979   */
980  function createQuerier(method) {
981    return function (selector, context) {
982      if (!isNonBlankString(selector)) {
983        return document[method](null);
984      }
985      if (isNonBlankString(context)) {
986        context = document.querySelector(context);
987      }
988      const ctx = isEl(context) ? context : document;
989      return ctx[method] && ctx[method](selector);
990    };
991  }
992
993  /**
994   * Creates an element and applies properties, attributes, and inserts content.
995   *
996   * @param  {string} [tagName='div']
997   *         Name of tag to be created.
998   *
999   * @param  {Object} [properties={}]
1000   *         Element properties to be applied.
1001   *
1002   * @param  {Object} [attributes={}]
1003   *         Element attributes to be applied.
1004   *
1005   * @param {ContentDescriptor} [content]
1006   *        A content descriptor object.
1007   *
1008   * @return {Element}
1009   *         The element that was created.
1010   */
1011  function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
1012    const el = document.createElement(tagName);
1013    Object.getOwnPropertyNames(properties).forEach(function (propName) {
1014      const val = properties[propName];
1015
1016      // Handle textContent since it's not supported everywhere and we have a
1017      // method for it.
1018      if (propName === 'textContent') {
1019        textContent(el, val);
1020      } else if (el[propName] !== val || propName === 'tabIndex') {
1021        el[propName] = val;
1022      }
1023    });
1024    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1025      el.setAttribute(attrName, attributes[attrName]);
1026    });
1027    if (content) {
1028      appendContent(el, content);
1029    }
1030    return el;
1031  }
1032
1033  /**
1034   * Injects text into an element, replacing any existing contents entirely.
1035   *
1036   * @param  {HTMLElement} el
1037   *         The element to add text content into
1038   *
1039   * @param  {string} text
1040   *         The text content to add.
1041   *
1042   * @return {Element}
1043   *         The element with added text content.
1044   */
1045  function textContent(el, text) {
1046    if (typeof el.textContent === 'undefined') {
1047      el.innerText = text;
1048    } else {
1049      el.textContent = text;
1050    }
1051    return el;
1052  }
1053
1054  /**
1055   * Insert an element as the first child node of another
1056   *
1057   * @param {Element} child
1058   *        Element to insert
1059   *
1060   * @param {Element} parent
1061   *        Element to insert child into
1062   */
1063  function prependTo(child, parent) {
1064    if (parent.firstChild) {
1065      parent.insertBefore(child, parent.firstChild);
1066    } else {
1067      parent.appendChild(child);
1068    }
1069  }
1070
1071  /**
1072   * Check if an element has a class name.
1073   *
1074   * @param  {Element} element
1075   *         Element to check
1076   *
1077   * @param  {string} classToCheck
1078   *         Class name to check for
1079   *
1080   * @return {boolean}
1081   *         Will be `true` if the element has a class, `false` otherwise.
1082   *
1083   * @throws {Error}
1084   *         Throws an error if `classToCheck` has white space.
1085   */
1086  function hasClass(element, classToCheck) {
1087    throwIfWhitespace(classToCheck);
1088    return element.classList.contains(classToCheck);
1089  }
1090
1091  /**
1092   * Add a class name to an element.
1093   *
1094   * @param  {Element} element
1095   *         Element to add class name to.
1096   *
1097   * @param  {...string} classesToAdd
1098   *         One or more class name to add.
1099   *
1100   * @return {Element}
1101   *         The DOM element with the added class name.
1102   */
1103  function addClass(element, ...classesToAdd) {
1104    element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
1105    return element;
1106  }
1107
1108  /**
1109   * Remove a class name from an element.
1110   *
1111   * @param  {Element} element
1112   *         Element to remove a class name from.
1113   *
1114   * @param  {...string} classesToRemove
1115   *         One or more class name to remove.
1116   *
1117   * @return {Element}
1118   *         The DOM element with class name removed.
1119   */
1120  function removeClass(element, ...classesToRemove) {
1121    // Protect in case the player gets disposed
1122    if (!element) {
1123      log$1.warn("removeClass was called with an element that doesn't exist");
1124      return null;
1125    }
1126    element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
1127    return element;
1128  }
1129
1130  /**
1131   * The callback definition for toggleClass.
1132   *
1133   * @callback 
1133module:dom~PredicateCallback
1134   * @param    {Element} element
1135   *           The DOM element of the Component.
1136   *
1137   * @param    {string} classToToggle
1138   *           The `className` that wants to be toggled
1139   *
1140   * @return   {boolean|undefined}
1141   *           If `true` is returned, the `classToToggle` will be added to the
1142   *           `element`. If `false`, the `classToToggle` will be removed from
1143   *           the `element`. If `undefined`, the callback will be ignored.
1144   */
1145
1146  /**
1147   * Adds or removes a class name to/from an element depending on an optional
1148   * condition or the presence/absence of the class name.
1149   *
1150   * @param  {Element} element
1151   *         The element to toggle a class name on.
1152   *
1153   * @param  {string} classToToggle
1154   *         The class that should be toggled.
1155   *
1156   * @param  {boolean|module:dom~
vendor: 7,975 bytes, lines 1156-1432
1156PredicateCallback} [predicate]
1157   *         See the return value for {@link module:dom~PredicateCallback}
1158   *
1159   * @return {Element}
1160   *         The element with a class that has been toggled.
1161   */
1162  function toggleClass(element, classToToggle, predicate) {
1163    if (typeof predicate === 'function') {
1164      predicate = predicate(element, classToToggle);
1165    }
1166    if (typeof predicate !== 'boolean') {
1167      predicate = undefined;
1168    }
1169    classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));
1170    return element;
1171  }
1172
1173  /**
1174   * Apply attributes to an HTML element.
1175   *
1176   * @param {Element} el
1177   *        Element to add attributes to.
1178   *
1179   * @param {Object} [attributes]
1180   *        Attributes to be applied.
1181   */
1182  function setAttributes(el, attributes) {
1183    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1184      const attrValue = attributes[attrName];
1185      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1186        el.removeAttribute(attrName);
1187      } else {
1188        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
1189      }
1190    });
1191  }
1192
1193  /**
1194   * Get an element's attribute values, as defined on the HTML tag.
1195   *
1196   * Attributes are not the same as properties. They're defined on the tag
1197   * or with setAttribute.
1198   *
1199   * @param  {Element} tag
1200   *         Element from which to get tag attributes.
1201   *
1202   * @return {Object}
1203   *         All attributes of the element. Boolean attributes will be `true` or
1204   *         `false`, others will be strings.
1205   */
1206  function getAttributes(tag) {
1207    const obj = {};
1208
1209    // known boolean attributes
1210    // we can check for matching boolean properties, but not all browsers
1211    // and not all tags know about these attributes, so, we still want to check them manually
1212    const knownBooleans = ['autoplay', 'controls', 'playsinline', 'loop', 'muted', 'default', 'defaultMuted'];
1213    if (tag && tag.attributes && tag.attributes.length > 0) {
1214      const attrs = tag.attributes;
1215      for (let i = attrs.length - 1; i >= 0; i--) {
1216        const attrName = attrs[i].name;
1217        /** @type {boolean|string} */
1218        let attrVal = attrs[i].value;
1219
1220        // check for known booleans
1221        // the matching element property will return a value for typeof
1222        if (knownBooleans.includes(attrName)) {
1223          // the value of an included boolean attribute is typically an empty
1224          // string ('') which would equal false if we just check for a false value.
1225          // we also don't want support bad code like autoplay='false'
1226          attrVal = attrVal !== null ? true : false;
1227        }
1228        obj[attrName] = attrVal;
1229      }
1230    }
1231    return obj;
1232  }
1233
1234  /**
1235   * Get the value of an element's attribute.
1236   *
1237   * @param {Element} el
1238   *        A DOM element.
1239   *
1240   * @param {string} attribute
1241   *        Attribute to get the value of.
1242   *
1243   * @return {string}
1244   *         The value of the attribute.
1245   */
1246  function getAttribute(el, attribute) {
1247    return el.getAttribute(attribute);
1248  }
1249
1250  /**
1251   * Set the value of an element's attribute.
1252   *
1253   * @param {Element} el
1254   *        A DOM element.
1255   *
1256   * @param {string} attribute
1257   *        Attribute to set.
1258   *
1259   * @param {string} value
1260   *        Value to set the attribute to.
1261   */
1262  function setAttribute(el, attribute, value) {
1263    el.setAttribute(attribute, value);
1264  }
1265
1266  /**
1267   * Remove an element's attribute.
1268   *
1269   * @param {Element} el
1270   *        A DOM element.
1271   *
1272   * @param {string} attribute
1273   *        Attribute to remove.
1274   */
1275  function removeAttribute(el, attribute) {
1276    el.removeAttribute(attribute);
1277  }
1278
1279  /**
1280   * Attempt to block the ability to select text.
1281   */
1282  function blockTextSelection() {
1283    document.body.focus();
1284    document.onselectstart = function () {
1285      return false;
1286    };
1287  }
1288
1289  /**
1290   * Turn off text selection blocking.
1291   */
1292  function unblockTextSelection() {
1293    document.onselectstart = function () {
1294      return true;
1295    };
1296  }
1297
1298  /**
1299   * Identical to the native `getBoundingClientRect` function, but ensures that
1300   * the method is supported at all (it is in all browsers we claim to support)
1301   * and that the element is in the DOM before continuing.
1302   *
1303   * This wrapper function also shims properties which are not provided by some
1304   * older browsers (namely, IE8).
1305   *
1306   * Additionally, some browsers do not support adding properties to a
1307   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1308   * properties (except `x` and `y` which are not widely supported). This helps
1309   * avoid implementations where keys are non-enumerable.
1310   *
1311   * @param  {Element} el
1312   *         Element whose `ClientRect` we want to calculate.
1313   *
1314   * @return {Object|undefined}
1315   *         Always returns a plain object - or `undefined` if it cannot.
1316   */
1317  function getBoundingClientRect(el) {
1318    if (el && el.getBoundingClientRect && el.parentNode) {
1319      const rect = el.getBoundingClientRect();
1320      const result = {};
1321      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
1322        if (rect[k] !== undefined) {
1323          result[k] = rect[k];
1324        }
1325      });
1326      if (!result.height) {
1327        result.height = parseFloat(computedStyle(el, 'height'));
1328      }
1329      if (!result.width) {
1330        result.width = parseFloat(computedStyle(el, 'width'));
1331      }
1332      return result;
1333    }
1334  }
1335
1336  /**
1337   * Represents the position of a DOM element on the page.
1338   *
1339   * @typedef  {Object} module:dom~Position
1340   *
1341   * @property {number} left
1342   *           Pixels to the left.
1343   *
1344   * @property {number} top
1345   *           Pixels from the top.
1346   */
1347
1348  /**
1349   * Get the position of an element in the DOM.
1350   *
1351   * Uses `getBoundingClientRect` technique from John Resig.
1352   *
1353   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1354   *
1355   * @param  {Element} el
1356   *         Element from which to get offset.
1357   *
1358   * @return {module:dom~Position}
1359   *         The position of the element that was passed in.
1360   */
1361  function findPosition(el) {
1362    if (!el || el && !el.offsetParent) {
1363      return {
1364        left: 0,
1365        top: 0,
1366        width: 0,
1367        height: 0
1368      };
1369    }
1370    const width = el.offsetWidth;
1371    const height = el.offsetHeight;
1372    let left = 0;
1373    let top = 0;
1374    while (el.offsetParent && el !== document[FullscreenApi.fullscreenElement]) {
1375      left += el.offsetLeft;
1376      top += el.offsetTop;
1377      el = el.offsetParent;
1378    }
1379    return {
1380      left,
1381      top,
1382      width,
1383      height
1384    };
1385  }
1386
1387  /**
1388   * Represents x and y coordinates for a DOM element or mouse pointer.
1389   *
1390   * @typedef  {Object} module:dom~Coordinates
1391   *
1392   * @property {number} x
1393   *           x coordinate in pixels
1394   *
1395   * @property {number} y
1396   *           y coordinate in pixels
1397   */
1398
1399  /**
1400   * Get the pointer position within an element.
1401   *
1402   * The base on the coordinates are the bottom left of the element.
1403   *
1404   * @param  {Element} el
1405   *         Element on which to get the pointer position on.
1406   *
1407   * @param  {Event} event
1408   *         Event object.
1409   *
1410   * @return {module:dom~Coordinates}
1411   *         A coordinates object corresponding to the mouse position.
1412   *
1413   */
1414  function getPointerPosition(el, event) {
1415    const translated = {
1416      x: 0,
1417      y: 0
1418    };
1419    if (IS_IOS) {
1420      let item = el;
1421      while (item && item.nodeName.toLowerCase() !== 'html') {
1422        const transform = computedStyle(item, 'transform');
1423        if (/^matrix/.test(transform)) {
1424          const values = transform.slice(7, -1).split(/,\s/).map(Number);
1425          translated.x += values[4];
1426          translated.y += values[5];
1427        } else if (/^matrix3d/.test(transform)) {
1428          const values = transform.slice(9, -1).split(/,\s/).map(Number);
1429          translated.x += values[12];
1430          translated.y += values[13];
1431        }
1432        i
vendor: 6,178 bytes, lines 1432-1612
1432tem = item.parentNode;
1433      }
1434    }
1435    const position = {};
1436    const boxTarget = findPosition(event.target);
1437    const box = findPosition(el);
1438    const boxW = box.width;
1439    const boxH = box.height;
1440    let offsetY = event.offsetY - (box.top - boxTarget.top);
1441    let offsetX = event.offsetX - (box.left - boxTarget.left);
1442    if (event.changedTouches) {
1443      offsetX = event.changedTouches[0].pageX - box.left;
1444      offsetY = event.changedTouches[0].pageY + box.top;
1445      if (IS_IOS) {
1446        offsetX -= translated.x;
1447        offsetY -= translated.y;
1448      }
1449    }
1450    position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
1451    position.x = Math.max(0, Math.min(1, offsetX / boxW));
1452    return position;
1453  }
1454
1455  /**
1456   * Determines, via duck typing, whether or not a value is a text node.
1457   *
1458   * @param  {*} value
1459   *         Check if this value is a text node.
1460   *
1461   * @return {boolean}
1462   *         Will be `true` if the value is a text node, `false` otherwise.
1463   */
1464  function isTextNode$1(value) {
1465    return isObject$1(value) && value.nodeType === 3;
1466  }
1467
1468  /**
1469   * Empties the contents of an element.
1470   *
1471   * @param  {Element} el
1472   *         The element to empty children from
1473   *
1474   * @return {Element}
1475   *         The element with no children
1476   */
1477  function emptyEl(el) {
1478    while (el.firstChild) {
1479      el.removeChild(el.firstChild);
1480    }
1481    return el;
1482  }
1483
1484  /**
1485   * This is a mixed value that describes content to be injected into the DOM
1486   * via some method. It can be of the following types:
1487   *
1488   * Type       | Description
1489   * -----------|-------------
1490   * `string`   | The value will be normalized into a text node.
1491   * `Element`  | The value will be accepted as-is.
1492   * `Text`     | A TextNode. The value will be accepted as-is.
1493   * `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).
1494   * `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.
1495   *
1496   * @typedef {string|Element|Text|Array|Function} ContentDescriptor
1497   */
1498
1499  /**
1500   * Normalizes content for eventual insertion into the DOM.
1501   *
1502   * This allows a wide range of content definition methods, but helps protect
1503   * from falling into the trap of simply writing to `innerHTML`, which could
1504   * be an XSS concern.
1505   *
1506   * The content for an element can be passed in multiple types and
1507   * combinations, whose behavior is as follows:
1508   *
1509   * @param {ContentDescriptor} content
1510   *        A content descriptor value.
1511   *
1512   * @return {Array}
1513   *         All of the content that was passed in, normalized to an array of
1514   *         elements or text nodes.
1515   */
1516  function normalizeContent(content) {
1517    // First, invoke content if it is a function. If it produces an array,
1518    // that needs to happen before normalization.
1519    if (typeof content === 'function') {
1520      content = content();
1521    }
1522
1523    // Next up, normalize to an array, so one or many items can be normalized,
1524    // filtered, and returned.
1525    return (Array.isArray(content) ? content : [content]).map(value => {
1526      // First, invoke value if it is a function to produce a new value,
1527      // which will be subsequently normalized to a Node of some kind.
1528      if (typeof value === 'function') {
1529        value = value();
1530      }
1531      if (isEl(value) || isTextNode$1(value)) {
1532        return value;
1533      }
1534      if (typeof value === 'string' && /\S/.test(value)) {
1535        return document.createTextNode(value);
1536      }
1537    }).filter(value => value);
1538  }
1539
1540  /**
1541   * Normalizes and appends content to an element.
1542   *
1543   * @param  {Element} el
1544   *         Element to append normalized content to.
1545   *
1546   * @param {ContentDescriptor} content
1547   *        A content descriptor value.
1548   *
1549   * @return {Element}
1550   *         The element with appended normalized content.
1551   */
1552  function appendContent(el, content) {
1553    normalizeContent(content).forEach(node => el.appendChild(node));
1554    return el;
1555  }
1556
1557  /**
1558   * Normalizes and inserts content into an element; this is identical to
1559   * `appendContent()`, except it empties the element first.
1560   *
1561   * @param {Element} el
1562   *        Element to insert normalized content into.
1563   *
1564   * @param {ContentDescriptor} content
1565   *        A content descriptor value.
1566   *
1567   * @return {Element}
1568   *         The element with inserted normalized content.
1569   */
1570  function insertContent(el, content) {
1571    return appendContent(emptyEl(el), content);
1572  }
1573
1574  /**
1575   * Check if an event was a single left click.
1576   *
1577   * @param  {MouseEvent} event
1578   *         Event object.
1579   *
1580   * @return {boolean}
1581   *         Will be `true` if a single left click, `false` otherwise.
1582   */
1583  function isSingleLeftClick(event) {
1584    // Note: if you create something draggable, be sure to
1585    // call it on both `mousedown` and `mousemove` event,
1586    // otherwise `mousedown` should be enough for a button
1587
1588    if (event.button === undefined && event.buttons === undefined) {
1589      // Why do we need `buttons` ?
1590      // Because, middle mouse sometimes have this:
1591      // e.button === 0 and e.buttons === 4
1592      // Furthermore, we want to prevent combination click, something like
1593      // HOLD middlemouse then left click, that would be
1594      // e.button === 0, e.buttons === 5
1595      // just `button` is not gonna work
1596
1597      // Alright, then what this block does ?
1598      // this is for chrome `simulate mobile devices`
1599      // I want to support this as well
1600
1601      return true;
1602    }
1603    if (event.button === 0 && event.buttons === undefined) {
1604      // Touch screen, sometimes on some specific device, `buttons`
1605      // doesn't have anything (safari on ios, blackberry...)
1606
1607      return true;
1608    }
1609
1610    // `mouseup` event on a single left click has
1611    // `button` and `buttons` equal to 0
1612    if (event.type === 'mouseup
vendor: 12,922 bytes, lines 1612-2043
1612' && event.button === 0 && event.buttons === 0) {
1613      return true;
1614    }
1615    if (event.button !== 0 || event.buttons !== 1) {
1616      // This is the reason we have those if else block above
1617      // if any special case we can catch and let it slide
1618      // we do it above, when get to here, this definitely
1619      // is-not-left-click
1620
1621      return false;
1622    }
1623    return true;
1624  }
1625
1626  /**
1627   * Finds a single DOM element matching `selector` within the optional
1628   * `context` of another DOM element (defaulting to `document`).
1629   *
1630   * @param  {string} selector
1631   *         A valid CSS selector, which will be passed to `querySelector`.
1632   *
1633   * @param  {Element|String} [context=document]
1634   *         A DOM element within which to query. Can also be a selector
1635   *         string in which case the first matching element will be used
1636   *         as context. If missing (or no element matches selector), falls
1637   *         back to `document`.
1638   *
1639   * @return {Element|null}
1640   *         The element that was found or null.
1641   */
1642  const $ = createQuerier('querySelector');
1643
1644  /**
1645   * Finds a all DOM elements matching `selector` within the optional
1646   * `context` of another DOM element (defaulting to `document`).
1647   *
1648   * @param  {string} selector
1649   *         A valid CSS selector, which will be passed to `querySelectorAll`.
1650   *
1651   * @param  {Element|String} [context=document]
1652   *         A DOM element within which to query. Can also be a selector
1653   *         string in which case the first matching element will be used
1654   *         as context. If missing (or no element matches selector), falls
1655   *         back to `document`.
1656   *
1657   * @return {NodeList}
1658   *         A element list of elements that were found. Will be empty if none
1659   *         were found.
1660   *
1661   */
1662  const $$ = createQuerier('querySelectorAll');
1663
1664  /**
1665   * A safe getComputedStyle.
1666   *
1667   * This is needed because in Firefox, if the player is loaded in an iframe with
1668   * `display:none`, then `getComputedStyle` returns `null`, so, we do a
1669   * null-check to make sure that the player doesn't break in these cases.
1670   *
1671   * @param    {Element} el
1672   *           The element you want the computed style of
1673   *
1674   * @param    {string} prop
1675   *           The property name you want
1676   *
1677   * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
1678   */
1679  function computedStyle(el, prop) {
1680    if (!el || !prop) {
1681      return '';
1682    }
1683    if (typeof window.getComputedStyle === 'function') {
1684      let computedStyleValue;
1685      try {
1686        computedStyleValue = window.getComputedStyle(el);
1687      } catch (e) {
1688        return '';
1689      }
1690      return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
1691    }
1692    return '';
1693  }
1694
1695  /**
1696   * Copy document style sheets to another window.
1697   *
1698   * @param    {Window} win
1699   *           The window element you want to copy the document style sheets to.
1700   *
1701   */
1702  function copyStyleSheetsToWindow(win) {
1703    [...document.styleSheets].forEach(styleSheet => {
1704      try {
1705        const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
1706        const style = document.createElement('style');
1707        style.textContent = cssRules;
1708        win.document.head.appendChild(style);
1709      } catch (e) {
1710        const link = document.createElement('link');
1711        link.rel = 'stylesheet';
1712        link.type = styleSheet.type;
1713        // For older Safari this has to be the string; on other browsers setting the MediaList works
1714        link.media = styleSheet.media.mediaText;
1715        link.href = styleSheet.href;
1716        win.document.head.appendChild(link);
1717      }
1718    });
1719  }
1720
1721  var Dom = /*#__PURE__*/Object.freeze({
1722    __proto__: null,
1723    isReal: isReal,
1724    isEl: isEl,
1725    isInFrame: isInFrame,
1726    createEl: createEl,
1727    textContent: textContent,
1728    prependTo: prependTo,
1729    hasClass: hasClass,
1730    addClass: addClass,
1731    removeClass: removeClass,
1732    toggleClass: toggleClass,
1733    setAttributes: setAttributes,
1734    getAttributes: getAttributes,
1735    getAttribute: getAttribute,
1736    setAttribute: setAttribute,
1737    removeAttribute: removeAttribute,
1738    blockTextSelection: blockTextSelection,
1739    unblockTextSelection: unblockTextSelection,
1740    getBoundingClientRect: getBoundingClientRect,
1741    findPosition: findPosition,
1742    getPointerPosition: getPointerPosition,
1743    isTextNode: isTextNode$1,
1744    emptyEl: emptyEl,
1745    normalizeContent: normalizeContent,
1746    appendContent: appendContent,
1747    insertContent: insertContent,
1748    isSingleLeftClick: isSingleLeftClick,
1749    $: $,
1750    $$: $$,
1751    computedStyle: computedStyle,
1752    copyStyleSheetsToWindow: copyStyleSheetsToWindow
1753  });
1754
1755  /**
1756   * @file setup.js - Functions for setting up a player without
1757   * user interaction based on the data-setup `attribute` of the video tag.
1758   *
1759   * @module setup
1760   */
1761  let _windowLoaded = false;
1762  let videojs$1;
1763
1764  /**
1765   * Set up any tags that have a data-setup `attribute` when the player is started.
1766   */
1767  const autoSetup = function () {
1768    if (videojs$1.options.autoSetup === false) {
1769      return;
1770    }
1771    const vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
1772    const audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
1773    const divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
1774    const mediaEls = vids.concat(audios, divs);
1775
1776    // Check if any media elements exist
1777    if (mediaEls && mediaEls.length > 0) {
1778      for (let i = 0, e = mediaEls.length; i < e; i++) {
1779        const mediaEl = mediaEls[i];
1780
1781        // Check if element exists, has getAttribute func.
1782        if (mediaEl && mediaEl.getAttribute) {
1783          // Make sure this player hasn't already been set up.
1784          if (mediaEl.player === undefined) {
1785            const options = mediaEl.getAttribute('data-setup');
1786
1787            // Check if data-setup attr exists.
1788            // We only auto-setup if they've added the data-setup attr.
1789            if (options !== null) {
1790              // Create new video.js instance.
1791              videojs$1(mediaEl);
1792            }
1793          }
1794
1795          // If getAttribute isn't defined, we need to wait for the DOM.
1796        } else {
1797          autoSetupTimeout(1);
1798          break;
1799        }
1800      }
1801
1802      // No videos were found, so keep looping unless page is finished loading.
1803    } else if (!_windowLoaded) {
1804      autoSetupTimeout(1);
1805    }
1806  };
1807
1808  /**
1809   * Wait until the page is loaded before running autoSetup. This will be called in
1810   * autoSetup if `hasLoaded` returns false.
1811   *
1812   * @param {number} wait
1813   *        How long to wait in ms
1814   *
1815   * @param {module:videojs} [vjs]
1816   *        The videojs library function
1817   */
1818  function autoSetupTimeout(wait, vjs) {
1819    // Protect against breakage in non-browser environments
1820    if (!isReal()) {
1821      return;
1822    }
1823    if (vjs) {
1824      videojs$1 = vjs;
1825    }
1826    window.setTimeout(autoSetup, wait);
1827  }
1828
1829  /**
1830   * Used to set the internal tracking of window loaded state to true.
1831   *
1832   * @private
1833   */
1834  function setWindowLoaded() {
1835    _windowLoaded = true;
1836    window.removeEventListener('load', setWindowLoaded);
1837  }
1838  if (isReal()) {
1839    if (document.readyState === 'complete') {
1840      setWindowLoaded();
1841    } else {
1842      /**
1843       * Listen for the load event on window, and set _windowLoaded to true.
1844       *
1845       * We use a standard event listener here to avoid incrementing the GUID
1846       * before any players are created.
1847       *
1848       * @listens load
1849       */
1850      window.addEventListener('load', setWindowLoaded);
1851    }
1852  }
1853
1854  /**
1855   * @file stylesheet.js
1856   * @module stylesheet
1857   */
1858
1859  /**
1860   * Create a DOM style element given a className for it.
1861   *
1862   * @param {string} className
1863   *        The className to add to the created style element.
1864   *
1865   * @return {Element}
1866   *         The element that was created.
1867   */
1868  const createStyleElement = function (className) {
1869    const style = document.createElement('style');
1870    style.className = className;
1871    return style;
1872  };
1873
1874  /**
1875   * Add text to a DOM element.
1876   *
1877   * @param {Element} el
1878   *        The Element to add text content to.
1879   *
1880   * @param {string} content
1881   *        The text to add to the element.
1882   */
1883  const setTextContent = function (el, content) {
1884    if (el.styleSheet) {
1885      el.styleSheet.cssText = content;
1886    } else {
1887      el.textContent = content;
1888    }
1889  };
1890
1891  /**
1892   * @file dom-data.js
1893   * @module dom-data
1894   */
1895
1896  /**
1897   * Element Data Store.
1898   *
1899   * Allows for binding data to an element without putting it directly on the
1900   * element. Ex. Event listeners are stored here.
1901   * (also from jsninja.com, slightly modified and updated for closure compiler)
1902   *
1903   * @type {Object}
1904   * @private
1905   */
1906  var DomData = new WeakMap();
1907
1908  /**
1909   * @file guid.js
1910   * @module guid
1911   */
1912
1913  // Default value for GUIDs. This allows us to reset the GUID counter in tests.
1914  //
1915  // The initial GUID is 3 because some users have come to rely on the first
1916  // default player ID ending up as `vjs_video_3`.
1917  //
1918  // See: https://github.com/videojs/video.js/pull/6216
1919  const _initialGuid = 3;
1920
1921  /**
1922   * Unique ID for an element or function
1923   *
1924   * @type {Number}
1925   */
1926  let _guid = _initialGuid;
1927
1928  /**
1929   * Get a unique auto-incrementing ID by number that has not been returned before.
1930   *
1931   * @return {number}
1932   *         A new unique ID.
1933   */
1934  function newGUID() {
1935    return _guid++;
1936  }
1937
1938  /**
1939   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1940   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1941   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1942   * robust as jquery's, so there's probably some differences.
1943   *
1944   * @file events.js
1945   * @module events
1946   */
1947
1948  /**
1949   * Clean up the listener cache and dispatchers
1950   *
1951   * @param {Element|Object} elem
1952   *        Element to clean up
1953   *
1954   * @param {string} type
1955   *        Type of event to clean up
1956   */
1957  function _cleanUpEvents(elem, type) {
1958    if (!DomData.has(elem)) {
1959      return;
1960    }
1961    const data = DomData.get(elem);
1962
1963    // Remove the events of a particular type if there are none left
1964    if (data.handlers[type].length === 0) {
1965      delete data.handlers[type];
1966      // data.handlers[type] = null;
1967      // Setting to null was causing an error with data.handlers
1968
1969      // Remove the meta-handler from the element
1970      if (elem.removeEventListener) {
1971        elem.removeEventListener(type, data.dispatcher, false);
1972      } else if (elem.detachEvent) {
1973        elem.detachEvent('on' + type, data.dispatcher);
1974      }
1975    }
1976
1977    // Remove the events object if there are no types left
1978    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1979      delete data.handlers;
1980      delete data.dispatcher;
1981      delete data.disabled;
1982    }
1983
1984    // Finally remove the element data if there is no data left
1985    if (Object.getOwnPropertyNames(data).length === 0) {
1986      DomData.delete(elem);
1987    }
1988  }
1989
1990  /**
1991   * Loops through an array of event types and calls the requested method for each type.
1992   *
1993   * @param {Function} fn
1994   *        The event method we want to use.
1995   *
1996   * @param {Element|Object} elem
1997   *        Element or object to bind listeners to
1998   *
1999   * @param {string[]} types
2000   *        Type of event to bind to.
2001   *
2002   * @param {Function} callback
2003   *        Event listener.
2004   */
2005  function _handleMultipleEvents(fn, elem, types, callback) {
2006    types.forEach(function (type) {
2007      // Call the event method for each one of the types
2008      fn(elem, type, callback);
2009    });
2010  }
2011
2012  /**
2013   * Fix a native event to have standard property values
2014   *
2015   * @param {Object} event
2016   *        Event object to fix.
2017   *
2018   * @return {Object}
2019   *         Fixed event object.
2020   */
2021  function fixEvent(event) {
2022    if (event.fixed_) {
2023      return event;
2024    }
2025    function returnTrue() {
2026      return true;
2027    }
2028    function returnFalse() {
2029      return false;
2030    }
2031
2032    // Test if fixing up is needed
2033    // Used to check if !event.stopPropagation instead of isPropagationStopped
2034    // But native events return true for stopPropagation, but don't have
2035    // other expected methods like isPropagationStopped. Seems to be a problem
2036    // with the Javascript Ninja code. So we're just overriding all events now.
2037    if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
2038      const old = event || window.event;
2039      event = {};
2040      // Clone the old object so that we can modify the values event = {};
2041      // IE8 Doesn't like when you mess with native event properties
2042      // Firefox returns false for event.hasOwnProperty('type') and other props
2043      //  which makes copying more difficult.
2044      // TODO: Probably best to create a whitelist of event props
2045      for (const key in old) {
2046        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
2047        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
2048        // and webkitMovementX/Y
2049        // Lighthouse complains if Event.path is copied
2050        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY' && key !== 'path') {
2051          // Chrome 32+ warns if you try to copy deprecated returnValue, but
2052          // we still want to if preventDefault isn't supported (IE8).
2053          if (!(key === 'returnValue' && old.preventDefault)) {
2054            event[key] = old[key];
2055          }
2056        }
2057      }
2058
2059      // The event occurred on this element
2060      if (!event.target) {
2061        event.target = event.srcElement || document;
2062      }
2063
2064      // Handle which other element the event is related to
2065      if (!event.relatedTarget) {
2066        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
2067      }
2068
2069      // Stop the default browser action
2070      event.preventDefault = function () {
2071        if (old.preventDefault) {
2072          old.preventDefault();
2073        }
2074        event.returnValue = false;
vendor: 2,327 bytes, lines 2075-2142
2075        old.returnValue = false;
2076        event.defaultPrevented = true;
2077      };
2078      event.defaultPrevented = false;
2079
2080      // Stop the event from bubbling
2081      event.stopPropagation = function () {
2082        if (old.stopPropagation) {
2083          old.stopPropagation();
2084        }
2085        event.cancelBubble = true;
2086        old.cancelBubble = true;
2087        event.isPropagationStopped = returnTrue;
2088      };
2089      event.isPropagationStopped = returnFalse;
2090
2091      // Stop the event from bubbling and executing other handlers
2092      event.stopImmediatePropagation = function () {
2093        if (old.stopImmediatePropagation) {
2094          old.stopImmediatePropagation();
2095        }
2096        event.isImmediatePropagationStopped = returnTrue;
2097        event.stopPropagation();
2098      };
2099      event.isImmediatePropagationStopped = returnFalse;
2100
2101      // Handle mouse position
2102      if (event.clientX !== null && event.clientX !== undefined) {
2103        const doc = document.documentElement;
2104        const body = document.body;
2105        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
2106        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
2107      }
2108
2109      // Handle key presses
2110      event.which = event.charCode || event.keyCode;
2111
2112      // Fix button for mouse clicks:
2113      // 0 == left; 1 == middle; 2 == right
2114      if (event.button !== null && event.button !== undefined) {
2115        // The following is disabled because it does not pass videojs-standard
2116        // and... yikes.
2117        /* eslint-disable */
2118        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
2119        /* eslint-enable */
2120      }
2121    }
2122
2123    event.fixed_ = true;
2124    // Returns fixed-up instance
2125    return event;
2126  }
2127
2128  /**
2129   * Whether passive event listeners are supported
2130   */
2131  let _supportsPassive;
2132  const supportsPassive = function () {
2133    if (typeof _supportsPassive !== 'boolean') {
2134      _supportsPassive = false;
2135      try {
2136        const opts = Object.defineProperty({}, 'passive', {
2137          get() {
2138            _supportsPassive = true;
2139          }
2140        });
2141        window.addEventListener('test', null, opts);
2142        window
vendor: 10,076 bytes, lines 2142-2490
2142.removeEventListener('test', null, opts);
2143      } catch (e) {
2144        // disregard
2145      }
2146    }
2147    return _supportsPassive;
2148  };
2149
2150  /**
2151   * Touch events Chrome expects to be passive
2152   */
2153  const passiveEvents = ['touchstart', 'touchmove'];
2154
2155  /**
2156   * Add an event listener to element
2157   * It stores the handler function in a separate cache object
2158   * and adds a generic handler to the element's event,
2159   * along with a unique id (guid) to the element.
2160   *
2161   * @param {Element|Object} elem
2162   *        Element or object to bind listeners to
2163   *
2164   * @param {string|string[]} type
2165   *        Type of event to bind to.
2166   *
2167   * @param {Function} fn
2168   *        Event listener.
2169   */
2170  function on(elem, type, fn) {
2171    if (Array.isArray(type)) {
2172      return _handleMultipleEvents(on, elem, type, fn);
2173    }
2174    if (!DomData.has(elem)) {
2175      DomData.set(elem, {});
2176    }
2177    const data = DomData.get(elem);
2178
2179    // We need a place to store all our handler data
2180    if (!data.handlers) {
2181      data.handlers = {};
2182    }
2183    if (!data.handlers[type]) {
2184      data.handlers[type] = [];
2185    }
2186    if (!fn.guid) {
2187      fn.guid = newGUID();
2188    }
2189    data.handlers[type].push(fn);
2190    if (!data.dispatcher) {
2191      data.disabled = false;
2192      data.dispatcher = function (event, hash) {
2193        if (data.disabled) {
2194          return;
2195        }
2196        event = fixEvent(event);
2197        const handlers = data.handlers[event.type];
2198        if (handlers) {
2199          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
2200          const handlersCopy = handlers.slice(0);
2201          for (let m = 0, n = handlersCopy.length; m < n; m++) {
2202            if (event.isImmediatePropagationStopped()) {
2203              break;
2204            } else {
2205              try {
2206                handlersCopy[m].call(elem, event, hash);
2207              } catch (e) {
2208                log$1.error(e);
2209              }
2210            }
2211          }
2212        }
2213      };
2214    }
2215    if (data.handlers[type].length === 1) {
2216      if (elem.addEventListener) {
2217        let options = false;
2218        if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
2219          options = {
2220            passive: true
2221          };
2222        }
2223        elem.addEventListener(type, data.dispatcher, options);
2224      } else if (elem.attachEvent) {
2225        elem.attachEvent('on' + type, data.dispatcher);
2226      }
2227    }
2228  }
2229
2230  /**
2231   * Removes event listeners from an element
2232   *
2233   * @param {Element|Object} elem
2234   *        Object to remove listeners from.
2235   *
2236   * @param {string|string[]} [type]
2237   *        Type of listener to remove. Don't include to remove all events from element.
2238   *
2239   * @param {Function} [fn]
2240   *        Specific listener to remove. Don't include to remove listeners for an event
2241   *        type.
2242   */
2243  function off(elem, type, fn) {
2244    // Don't want to add a cache object through getElData if not needed
2245    if (!DomData.has(elem)) {
2246      return;
2247    }
2248    const data = DomData.get(elem);
2249
2250    // If no events exist, nothing to unbind
2251    if (!data.handlers) {
2252      return;
2253    }
2254    if (Array.isArray(type)) {
2255      return _handleMultipleEvents(off, elem, type, fn);
2256    }
2257
2258    // Utility function
2259    const removeType = function (el, t) {
2260      data.handlers[t] = [];
2261      _cleanUpEvents(el, t);
2262    };
2263
2264    // Are we removing all bound events?
2265    if (type === undefined) {
2266      for (const t in data.handlers) {
2267        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
2268          removeType(elem, t);
2269        }
2270      }
2271      return;
2272    }
2273    const handlers = data.handlers[type];
2274
2275    // If no handlers exist, nothing to unbind
2276    if (!handlers) {
2277      return;
2278    }
2279
2280    // If no listener was provided, remove all listeners for type
2281    if (!fn) {
2282      removeType(elem, type);
2283      return;
2284    }
2285
2286    // We're only removing a single handler
2287    if (fn.guid) {
2288      for (let n = 0; n < handlers.length; n++) {
2289        if (handlers[n].guid === fn.guid) {
2290          handlers.splice(n--, 1);
2291        }
2292      }
2293    }
2294    _cleanUpEvents(elem, type);
2295  }
2296
2297  /**
2298   * Trigger an event for an element
2299   *
2300   * @param {Element|Object} elem
2301   *        Element to trigger an event on
2302   *
2303   * @param {EventTarget~Event|string} event
2304   *        A string (the type) or an event object with a type attribute
2305   *
2306   * @param {Object} [hash]
2307   *        data hash to pass along with the event
2308   *
2309   * @return {boolean|undefined}
2310   *         Returns the opposite of `defaultPrevented` if default was
2311   *         prevented. Otherwise, returns `undefined`
2312   */
2313  function trigger(elem, event, hash) {
2314    // Fetches element data and a reference to the parent (for bubbling).
2315    // Don't want to add a data object to cache for every parent,
2316    // so checking hasElData first.
2317    const elemData = DomData.has(elem) ? DomData.get(elem) : {};
2318    const parent = elem.parentNode || elem.ownerDocument;
2319    // type = event.type || event,
2320    // handler;
2321
2322    // If an event name was passed as a string, creates an event out of it
2323    if (typeof event === 'string') {
2324      event = {
2325        type: event,
2326        target: elem
2327      };
2328    } else if (!event.target) {
2329      event.target = elem;
2330    }
2331
2332    // Normalizes the event properties.
2333    event = fixEvent(event);
2334
2335    // If the passed element has a dispatcher, executes the established handlers.
2336    if (elemData.dispatcher) {
2337      elemData.dispatcher.call(elem, event, hash);
2338    }
2339
2340    // Unless explicitly stopped or the event does not bubble (e.g. media events)
2341    // recursively calls this function to bubble the event up the DOM.
2342    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2343      trigger.call(null, parent, event, hash);
2344
2345      // If at the top of the DOM, triggers the default action unless disabled.
2346    } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2347      if (!DomData.has(event.target)) {
2348        DomData.set(event.target, {});
2349      }
2350      const targetData = DomData.get(event.target);
2351
2352      // Checks if the target has a default action for this event.
2353      if (event.target[event.type]) {
2354        // Temporarily disables event dispatching on the target as we have already executed the handler.
2355        targetData.disabled = true;
2356        // Executes the default action.
2357        if (typeof event.target[event.type] === 'function') {
2358          event.target[event.type]();
2359        }
2360        // Re-enables event dispatching.
2361        targetData.disabled = false;
2362      }
2363    }
2364
2365    // Inform the triggerer if the default was prevented by returning false
2366    return !event.defaultPrevented;
2367  }
2368
2369  /**
2370   * Trigger a listener only once for an event.
2371   *
2372   * @param {Element|Object} elem
2373   *        Element or object to bind to.
2374   *
2375   * @param {string|string[]} type
2376   *        Name/type of event
2377   *
2378   * @param {Event~EventListener} fn
2379   *        Event listener function
2380   */
2381  function one(elem, type, fn) {
2382    if (Array.isArray(type)) {
2383      return _handleMultipleEvents(one, elem, type, fn);
2384    }
2385    const func = function () {
2386      off(elem, type, func);
2387      fn.apply(this, arguments);
2388    };
2389
2390    // copy the guid to the new function so it can removed using the original function's ID
2391    func.guid = fn.guid = fn.guid || newGUID();
2392    on(elem, type, func);
2393  }
2394
2395  /**
2396   * Trigger a listener only once and then turn if off for all
2397   * configured events
2398   *
2399   * @param {Element|Object} elem
2400   *        Element or object to bind to.
2401   *
2402   * @param {string|string[]} type
2403   *        Name/type of event
2404   *
2405   * @param {Event~EventListener} fn
2406   *        Event listener function
2407   */
2408  function any(elem, type, fn) {
2409    const func = function () {
2410      off(elem, type, func);
2411      fn.apply(this, arguments);
2412    };
2413
2414    // copy the guid to the new function so it can removed using the original function's ID
2415    func.guid = fn.guid = fn.guid || newGUID();
2416
2417    // multiple ons, but one off for everything
2418    on(elem, type, func);
2419  }
2420
2421  var Events = /*#__PURE__*/Object.freeze({
2422    __proto__: null,
2423    fixEvent: fixEvent,
2424    on: on,
2425    off: off,
2426    trigger: trigger,
2427    one: one,
2428    any: any
2429  });
2430
2431  /**
2432   * @file fn.js
2433   * @module fn
2434   */
2435  const UPDATE_REFRESH_INTERVAL = 30;
2436
2437  /**
2438   * A private, internal-only function for changing the context of a function.
2439   *
2440   * It also stores a unique id on the function so it can be easily removed from
2441   * events.
2442   *
2443   * @private
2444   * @function
2445   * @param    {*} context
2446   *           The object to bind as scope.
2447   *
2448   * @param    {Function} fn
2449   *           The function to be bound to a scope.
2450   *
2451   * @param    {number} [uid]
2452   *           An optional unique ID for the function to be set
2453   *
2454   * @return   {Function}
2455   *           The new function that will be bound into the context given
2456   */
2457  const bind_ = function (context, fn, uid) {
2458    // Make sure the function has a unique ID
2459    if (!fn.guid) {
2460      fn.guid = newGUID();
2461    }
2462
2463    // Create the new function that changes the context
2464    const bound = fn.bind(context);
2465
2466    // Allow for the ability to individualize this function
2467    // Needed in the case where multiple objects might share the same prototype
2468    // IF both items add an event listener with the same function, then you try to remove just one
2469    // it will remove both because they both have the same guid.
2470    // when using this, you need to use the bind method when you remove the listener as well.
2471    // currently used in text tracks
2472    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2473    return bound;
2474  };
2475
2476  /**
2477   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2478   * at most once per every `wait` milliseconds.
2479   *
2480   * @function
2481   * @param    {Function} fn
2482   *           The function to be throttled.
2483   *
2484   * @param    {number}   wait
2485   *           The number of milliseconds by which to throttle.
2486   *
2487   * @return   {Function}
2488   */
2489  const throttle = function (fn, wait) {
2490    let last = window
vendor: 2,592 bytes, lines 2490-2578
2490.performance.now();
2491    const throttled = function (...args) {
2492      const now = window.performance.now();
2493      if (now - last >= wait) {
2494        fn(...args);
2495        last = now;
2496      }
2497    };
2498    return throttled;
2499  };
2500
2501  /**
2502   * Creates a debounced function that delays invoking `func` until after `wait`
2503   * milliseconds have elapsed since the last time the debounced function was
2504   * invoked.
2505   *
2506   * Inspired by lodash and underscore implementations.
2507   *
2508   * @function
2509   * @param    {Function} func
2510   *           The function to wrap with debounce behavior.
2511   *
2512   * @param    {number} wait
2513   *           The number of milliseconds to wait after the last invocation.
2514   *
2515   * @param    {boolean} [immediate]
2516   *           Whether or not to invoke the function immediately upon creation.
2517   *
2518   * @param    {Object} [context=window]
2519   *           The "context" in which the debounced function should debounce. For
2520   *           example, if this function should be tied to a Video.js player,
2521   *           the player can be passed here. Alternatively, defaults to the
2522   *           global `window` object.
2523   *
2524   * @return   {Function}
2525   *           A debounced function.
2526   */
2527  const debounce = function (func, wait, immediate, context = window) {
2528    let timeout;
2529    const cancel = () => {
2530      context.clearTimeout(timeout);
2531      timeout = null;
2532    };
2533
2534    /* eslint-disable consistent-this */
2535    const debounced = function () {
2536      const self = this;
2537      const args = arguments;
2538      let later = function () {
2539        timeout = null;
2540        later = null;
2541        if (!immediate) {
2542          func.apply(self, args);
2543        }
2544      };
2545      if (!timeout && immediate) {
2546        func.apply(self, args);
2547      }
2548      context.clearTimeout(timeout);
2549      timeout = context.setTimeout(later, wait);
2550    };
2551    /* eslint-enable consistent-this */
2552
2553    debounced.cancel = cancel;
2554    return debounced;
2555  };
2556
2557  var Fn = /*#__PURE__*/Object.freeze({
2558    __proto__: null,
2559    UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
2560    bind_: bind_,
2561    throttle: throttle,
2562    debounce: debounce
2563  });
2564
2565  /**
2566   * @file src/js/event-target.js
2567   */
2568  let EVENT_MAP;
2569
2570  /**
2571   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2572   * adds shorthand functions that wrap around lengthy functions. For example:
2573   * the `on` function is a wrapper around `addEventListener`.
2574   *
2575   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2576   * @class EventTarget
2577   */
2578  class EventTarget$2
vendor: 4,713 bytes, lines 2578-2712
2578 {
2579    /**
2580     * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2581     * function that will get called when an event with a certain name gets triggered.
2582     *
2583     * @param {string|string[]} type
2584     *        An event name or an array of event names.
2585     *
2586     * @param {Function} fn
2587     *        The function to call with `EventTarget`s
2588     */
2589    on(type, fn) {
2590      // Remove the addEventListener alias before calling Events.on
2591      // so we don't get into an infinite type loop
2592      const ael = this.addEventListener;
2593      this.addEventListener = () => {};
2594      on(this, type, fn);
2595      this.addEventListener = ael;
2596    }
2597    /**
2598     * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2599     * This makes it so that the `event listener` will no longer get called when the
2600     * named event happens.
2601     *
2602     * @param {string|string[]} type
2603     *        An event name or an array of event names.
2604     *
2605     * @param {Function} fn
2606     *        The function to remove.
2607     */
2608    off(type, fn) {
2609      off(this, type, fn);
2610    }
2611    /**
2612     * This function will add an `event listener` that gets triggered only once. After the
2613     * first trigger it will get removed. This is like adding an `event listener`
2614     * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2615     *
2616     * @param {string|string[]} type
2617     *        An event name or an array of event names.
2618     *
2619     * @param {Function} fn
2620     *        The function to be called once for each event name.
2621     */
2622    one(type, fn) {
2623      // Remove the addEventListener aliasing Events.on
2624      // so we don't get into an infinite type loop
2625      const ael = this.addEventListener;
2626      this.addEventListener = () => {};
2627      one(this, type, fn);
2628      this.addEventListener = ael;
2629    }
2630    /**
2631     * This function will add an `event listener` that gets triggered only once and is
2632     * removed from all events. This is like adding an array of `event listener`s
2633     * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
2634     * first time it is triggered.
2635     *
2636     * @param {string|string[]} type
2637     *        An event name or an array of event names.
2638     *
2639     * @param {Function} fn
2640     *        The function to be called once for each event name.
2641     */
2642    any(type, fn) {
2643      // Remove the addEventListener aliasing Events.on
2644      // so we don't get into an infinite type loop
2645      const ael = this.addEventListener;
2646      this.addEventListener = () => {};
2647      any(this, type, fn);
2648      this.addEventListener = ael;
2649    }
2650    /**
2651     * This function causes an event to happen. This will then cause any `event listeners`
2652     * that are waiting for that event, to get called. If there are no `event listeners`
2653     * for an event then nothing will happen.
2654     *
2655     * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2656     * Trigger will also call the `on` + `uppercaseEventName` function.
2657     *
2658     * Example:
2659     * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2660     * `onClick` if it exists.
2661     *
2662     * @param {string|EventTarget~Event|Object} event
2663     *        The name of the event, an `Event`, or an object with a key of type set to
2664     *        an event name.
2665     */
2666    trigger(event) {
2667      const type = event.type || event;
2668
2669      // deprecation
2670      // In a future version we should default target to `this`
2671      // similar to how we default the target to `elem` in
2672      // `Events.trigger`. Right now the default `target` will be
2673      // `document` due to the `Event.fixEvent` call.
2674      if (typeof event === 'string') {
2675        event = {
2676          type
2677        };
2678      }
2679      event = fixEvent(event);
2680      if (this.allowedEvents_[type] && this['on' + type]) {
2681        this['on' + type](event);
2682      }
2683      trigger(this, event);
2684    }
2685    queueTrigger(event) {
2686      // only set up EVENT_MAP if it'll be used
2687      if (!EVENT_MAP) {
2688        EVENT_MAP = new Map();
2689      }
2690      const type = event.type || event;
2691      let map = EVENT_MAP.get(this);
2692      if (!map) {
2693        map = new Map();
2694        EVENT_MAP.set(this, map);
2695      }
2696      const oldTimeout = map.get(type);
2697      map.delete(type);
2698      window.clearTimeout(oldTimeout);
2699      const timeout = window.setTimeout(() => {
2700        map.delete(type);
2701        // if we cleared out all timeouts for the current target, delete its map
2702        if (map.size === 0) {
2703          map = null;
2704          EVENT_MAP.delete(this);
2705        }
2706        this.trigger(event);
2707      }, 0);
2708      map.set(type, timeout);
2709    }
2710  }
2711
2712  /**
vendor: 6,549 bytes, lines 2713-2936
2713   * A Custom DOM event.
2714   *
2715   * @typedef {CustomEvent} Event
2716   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2717   */
2718
2719  /**
2720   * All event listeners should follow the following format.
2721   *
2722   * @callback EventListener
2723   * @this {EventTarget}
2724   *
2725   * @param {Event} event
2726   *        the event that triggered this function
2727   *
2728   * @param {Object} [hash]
2729   *        hash of data sent during the event
2730   */
2731
2732  /**
2733   * An object containing event names as keys and booleans as values.
2734   *
2735   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2736   *         will have extra functionality. See that function for more information.
2737   *
2738   * @property EventTarget.prototype.allowedEvents_
2739   * @protected
2740   */
2741  EventTarget$2.prototype.allowedEvents_ = {};
2742
2743  /**
2744   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2745   * the standard DOM API.
2746   *
2747   * @function
2748   * @see {@link EventTarget#on}
2749   */
2750  EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
2751
2752  /**
2753   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2754   * the standard DOM API.
2755   *
2756   * @function
2757   * @see {@link EventTarget#off}
2758   */
2759  EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
2760
2761  /**
2762   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2763   * the standard DOM API.
2764   *
2765   * @function
2766   * @see {@link EventTarget#trigger}
2767   */
2768  EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
2769
2770  /**
2771   * @file mixins/evented.js
2772   * @module evented
2773   */
2774  const objName = obj => {
2775    if (typeof obj.name === 'function') {
2776      return obj.name();
2777    }
2778    if (typeof obj.name === 'string') {
2779      return obj.name;
2780    }
2781    if (obj.name_) {
2782      return obj.name_;
2783    }
2784    if (obj.constructor && obj.constructor.name) {
2785      return obj.constructor.name;
2786    }
2787    return typeof obj;
2788  };
2789
2790  /**
2791   * Returns whether or not an object has had the evented mixin applied.
2792   *
2793   * @param  {Object} object
2794   *         An object to test.
2795   *
2796   * @return {boolean}
2797   *         Whether or not the object appears to be evented.
2798   */
2799  const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
2800
2801  /**
2802   * Adds a callback to run after the evented mixin applied.
2803   *
2804   * @param  {Object} target
2805   *         An object to Add
2806   * @param  {Function} callback
2807   *         The callback to run.
2808   */
2809  const addEventedCallback = (target, callback) => {
2810    if (isEvented(target)) {
2811      callback();
2812    } else {
2813      if (!target.eventedCallbacks) {
2814        target.eventedCallbacks = [];
2815      }
2816      target.eventedCallbacks.push(callback);
2817    }
2818  };
2819
2820  /**
2821   * Whether a value is a valid event type - non-empty string or array.
2822   *
2823   * @private
2824   * @param  {string|Array} type
2825   *         The type value to test.
2826   *
2827   * @return {boolean}
2828   *         Whether or not the type is a valid event type.
2829   */
2830  const isValidEventType = type =>
2831  // The regex here verifies that the `type` contains at least one non-
2832  // whitespace character.
2833  typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length;
2834
2835  /**
2836   * Validates a value to determine if it is a valid event target. Throws if not.
2837   *
2838   * @private
2839   * @throws {Error}
2840   *         If the target does not appear to be a valid event target.
2841   *
2842   * @param  {Object} target
2843   *         The object to test.
2844   *
2845   * @param  {Object} obj
2846   *         The evented object we are validating for
2847   *
2848   * @param  {string} fnName
2849   *         The name of the evented mixin function that called this.
2850   */
2851  const validateTarget = (target, obj, fnName) => {
2852    if (!target || !target.nodeName && !isEvented(target)) {
2853      throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
2854    }
2855  };
2856
2857  /**
2858   * Validates a value to determine if it is a valid event target. Throws if not.
2859   *
2860   * @private
2861   * @throws {Error}
2862   *         If the type does not appear to be a valid event type.
2863   *
2864   * @param  {string|Array} type
2865   *         The type to test.
2866   *
2867   * @param  {Object} obj
2868  *         The evented object we are validating for
2869   *
2870   * @param  {string} fnName
2871   *         The name of the evented mixin function that called this.
2872   */
2873  const validateEventType = (type, obj, fnName) => {
2874    if (!isValidEventType(type)) {
2875      throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
2876    }
2877  };
2878
2879  /**
2880   * Validates a value to determine if it is a valid listener. Throws if not.
2881   *
2882   * @private
2883   * @throws {Error}
2884   *         If the listener is not a function.
2885   *
2886   * @param  {Function} listener
2887   *         The listener to test.
2888   *
2889   * @param  {Object} obj
2890   *         The evented object we are validating for
2891   *
2892   * @param  {string} fnName
2893   *         The name of the evented mixin function that called this.
2894   */
2895  const validateListener = (listener, obj, fnName) => {
2896    if (typeof listener !== 'function') {
2897      throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
2898    }
2899  };
2900
2901  /**
2902   * Takes an array of arguments given to `on()` or `one()`, validates them, and
2903   * normalizes them into an object.
2904   *
2905   * @private
2906   * @param  {Object} self
2907   *         The evented object on which `on()` or `one()` was called. This
2908   *         object will be bound as the `this` value for the listener.
2909   *
2910   * @param  {Array} args
2911   *         An array of arguments passed to `on()` or `one()`.
2912   *
2913   * @param  {string} fnName
2914   *         The name of the evented mixin function that called this.
2915   *
2916   * @return {Object}
2917   *         An object containing useful values for `on()` or `one()` calls.
2918   */
2919  const normalizeListenArgs = (self, args, fnName) => {
2920    // If the number of arguments is less than 3, the target is always the
2921    // evented object itself.
2922    const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2923    let target;
2924    let type;
2925    let listener;
2926    if (isTargetingSelf) {
2927      target = self.eventBusEl_;
2928
2929      // Deal with cases where we got 3 arguments, but we are still listening to
2930      // the evented object itself.
2931      if (args.length >= 3) {
2932        args.shift();
2933      }
2934      [type, listener] = args;
2935    } else {
2936      
2936[target, type, listener] = args
vendor: 11,498 bytes, lines 2936-3256
2936;
2937    }
2938    validateTarget(target, self, fnName);
2939    validateEventType(type, self, fnName);
2940    validateListener(listener, self, fnName);
2941    listener = bind_(self, listener);
2942    return {
2943      isTargetingSelf,
2944      target,
2945      type,
2946      listener
2947    };
2948  };
2949
2950  /**
2951   * Adds the listener to the event type(s) on the target, normalizing for
2952   * the type of target.
2953   *
2954   * @private
2955   * @param  {Element|Object} target
2956   *         A DOM node or evented object.
2957   *
2958   * @param  {string} method
2959   *         The event binding method to use ("on" or "one").
2960   *
2961   * @param  {string|Array} type
2962   *         One or more event type(s).
2963   *
2964   * @param  {Function} listener
2965   *         A listener function.
2966   */
2967  const listen = (target, method, type, listener) => {
2968    validateTarget(target, target, method);
2969    if (target.nodeName) {
2970      Events[method](target, type, listener);
2971    } else {
2972      target[method](type, listener);
2973    }
2974  };
2975
2976  /**
2977   * Contains methods that provide event capabilities to an object which is passed
2978   * to {@link module:evented|evented}.
2979   *
2980   * @mixin EventedMixin
2981   */
2982  const EventedMixin = {
2983    /**
2984     * Add a listener to an event (or events) on this object or another evented
2985     * object.
2986     *
2987     * @param  {string|Array|Element|Object} targetOrType
2988     *         If this is a string or array, it represents the event type(s)
2989     *         that will trigger the listener.
2990     *
2991     *         Another evented object can be passed here instead, which will
2992     *         cause the listener to listen for events on _that_ object.
2993     *
2994     *         In either case, the listener's `this` value will be bound to
2995     *         this object.
2996     *
2997     * @param  {string|Array|Function} typeOrListener
2998     *         If the first argument was a string or array, this should be the
2999     *         listener function. Otherwise, this is a string or array of event
3000     *         type(s).
3001     *
3002     * @param  {Function} [listener]
3003     *         If the first argument was another evented object, this will be
3004     *         the listener function.
3005     */
3006    on(...args) {
3007      const {
3008        isTargetingSelf,
3009        target,
3010        type,
3011        listener
3012      } = normalizeListenArgs(this, args, 'on');
3013      listen(target, 'on', type, listener);
3014
3015      // If this object is listening to another evented object.
3016      if (!isTargetingSelf) {
3017        // If this object is disposed, remove the listener.
3018        const removeListenerOnDispose = () => this.off(target, type, listener);
3019
3020        // Use the same function ID as the listener so we can remove it later it
3021        // using the ID of the original listener.
3022        removeListenerOnDispose.guid = listener.guid;
3023
3024        // Add a listener to the target's dispose event as well. This ensures
3025        // that if the target is disposed BEFORE this object, we remove the
3026        // removal listener that was just added. Otherwise, we create a memory leak.
3027        const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
3028
3029        // Use the same function ID as the listener so we can remove it later
3030        // it using the ID of the original listener.
3031        removeRemoverOnTargetDispose.guid = listener.guid;
3032        listen(this, 'on', 'dispose', removeListenerOnDispose);
3033        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
3034      }
3035    },
3036    /**
3037     * Add a listener to an event (or events) on this object or another evented
3038     * object. The listener will be called once per event and then removed.
3039     *
3040     * @param  {string|Array|Element|Object} targetOrType
3041     *         If this is a string or array, it represents the event type(s)
3042     *         that will trigger the listener.
3043     *
3044     *         Another evented object can be passed here instead, which will
3045     *         cause the listener to listen for events on _that_ object.
3046     *
3047     *         In either case, the listener's `this` value will be bound to
3048     *         this object.
3049     *
3050     * @param  {string|Array|Function} typeOrListener
3051     *         If the first argument was a string or array, this should be the
3052     *         listener function. Otherwise, this is a string or array of event
3053     *         type(s).
3054     *
3055     * @param  {Function} [listener]
3056     *         If the first argument was another evented object, this will be
3057     *         the listener function.
3058     */
3059    one(...args) {
3060      const {
3061        isTargetingSelf,
3062        target,
3063        type,
3064        listener
3065      } = normalizeListenArgs(this, args, 'one');
3066
3067      // Targeting this evented object.
3068      if (isTargetingSelf) {
3069        listen(target, 'one', type, listener);
3070
3071        // Targeting another evented object.
3072      } else {
3073        // TODO: This wrapper is incorrect! It should only
3074        //       remove the wrapper for the event type that called it.
3075        //       Instead all listeners are removed on the first trigger!
3076        //       see https://github.com/videojs/video.js/issues/5962
3077        const wrapper = (...largs) => {
3078          this.off(target, type, wrapper);
3079          listener.apply(null, largs);
3080        };
3081
3082        // Use the same function ID as the listener so we can remove it later
3083        // it using the ID of the original listener.
3084        wrapper.guid = listener.guid;
3085        listen(target, 'one', type, wrapper);
3086      }
3087    },
3088    /**
3089     * Add a listener to an event (or events) on this object or another evented
3090     * object. The listener will only be called once for the first event that is triggered
3091     * then removed.
3092     *
3093     * @param  {string|Array|Element|Object} targetOrType
3094     *         If this is a string or array, it represents the event type(s)
3095     *         that will trigger the listener.
3096     *
3097     *         Another evented object can be passed here instead, which will
3098     *         cause the listener to listen for events on _that_ object.
3099     *
3100     *         In either case, the listener's `this` value will be bound to
3101     *         this object.
3102     *
3103     * @param  {string|Array|Function} typeOrListener
3104     *         If the first argument was a string or array, this should be the
3105     *         listener function. Otherwise, this is a string or array of event
3106     *         type(s).
3107     *
3108     * @param  {Function} [listener]
3109     *         If the first argument was another evented object, this will be
3110     *         the listener function.
3111     */
3112    any(...args) {
3113      const {
3114        isTargetingSelf,
3115        target,
3116        type,
3117        listener
3118      } = normalizeListenArgs(this, args, 'any');
3119
3120      // Targeting this evented object.
3121      if (isTargetingSelf) {
3122        listen(target, 'any', type, listener);
3123
3124        // Targeting another evented object.
3125      } else {
3126        const wrapper = (...largs) => {
3127          this.off(target, type, wrapper);
3128          listener.apply(null, largs);
3129        };
3130
3131        // Use the same function ID as the listener so we can remove it later
3132        // it using the ID of the original listener.
3133        wrapper.guid = listener.guid;
3134        listen(target, 'any', type, wrapper);
3135      }
3136    },
3137    /**
3138     * Removes listener(s) from event(s) on an evented object.
3139     *
3140     * @param  {string|Array|Element|Object} [targetOrType]
3141     *         If this is a string or array, it represents the event type(s).
3142     *
3143     *         Another evented object can be passed here instead, in which case
3144     *         ALL 3 arguments are _required_.
3145     *
3146     * @param  {string|Array|Function} [typeOrListener]
3147     *         If the first argument was a string or array, this may be the
3148     *         listener function. Otherwise, this is a string or array of event
3149     *         type(s).
3150     *
3151     * @param  {Function} [listener]
3152     *         If the first argument was another evented object, this will be
3153     *         the listener function; otherwise, _all_ listeners bound to the
3154     *         event type(s) will be removed.
3155     */
3156    off(targetOrType, typeOrListener, listener) {
3157      // Targeting this evented object.
3158      if (!targetOrType || isValidEventType(targetOrType)) {
3159        off(this.eventBusEl_, targetOrType, typeOrListener);
3160
3161        // Targeting another evented object.
3162      } else {
3163        const target = targetOrType;
3164        const type = typeOrListener;
3165
3166        // Fail fast and in a meaningful way!
3167        validateTarget(target, this, 'off');
3168        validateEventType(type, this, 'off');
3169        validateListener(listener, this, 'off');
3170
3171        // Ensure there's at least a guid, even if the function hasn't been used
3172        listener = bind_(this, listener);
3173
3174        // Remove the dispose listener on this evented object, which was given
3175        // the same guid as the event listener in on().
3176        this.off('dispose', listener);
3177        if (target.nodeName) {
3178          off(target, type, listener);
3179          off(target, 'dispose', listener);
3180        } else if (isEvented(target)) {
3181          target.off(type, listener);
3182          target.off('dispose', listener);
3183        }
3184      }
3185    },
3186    /**
3187     * Fire an event on this evented object, causing its listeners to be called.
3188     *
3189     * @param   {string|Object} event
3190     *          An event type or an object with a type property.
3191     *
3192     * @param   {Object} [hash]
3193     *          An additional object to pass along to listeners.
3194     *
3195     * @return {boolean}
3196     *          Whether or not the default behavior was prevented.
3197     */
3198    trigger(event, hash) {
3199      validateTarget(this.eventBusEl_, this, 'trigger');
3200      const type = event && typeof event !== 'string' ? event.type : event;
3201      if (!isValidEventType(type)) {
3202        throw new 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.');
3203      }
3204      return trigger(this.eventBusEl_, event, hash);
3205    }
3206  };
3207
3208  /**
3209   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
3210   *
3211   * @param  {Object} target
3212   *         The object to which to add event methods.
3213   *
3214   * @param  {Object} [options={}]
3215   *         Options for customizing the mixin behavior.
3216   *
3217   * @param  {string} [options.eventBusKey]
3218   *         By default, adds a `eventBusEl_` DOM element to the target object,
3219   *         which is used as an event bus. If the target object already has a
3220   *         DOM element that should be used, pass its key here.
3221   *
3222   * @return {Object}
3223   *         The target object.
3224   */
3225  function evented(target, options = {}) {
3226    const {
3227      eventBusKey
3228    } = options;
3229
3230    // Set or create the eventBusEl_.
3231    if (eventBusKey) {
3232      if (!target[eventBusKey].nodeName) {
3233        throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
3234      }
3235      target.eventBusEl_ = target[eventBusKey];
3236    } else {
3237      target.eventBusEl_ = createEl('span', {
3238        className: 'vjs-event-bus'
3239      });
3240    }
3241    Object.assign(target, EventedMixin);
3242    if (target.eventedCallbacks) {
3243      target.eventedCallbacks.forEach(callback => {
3244        callback();
3245      });
3246    }
3247
3248    // When any evented object is disposed, it removes all its listeners.
3249    target.on('dispose', () => {
3250      target.off();
3251      [target, target.el_, target.eventBusEl_].forEach(function (val) {
3252        if (val && DomData.has(val)) {
3253          DomData.delete(val);
3254        }
3255      });
3256      window
vendor: 5,486 bytes, lines 3256-3439
3256.setTimeout(() => {
3257        target.eventBusEl_ = null;
3258      }, 0);
3259    });
3260    return target;
3261  }
3262
3263  /**
3264   * @file mixins/stateful.js
3265   * @module stateful
3266   */
3267
3268  /**
3269   * Contains methods that provide statefulness to an object which is passed
3270   * to {@link module:stateful}.
3271   *
3272   * @mixin StatefulMixin
3273   */
3274  const StatefulMixin = {
3275    /**
3276     * A hash containing arbitrary keys and values representing the state of
3277     * the object.
3278     *
3279     * @type {Object}
3280     */
3281    state: {},
3282    /**
3283     * Set the state of an object by mutating its
3284     * {@link module:stateful~StatefulMixin.state|state} object in place.
3285     *
3286     * @fires   module:stateful~StatefulMixin#statechanged
3287     * @param   {Object|Function} stateUpdates
3288     *          A new set of properties to shallow-merge into the plugin state.
3289     *          Can be a plain object or a function returning a plain object.
3290     *
3291     * @return {Object|undefined}
3292     *          An object containing changes that occurred. If no changes
3293     *          occurred, returns `undefined`.
3294     */
3295    setState(stateUpdates) {
3296      // Support providing the `stateUpdates` state as a function.
3297      if (typeof stateUpdates === 'function') {
3298        stateUpdates = stateUpdates();
3299      }
3300      let changes;
3301      each(stateUpdates, (value, key) => {
3302        // Record the change if the value is different from what's in the
3303        // current state.
3304        if (this.state[key] !== value) {
3305          changes = changes || {};
3306          changes[key] = {
3307            from: this.state[key],
3308            to: value
3309          };
3310        }
3311        this.state[key] = value;
3312      });
3313
3314      // Only trigger "statechange" if there were changes AND we have a trigger
3315      // function. This allows us to not require that the target object be an
3316      // evented object.
3317      if (changes && isEvented(this)) {
3318        /**
3319         * An event triggered on an object that is both
3320         * {@link module:stateful|stateful} and {@link module:evented|evented}
3321         * indicating that its state has changed.
3322         *
3323         * @event    module:stateful~StatefulMixin#statechanged
3324         * @type     {Object}
3325         * @property {Object} changes
3326         *           A hash containing the properties that were changed and
3327         *           the values they were changed `from` and `to`.
3328         */
3329        this.trigger({
3330          changes,
3331          type: 'statechanged'
3332        });
3333      }
3334      return changes;
3335    }
3336  };
3337
3338  /**
3339   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3340   * object.
3341   *
3342   * If the target object is {@link module:evented|evented} and has a
3343   * `handleStateChanged` method, that method will be automatically bound to the
3344   * `statechanged` event on itself.
3345   *
3346   * @param   {Object} target
3347   *          The object to be made stateful.
3348   *
3349   * @param   {Object} [defaultState]
3350   *          A default set of properties to populate the newly-stateful object's
3351   *          `state` property.
3352   *
3353   * @return {Object}
3354   *          Returns the `target`.
3355   */
3356  function stateful(target, defaultState) {
3357    Object.assign(target, StatefulMixin);
3358
3359    // This happens after the mixing-in because we need to replace the `state`
3360    // added in that step.
3361    target.state = Object.assign({}, target.state, defaultState);
3362
3363    // Auto-bind the `handleStateChanged` method of the target object if it exists.
3364    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3365      target.on('statechanged', target.handleStateChanged);
3366    }
3367    return target;
3368  }
3369
3370  /**
3371   * @file str.js
3372   * @module to-lower-case
3373   */
3374
3375  /**
3376   * Lowercase the first letter of a string.
3377   *
3378   * @param {string} string
3379   *        String to be lowercased
3380   *
3381   * @return {string}
3382   *         The string with a lowercased first letter
3383   */
3384  const toLowerCase = function (string) {
3385    if (typeof string !== 'string') {
3386      return string;
3387    }
3388    return string.replace(/./, w => w.toLowerCase());
3389  };
3390
3391  /**
3392   * Uppercase the first letter of a string.
3393   *
3394   * @param {string} string
3395   *        String to be uppercased
3396   *
3397   * @return {string}
3398   *         The string with an uppercased first letter
3399   */
3400  const toTitleCase$1 = function (string) {
3401    if (typeof string !== 'string') {
3402      return string;
3403    }
3404    return string.replace(/./, w => w.toUpperCase());
3405  };
3406
3407  /**
3408   * Compares the TitleCase versions of the two strings for equality.
3409   *
3410   * @param {string} str1
3411   *        The first string to compare
3412   *
3413   * @param {string} str2
3414   *        The second string to compare
3415   *
3416   * @return {boolean}
3417   *         Whether the TitleCase versions of the strings are equal
3418   */
3419  const titleCaseEquals = function (str1, str2) {
3420    return toTitleCase$1(str1) === toTitleCase$1(str2);
3421  };
3422
3423  var Str = /*#__PURE__*/Object.freeze({
3424    __proto__: null,
3425    toLowerCase: toLowerCase,
3426    toTitleCase: toTitleCase$1,
3427    titleCaseEquals: titleCaseEquals
3428  });
3429
3430  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
3431
3432  function unwrapExports (x) {
3433  	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3434  }
3435
3436  function createCommonjsModule(fn, module) {
3437  	return module = { exports: {} }, fn(module, module.exports), module.exports;
3438  }
3439
vendor: 4,223 bytes, lines 3440-3610
3440  var keycode = createCommonjsModule(function (module, exports) {
3441    // Source: http://jsfiddle.net/vWx8V/
3442    // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
3443
3444    /**
3445     * Conenience method returns corresponding value for given keyName or keyCode.
3446     *
3447     * @param {Mixed} keyCode {Number} or keyName {String}
3448     * @return {Mixed}
3449     * @api public
3450     */
3451
3452    function keyCode(searchInput) {
3453      // Keyboard Events
3454      if (searchInput && 'object' === typeof searchInput) {
3455        var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
3456        if (hasKeyCode) searchInput = hasKeyCode;
3457      }
3458
3459      // Numbers
3460      if ('number' === typeof searchInput) return names[searchInput];
3461
3462      // Everything else (cast to string)
3463      var search = String(searchInput);
3464
3465      // check codes
3466      var foundNamedKey = codes[search.toLowerCase()];
3467      if (foundNamedKey) return foundNamedKey;
3468
3469      // check aliases
3470      var foundNamedKey = aliases[search.toLowerCase()];
3471      if (foundNamedKey) return foundNamedKey;
3472
3473      // weird character?
3474      if (search.length === 1) return search.charCodeAt(0);
3475      return undefined;
3476    }
3477
3478    /**
3479     * Compares a keyboard event with a given keyCode or keyName.
3480     *
3481     * @param {Event} event Keyboard event that should be tested
3482     * @param {Mixed} keyCode {Number} or keyName {String}
3483     * @return {Boolean}
3484     * @api public
3485     */
3486    keyCode.isEventKey = function isEventKey(event, nameOrCode) {
3487      if (event && 'object' === typeof event) {
3488        var keyCode = event.which || event.keyCode || event.charCode;
3489        if (keyCode === null || keyCode === undefined) {
3490          return false;
3491        }
3492        if (typeof nameOrCode === 'string') {
3493          // check codes
3494          var foundNamedKey = codes[nameOrCode.toLowerCase()];
3495          if (foundNamedKey) {
3496            return foundNamedKey === keyCode;
3497          }
3498
3499          // check aliases
3500          var foundNamedKey = aliases[nameOrCode.toLowerCase()];
3501          if (foundNamedKey) {
3502            return foundNamedKey === keyCode;
3503          }
3504        } else if (typeof nameOrCode === 'number') {
3505          return nameOrCode === keyCode;
3506        }
3507        return false;
3508      }
3509    };
3510    exports = module.exports = keyCode;
3511
3512    /**
3513     * Get by name
3514     *
3515     *   exports.code['enter'] // => 13
3516     */
3517
3518    var codes = exports.code = exports.codes = {
3519      'backspace': 8,
3520      'tab': 9,
3521      'enter': 13,
3522      'shift': 16,
3523      'ctrl': 17,
3524      'alt': 18,
3525      'pause/break': 19,
3526      'caps lock': 20,
3527      'esc': 27,
3528      'space': 32,
3529      'page up': 33,
3530      'page down': 34,
3531      'end': 35,
3532      'home': 36,
3533      'left': 37,
3534      'up': 38,
3535      'right': 39,
3536      'down': 40,
3537      'insert': 45,
3538      'delete': 46,
3539      'command': 91,
3540      'left command': 91,
3541      'right command': 93,
3542      'numpad *': 106,
3543      'numpad +': 107,
3544      'numpad -': 109,
3545      'numpad .': 110,
3546      'numpad /': 111,
3547      'num lock': 144,
3548      'scroll lock': 145,
3549      'my computer': 182,
3550      'my calculator': 183,
3551      ';': 186,
3552      '=': 187,
3553      ',': 188,
3554      '-': 189,
3555      '.': 190,
3556      '/': 191,
3557      '`': 192,
3558      '[': 219,
3559      '\\': 220,
3560      ']': 221,
3561      "'": 222
3562    };
3563
3564    // Helper aliases
3565
3566    var aliases = exports.aliases = {
3567      'windows': 91,
3568      '⇧': 16,
3569      '⌥': 18,
3570      '⌃': 17,
3571      '⌘': 91,
3572      'ctl': 17,
3573      'control': 17,
3574      'option': 18,
3575      'pause': 19,
3576      'break': 19,
3577      'caps': 20,
3578      'return': 13,
3579      'escape': 27,
3580      'spc': 32,
3581      'spacebar': 32,
3582      'pgup': 33,
3583      'pgdn': 34,
3584      'ins': 45,
3585      'del': 46,
3586      'cmd': 91
3587    };
3588
3589    /*!
3590     * Programatically add the following
3591     */
3592
3593    // lower case chars
3594    for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32;
3595
3596    // numbers
3597    for (var i = 48; i < 58; i++) codes[i - 48] = i;
3598
3599    // function keys
3600    for (i = 1; i < 13; i++) codes['f' + i] = i + 111;
3601
3602    // numpad keys
3603    for (i = 0; i < 10; i++) codes['numpad ' + i] = i + 96;
3604
3605    /**
3606     * Get by code
3607     *
3608     *   exports.name[13] // => 'Enter'
3609     */
3610
vendor: 4,217 bytes, lines 3611-3732
3611    var names = exports.names = exports.title = {}; // title for backward compat
3612
3613    // Create reverse mapping
3614    for (i in codes) names[codes[i]] = i;
3615
3616    // Add aliases
3617    for (var alias in aliases) {
3618      codes[alias] = aliases[alias];
3619    }
3620  });
3621  keycode.code;
3622  keycode.codes;
3623  keycode.aliases;
3624  keycode.names;
3625  keycode.title;
3626
3627  /**
3628   * Player Component - Base class for all UI objects
3629   *
3630   * @file component.js
3631   */
3632
3633  /**
3634   * Base class for all UI Components.
3635   * Components are UI objects which represent both a javascript object and an element
3636   * in the DOM. They can be children of other components, and can have
3637   * children themselves.
3638   *
3639   * Components can also use methods from {@link EventTarget}
3640   */
3641  class Component$1 {
3642    /**
3643     * A callback that is called when a component is ready. Does not have any
3644     * parameters and any callback value will be ignored.
3645     *
3646     * @callback ReadyCallback
3647     * @this Component
3648     */
3649
3650    /**
3651     * Creates an instance of this class.
3652     *
3653     * @param { import('./player').default } player
3654     *        The `Player` that this class should be attached to.
3655     *
3656     * @param {Object} [options]
3657     *        The key/value store of component options.
3658     *
3659     * @param {Object[]} [options.children]
3660     *        An array of children objects to initialize this component with. Children objects have
3661     *        a name property that will be used if more than one component of the same type needs to be
3662     *        added.
3663     *
3664     * @param  {string} [options.className]
3665     *         A class or space separated list of classes to add the component
3666     *
3667     * @param {ReadyCallback} [ready]
3668     *        Function that gets called when the `Component` is ready.
3669     */
3670    constructor(player, options, ready) {
3671      // The component might be the player itself and we can't pass `this` to super
3672      if (!player && this.play) {
3673        this.player_ = player = this; // eslint-disable-line
3674      } else {
3675        this.player_ = player;
3676      }
3677      this.isDisposed_ = false;
3678
3679      // Hold the reference to the parent component via `addChild` method
3680      this.parentComponent_ = null;
3681
3682      // Make a copy of prototype.options_ to protect against overriding defaults
3683      this.options_ = merge$2({}, this.options_);
3684
3685      // Updated options with supplied options
3686      options = this.options_ = merge$2(this.options_, options);
3687
3688      // Get ID from options or options element if one is supplied
3689      this.id_ = options.id || options.el && options.el.id;
3690
3691      // If there was no ID from the options, generate one
3692      if (!this.id_) {
3693        // Don't require the player ID function in the case of mock players
3694        const id = player && player.id && player.id() || 'no_player';
3695        this.id_ = `${id}_component_${newGUID()}`;
3696      }
3697      this.name_ = options.name || null;
3698
3699      // Create element if one wasn't provided in options
3700      if (options.el) {
3701        this.el_ = options.el;
3702      } else if (options.createEl !== false) {
3703        this.el_ = this.createEl();
3704      }
3705      if (options.className && this.el_) {
3706        options.className.split(' ').forEach(c => this.addClass(c));
3707      }
3708
3709      // Remove the placeholder event methods. If the component is evented, the
3710      // real methods are added next
3711      ['on', 'off', 'one', 'any', 'trigger'].forEach(fn => {
3712        this[fn] = undefined;
3713      });
3714
3715      // if evented is anything except false, we want to mixin in evented
3716      if (options.evented !== false) {
3717        // Make this an evented object and use `el_`, if available, as its event bus
3718        evented(this, {
3719          eventBusKey: this.el_ ? 'el_' : null
3720        });
3721        this.handleLanguagechange = this.handleLanguagechange.bind(this);
3722        this.on(this.player_, 'languagechange', this.handleLanguagechange);
3723      }
3724      stateful(this, this.constructor.defaultState);
3725      this.children_ = [];
3726      this.childIndex_ = {};
3727      this.childNameIndex_ = {};
3728      this.setTimeoutIds_ = new Set();
3729      this.setIntervalIds_ = new Set();
3730      this.rafIds_ = new Set();
3731      this.namedRafs_ = new Map();
3732      this.clearingTimersOnDispose_ = false;
vendor: 5,328 bytes, lines 3733-3897
3733
3734      // Add any child components in options
3735      if (options.initChildren !== false) {
3736        this.initChildren();
3737      }
3738
3739      // Don't want to trigger ready here or it will go before init is actually
3740      // finished for all children that run this constructor
3741      this.ready(ready);
3742      if (options.reportTouchActivity !== false) {
3743        this.enableTouchActivity();
3744      }
3745    }
3746
3747    // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
3748    // They are replaced or removed in the constructor
3749
3750    /**
3751     * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
3752     * function that will get called when an event with a certain name gets triggered.
3753     *
3754     * @param {string|string[]} type
3755     *        An event name or an array of event names.
3756     *
3757     * @param {Function} fn
3758     *        The function to call with `EventTarget`s
3759     */
3760    on(type, fn) {}
3761
3762    /**
3763     * Removes an `event listener` for a specific event from an instance of `EventTarget`.
3764     * This makes it so that the `event listener` will no longer get called when the
3765     * named event happens.
3766     *
3767     * @param {string|string[]} type
3768     *        An event name or an array of event names.
3769     *
3770     * @param {Function} [fn]
3771     *        The function to remove. If not specified, all listeners managed by Video.js will be removed.
3772     */
3773    off(type, fn) {}
3774
3775    /**
3776     * This function will add an `event listener` that gets triggered only once. After the
3777     * first trigger it will get removed. This is like adding an `event listener`
3778     * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
3779     *
3780     * @param {string|string[]} type
3781     *        An event name or an array of event names.
3782     *
3783     * @param {Function} fn
3784     *        The function to be called once for each event name.
3785     */
3786    one(type, fn) {}
3787
3788    /**
3789     * This function will add an `event listener` that gets triggered only once and is
3790     * removed from all events. This is like adding an array of `event listener`s
3791     * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
3792     * first time it is triggered.
3793     *
3794     * @param {string|string[]} type
3795     *        An event name or an array of event names.
3796     *
3797     * @param {Function} fn
3798     *        The function to be called once for each event name.
3799     */
3800    any(type, fn) {}
3801
3802    /**
3803     * This function causes an event to happen. This will then cause any `event listeners`
3804     * that are waiting for that event, to get called. If there are no `event listeners`
3805     * for an event then nothing will happen.
3806     *
3807     * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
3808     * Trigger will also call the `on` + `uppercaseEventName` function.
3809     *
3810     * Example:
3811     * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
3812     * `onClick` if it exists.
3813     *
3814     * @param {string|Event|Object} event
3815     *        The name of the event, an `Event`, or an object with a key of type set to
3816     *        an event name.
3817     *
3818     * @param {Object} [hash]
3819     *        Optionally extra argument to pass through to an event listener
3820     */
3821    trigger(event, hash) {}
3822
3823    /**
3824     * Dispose of the `Component` and all child components.
3825     *
3826     * @fires Component#dispose
3827     *
3828     * @param {Object} options
3829     * @param {Element} options.originalEl element with which to replace player element
3830     */
3831    dispose(options = {}) {
3832      // Bail out if the component has already been disposed.
3833      if (this.isDisposed_) {
3834        return;
3835      }
3836      if (this.readyQueue_) {
3837        this.readyQueue_.length = 0;
3838      }
3839
3840      /**
3841       * Triggered when a `Component` is disposed.
3842       *
3843       * @event Component#dispose
3844       * @type {Event}
3845       *
3846       * @property {boolean} [bubbles=false]
3847       *           set to false so that the dispose event does not
3848       *           bubble up
3849       */
3850      this.trigger({
3851        type: 'dispose',
3852        bubbles: false
3853      });
3854      this.isDisposed_ = true;
3855
3856      // Dispose all children.
3857      if (this.children_) {
3858        for (let i = this.children_.length - 1; i >= 0; i--) {
3859          if (this.children_[i].dispose) {
3860            this.children_[i].dispose();
3861          }
3862        }
3863      }
3864
3865      // Delete child references
3866      this.children_ = null;
3867      this.childIndex_ = null;
3868      this.childNameIndex_ = null;
3869      this.parentComponent_ = null;
3870      if (this.el_) {
3871        // Remove element from DOM
3872        if (this.el_.parentNode) {
3873          if (options.restoreEl) {
3874            this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
3875          } else {
3876            this.el_.parentNode.removeChild(this.el_);
3877          }
3878        }
3879        this.el_ = null;
3880      }
3881
3882      // remove reference to the player after disposing of the element
3883      this.player_ = null;
3884    }
3885
3886    /**
3887     * Determine whether or not this component has been disposed.
3888     *
3889     * @return {boolean}
3890     *         If the component has been disposed, will be `true`. Otherwise, `false`.
3891     */
3892    isDisposed() {
3893      return Boolean(this.isDisposed_);
3894    }
3895
3896    /**
3897     * Return the {@link Player}
3897 that the `Component` has attached to.
3898     *
3899     * @return { import('./player').default 
vendor: 12,133 bytes, lines 3899-4260
3899}
3900     *         The player that this `Component` has attached to.
3901     */
3902    player() {
3903      return this.player_;
3904    }
3905
3906    /**
3907     * Deep merge of options objects with new options.
3908     * > Note: When both `obj` and `options` contain properties whose values are objects.
3909     *         The two properties get merged using {@link module:obj.merge}
3910     *
3911     * @param {Object} obj
3912     *        The object that contains new options.
3913     *
3914     * @return {Object}
3915     *         A new object of `this.options_` and `obj` merged together.
3916     */
3917    options(obj) {
3918      if (!obj) {
3919        return this.options_;
3920      }
3921      this.options_ = merge$2(this.options_, obj);
3922      return this.options_;
3923    }
3924
3925    /**
3926     * Get the `Component`s DOM element
3927     *
3928     * @return {Element}
3929     *         The DOM element for this `Component`.
3930     */
3931    el() {
3932      return this.el_;
3933    }
3934
3935    /**
3936     * Create the `Component`s DOM element.
3937     *
3938     * @param {string} [tagName]
3939     *        Element's DOM node type. e.g. 'div'
3940     *
3941     * @param {Object} [properties]
3942     *        An object of properties that should be set.
3943     *
3944     * @param {Object} [attributes]
3945     *        An object of attributes that should be set.
3946     *
3947     * @return {Element}
3948     *         The element that gets created.
3949     */
3950    createEl(tagName, properties, attributes) {
3951      return createEl(tagName, properties, attributes);
3952    }
3953
3954    /**
3955     * Localize a string given the string in english.
3956     *
3957     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3958     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3959     *
3960     * If a `defaultValue` is provided, it'll use that over `string`,
3961     * if a value isn't found in provided language files.
3962     * This is useful if you want to have a descriptive key for token replacement
3963     * but have a succinct localized string and not require `en.json` to be included.
3964     *
3965     * Currently, it is used for the progress bar timing.
3966     * ```js
3967     * {
3968     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3969     * }
3970     * ```
3971     * It is then used like so:
3972     * ```js
3973     * this.localize('progress bar timing: currentTime={1} duration{2}',
3974     *               [this.player_.currentTime(), this.player_.duration()],
3975     *               '{1} of {2}');
3976     * ```
3977     *
3978     * Which outputs something like: `01:23 of 24:56`.
3979     *
3980     *
3981     * @param {string} string
3982     *        The string to localize and the key to lookup in the language files.
3983     * @param {string[]} [tokens]
3984     *        If the current item has token replacements, provide the tokens here.
3985     * @param {string} [defaultValue]
3986     *        Defaults to `string`. Can be a default value to use for token replacement
3987     *        if the lookup key is needed to be separate.
3988     *
3989     * @return {string}
3990     *         The localized string or if no localization exists the english string.
3991     */
3992    localize(string, tokens, defaultValue = string) {
3993      const code = this.player_.language && this.player_.language();
3994      const languages = this.player_.languages && this.player_.languages();
3995      const language = languages && languages[code];
3996      const primaryCode = code && code.split('-')[0];
3997      const primaryLang = languages && languages[primaryCode];
3998      let localizedString = defaultValue;
3999      if (language && language[string]) {
4000        localizedString = language[string];
4001      } else if (primaryLang && primaryLang[string]) {
4002        localizedString = primaryLang[string];
4003      }
4004      if (tokens) {
4005        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
4006          const value = tokens[index - 1];
4007          let ret = value;
4008          if (typeof value === 'undefined') {
4009            ret = match;
4010          }
4011          return ret;
4012        });
4013      }
4014      return localizedString;
4015    }
4016
4017    /**
4018     * Handles language change for the player in components. Should be overridden by sub-components.
4019     *
4020     * @abstract
4021     */
4022    handleLanguagechange() {}
4023
4024    /**
4025     * Return the `Component`s DOM element. This is where children get inserted.
4026     * This will usually be the the same as the element returned in {@link Component#el}.
4027     *
4028     * @return {Element}
4029     *         The content element for this `Component`.
4030     */
4031    contentEl() {
4032      return this.contentEl_ || this.el_;
4033    }
4034
4035    /**
4036     * Get this `Component`s ID
4037     *
4038     * @return {string}
4039     *         The id of this `Component`
4040     */
4041    id() {
4042      return this.id_;
4043    }
4044
4045    /**
4046     * Get the `Component`s name. The name gets used to reference the `Component`
4047     * and is set during registration.
4048     *
4049     * @return {string}
4050     *         The name of this `Component`.
4051     */
4052    name() {
4053      return this.name_;
4054    }
4055
4056    /**
4057     * Get an array of all child components
4058     *
4059     * @return {Array}
4060     *         The children
4061     */
4062    children() {
4063      return this.children_;
4064    }
4065
4066    /**
4067     * Returns the child `Component` with the given `id`.
4068     *
4069     * @param {string} id
4070     *        The id of the child `Component` to get.
4071     *
4072     * @return {Component|undefined}
4073     *         The child `Component` with the given `id` or undefined.
4074     */
4075    getChildById(id) {
4076      return this.childIndex_[id];
4077    }
4078
4079    /**
4080     * Returns the child `Component` with the given `name`.
4081     *
4082     * @param {string} name
4083     *        The name of the child `Component` to get.
4084     *
4085     * @return {Component|undefined}
4086     *         The child `Component` with the given `name` or undefined.
4087     */
4088    getChild(name) {
4089      if (!name) {
4090        return;
4091      }
4092      return this.childNameIndex_[name];
4093    }
4094
4095    /**
4096     * Returns the descendant `Component` following the givent
4097     * descendant `names`. For instance ['foo', 'bar', 'baz'] would
4098     * try to get 'foo' on the current component, 'bar' on the 'foo'
4099     * component and 'baz' on the 'bar' component and return undefined
4100     * if any of those don't exist.
4101     *
4102     * @param {...string[]|...string} names
4103     *        The name of the child `Component` to get.
4104     *
4105     * @return {Component|undefined}
4106     *         The descendant `Component` following the given descendant
4107     *         `names` or undefined.
4108     */
4109    getDescendant(...names) {
4110      // flatten array argument into the main array
4111      names = names.reduce((acc, n) => acc.concat(n), []);
4112      let currentChild = this;
4113      for (let i = 0; i < names.length; i++) {
4114        currentChild = currentChild.getChild(names[i]);
4115        if (!currentChild || !currentChild.getChild) {
4116          return;
4117        }
4118      }
4119      return currentChild;
4120    }
4121
4122    /**
4123     * Adds an SVG icon element to another element or component.
4124     *
4125     * @param {string} iconName
4126     *        The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html'
4127     *
4128     * @param {Element} [el=this.el()]
4129     *        Element to set the title on. Defaults to the current Component's element.
4130     *
4131     * @return {Element}
4132     *        The newly created icon element.
4133     */
4134    setIcon(iconName, el = this.el()) {
4135      // TODO: In v9 of video.js, we will want to remove font icons entirely.
4136      // This means this check, as well as the others throughout the code, and
4137      // the unecessary CSS for font icons, will need to be removed.
4138      // See https://github.com/videojs/video.js/pull/8260 as to which components
4139      // need updating.
4140      if (!this.player_.options_.experimentalSvgIcons) {
4141        return;
4142      }
4143      const xmlnsURL = 'http://www.w3.org/2000/svg';
4144
4145      // The below creates an element in the format of:
4146      // <span><svg><use>....</use></svg></span>
4147      const iconContainer = createEl('span', {
4148        className: 'vjs-icon-placeholder vjs-svg-icon'
4149      }, {
4150        'aria-hidden': 'true'
4151      });
4152      const svgEl = document.createElementNS(xmlnsURL, 'svg');
4153      svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512');
4154      const useEl = document.createElementNS(xmlnsURL, 'use');
4155      svgEl.appendChild(useEl);
4156      useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`);
4157      iconContainer.appendChild(svgEl);
4158
4159      // Replace a pre-existing icon if one exists.
4160      if (this.iconIsSet_) {
4161        el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder'));
4162      } else {
4163        el.appendChild(iconContainer);
4164      }
4165      this.iconIsSet_ = true;
4166      return iconContainer;
4167    }
4168
4169    /**
4170     * Add a child `Component` inside the current `Component`.
4171     *
4172     * @param {string|Component} child
4173     *        The name or instance of a child to add.
4174     *
4175     * @param {Object} [options={}]
4176     *        The key/value store of options that will get passed to children of
4177     *        the child.
4178     *
4179     * @param {number} [index=this.children_.length]
4180     *        The index to attempt to add a child into.
4181     *
4182     *
4183     * @return {Component}
4184     *         The `Component` that gets added as a child. When using a string the
4185     *         `Component` will get created by this process.
4186     */
4187    addChild(child, options = {}, index = this.children_.length) {
4188      let component;
4189      let componentName;
4190
4191      // If child is a string, create component with options
4192      if (typeof child === 'string') {
4193        componentName = toTitleCase$1(child);
4194        const componentClassName = options.componentClass || componentName;
4195
4196        // Set name through options
4197        options.name = componentName;
4198
4199        // Create a new object & element for this controls set
4200        // If there's no .player_, this is a player
4201        const ComponentClass = Component$1.getComponent(componentClassName);
4202        if (!ComponentClass) {
4203          throw new Error(`Component ${componentClassName} does not exist`);
4204        }
4205
4206        // data stored directly on the videojs object may be
4207        // misidentified as a component to retain
4208        // backwards-compatibility with 4.x. check to make sure the
4209        // component class can be instantiated.
4210        if (typeof ComponentClass !== 'function') {
4211          return null;
4212        }
4213        component = new ComponentClass(this.player_ || this, options);
4214
4215        // child is a component instance
4216      } else {
4217        component = child;
4218      }
4219      if (component.parentComponent_) {
4220        component.parentComponent_.removeChild(component);
4221      }
4222      this.children_.splice(index, 0, component);
4223      component.parentComponent_ = this;
4224      if (typeof component.id === 'function') {
4225        this.childIndex_[component.id()] = component;
4226      }
4227
4228      // If a name wasn't used to create the component, check if we can use the
4229      // name function of the component
4230      componentName = componentName || component.name && toTitleCase$1(component.name());
4231      if (componentName) {
4232        this.childNameIndex_[componentName] = component;
4233        this.childNameIndex_[toLowerCase(componentName)] = component;
4234      }
4235
4236      // Add the UI object's element to the container div (box)
4237      // Having an element is not required
4238      if (typeof component.el === 'function' && component.el()) {
4239        // If inserting before a component, insert before that component's element
4240        let refNode = null;
4241        if (this.children_[index + 1]) {
4242          // Most children are components, but the video tech is an HTML element
4243          if (this.children_[index + 1].el_) {
4244            refNode = this.children_[index + 1].el_;
4245          } else if (isEl(this.children_[index + 1])) {
4246            refNode = this.children_[index + 1];
4247          }
4248        }
4249        this.contentEl().insertBefore(component.el(), refNode);
4250      }
4251
4252      // Return so it can stored on parent object if desired.
4253      return component;
4254    }
4255
4256    /**
4257     * Remove a child `Component` from this `Component`s list of children. Also removes
4258     * the child `Component`s element from this `Component`s element.
4259     *
4260     * @param {
vendor: 9,854 bytes, lines 4260-4561
4260Component} component
4261     *        The child `Component` to remove.
4262     */
4263    removeChild(component) {
4264      if (typeof component === 'string') {
4265        component = this.getChild(component);
4266      }
4267      if (!component || !this.children_) {
4268        return;
4269      }
4270      let childFound = false;
4271      for (let i = this.children_.length - 1; i >= 0; i--) {
4272        if (this.children_[i] === component) {
4273          childFound = true;
4274          this.children_.splice(i, 1);
4275          break;
4276        }
4277      }
4278      if (!childFound) {
4279        return;
4280      }
4281      component.parentComponent_ = null;
4282      this.childIndex_[component.id()] = null;
4283      this.childNameIndex_[toTitleCase$1(component.name())] = null;
4284      this.childNameIndex_[toLowerCase(component.name())] = null;
4285      const compEl = component.el();
4286      if (compEl && compEl.parentNode === this.contentEl()) {
4287        this.contentEl().removeChild(component.el());
4288      }
4289    }
4290
4291    /**
4292     * Add and initialize default child `Component`s based upon options.
4293     */
4294    initChildren() {
4295      const children = this.options_.children;
4296      if (children) {
4297        // `this` is `parent`
4298        const parentOptions = this.options_;
4299        const handleAdd = child => {
4300          const name = child.name;
4301          let opts = child.opts;
4302
4303          // Allow options for children to be set at the parent options
4304          // e.g. videojs(id, { controlBar: false });
4305          // instead of videojs(id, { children: { controlBar: false });
4306          if (parentOptions[name] !== undefined) {
4307            opts = parentOptions[name];
4308          }
4309
4310          // Allow for disabling default components
4311          // e.g. options['children']['posterImage'] = false
4312          if (opts === false) {
4313            return;
4314          }
4315
4316          // Allow options to be passed as a simple boolean if no configuration
4317          // is necessary.
4318          if (opts === true) {
4319            opts = {};
4320          }
4321
4322          // We also want to pass the original player options
4323          // to each component as well so they don't need to
4324          // reach back into the player for options later.
4325          opts.playerOptions = this.options_.playerOptions;
4326
4327          // Create and add the child component.
4328          // Add a direct reference to the child by name on the parent instance.
4329          // If two of the same component are used, different names should be supplied
4330          // for each
4331          const newChild = this.addChild(name, opts);
4332          if (newChild) {
4333            this[name] = newChild;
4334          }
4335        };
4336
4337        // Allow for an array of children details to passed in the options
4338        let workingChildren;
4339        const Tech = Component$1.getComponent('Tech');
4340        if (Array.isArray(children)) {
4341          workingChildren = children;
4342        } else {
4343          workingChildren = Object.keys(children);
4344        }
4345        workingChildren
4346        // children that are in this.options_ but also in workingChildren  would
4347        // give us extra children we do not want. So, we want to filter them out.
4348        .concat(Object.keys(this.options_).filter(function (child) {
4349          return !workingChildren.some(function (wchild) {
4350            if (typeof wchild === 'string') {
4351              return child === wchild;
4352            }
4353            return child === wchild.name;
4354          });
4355        })).map(child => {
4356          let name;
4357          let opts;
4358          if (typeof child === 'string') {
4359            name = child;
4360            opts = children[name] || this.options_[name] || {};
4361          } else {
4362            name = child.name;
4363            opts = child;
4364          }
4365          return {
4366            name,
4367            opts
4368          };
4369        }).filter(child => {
4370          // we have to make sure that child.name isn't in the techOrder since
4371          // techs are registered as Components but can't aren't compatible
4372          // See https://github.com/videojs/video.js/issues/2772
4373          const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
4374          return c && !Tech.isTech(c);
4375        }).forEach(handleAdd);
4376      }
4377    }
4378
4379    /**
4380     * Builds the default DOM class name. Should be overridden by sub-components.
4381     *
4382     * @return {string}
4383     *         The DOM class name for this object.
4384     *
4385     * @abstract
4386     */
4387    buildCSSClass() {
4388      // Child classes can include a function that does:
4389      // return 'CLASS NAME' + this._super();
4390      return '';
4391    }
4392
4393    /**
4394     * Bind a listener to the component's ready state.
4395     * Different from event listeners in that if the ready event has already happened
4396     * it will trigger the function immediately.
4397     *
4398     * @param {ReadyCallback} fn
4399     *        Function that gets called when the `Component` is ready.
4400     *
4401     * @return {Component}
4402     *         Returns itself; method can be chained.
4403     */
4404    ready(fn, sync = false) {
4405      if (!fn) {
4406        return;
4407      }
4408      if (!this.isReady_) {
4409        this.readyQueue_ = this.readyQueue_ || [];
4410        this.readyQueue_.push(fn);
4411        return;
4412      }
4413      if (sync) {
4414        fn.call(this);
4415      } else {
4416        // Call the function asynchronously by default for consistency
4417        this.setTimeout(fn, 1);
4418      }
4419    }
4420
4421    /**
4422     * Trigger all the ready listeners for this `Component`.
4423     *
4424     * @fires Component#ready
4425     */
4426    triggerReady() {
4427      this.isReady_ = true;
4428
4429      // Ensure ready is triggered asynchronously
4430      this.setTimeout(function () {
4431        const readyQueue = this.readyQueue_;
4432
4433        // Reset Ready Queue
4434        this.readyQueue_ = [];
4435        if (readyQueue && readyQueue.length > 0) {
4436          readyQueue.forEach(function (fn) {
4437            fn.call(this);
4438          }, this);
4439        }
4440
4441        // Allow for using event listeners also
4442        /**
4443         * Triggered when a `Component` is ready.
4444         *
4445         * @event Component#ready
4446         * @type {Event}
4447         */
4448        this.trigger('ready');
4449      }, 1);
4450    }
4451
4452    /**
4453     * Find a single DOM element matching a `selector`. This can be within the `Component`s
4454     * `contentEl()` or another custom context.
4455     *
4456     * @param {string} selector
4457     *        A valid CSS selector, which will be passed to `querySelector`.
4458     *
4459     * @param {Element|string} [context=this.contentEl()]
4460     *        A DOM element within which to query. Can also be a selector string in
4461     *        which case the first matching element will get used as context. If
4462     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4463     *        nothing it falls back to `document`.
4464     *
4465     * @return {Element|null}
4466     *         the dom element that was found, or null
4467     *
4468     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4469     */
4470    $(selector, context) {
4471      return $(selector, context || this.contentEl());
4472    }
4473
4474    /**
4475     * Finds all DOM element matching a `selector`. This can be within the `Component`s
4476     * `contentEl()` or another custom context.
4477     *
4478     * @param {string} selector
4479     *        A valid CSS selector, which will be passed to `querySelectorAll`.
4480     *
4481     * @param {Element|string} [context=this.contentEl()]
4482     *        A DOM element within which to query. Can also be a selector string in
4483     *        which case the first matching element will get used as context. If
4484     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4485     *        nothing it falls back to `document`.
4486     *
4487     * @return {NodeList}
4488     *         a list of dom elements that were found
4489     *
4490     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4491     */
4492    $$(selector, context) {
4493      return $$(selector, context || this.contentEl());
4494    }
4495
4496    /**
4497     * Check if a component's element has a CSS class name.
4498     *
4499     * @param {string} classToCheck
4500     *        CSS class name to check.
4501     *
4502     * @return {boolean}
4503     *         - True if the `Component` has the class.
4504     *         - False if the `Component` does not have the class`
4505     */
4506    hasClass(classToCheck) {
4507      return hasClass(this.el_, classToCheck);
4508    }
4509
4510    /**
4511     * Add a CSS class name to the `Component`s element.
4512     *
4513     * @param {...string} classesToAdd
4514     *        One or more CSS class name to add.
4515     */
4516    addClass(...classesToAdd) {
4517      addClass(this.el_, ...classesToAdd);
4518    }
4519
4520    /**
4521     * Remove a CSS class name from the `Component`s element.
4522     *
4523     * @param {...string} classesToRemove
4524     *        One or more CSS class name to remove.
4525     */
4526    removeClass(...classesToRemove) {
4527      removeClass(this.el_, ...classesToRemove);
4528    }
4529
4530    /**
4531     * Add or remove a CSS class name from the component's element.
4532     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4533     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4534     *
4535     * @param  {string} classToToggle
4536     *         The class to add or remove based on (@link Component#hasClass}
4537     *
4538     * @param  {boolean|Dom~predicate} [predicate]
4539     *         An {@link Dom~predicate} function or a boolean
4540     */
4541    toggleClass(classToToggle, predicate) {
4542      toggleClass(this.el_, classToToggle, predicate);
4543    }
4544
4545    /**
4546     * Show the `Component`s element if it is hidden by removing the
4547     * 'vjs-hidden' class name from it.
4548     */
4549    show() {
4550      this.removeClass('vjs-hidden');
4551    }
4552
4553    /**
4554     * Hide the `Component`s element if it is currently showing by adding the
4555     * 'vjs-hidden` class name to it.
4556     */
4557    hide() {
4558      this.addClass('vjs-hidden');
4559    }
4560
4561    /**
4562     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4563     * class name to it. Used during fadeIn/fadeOut.
4564     *
4565     * @private
4566     */
4567    lockShowing() {
4568      this.addClass('vjs-lock-showing');
4569    }
4570
4571    /**
4572     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4573     * class name from it. Used during fadeIn/fadeOut.
4574     *
4575     * @private
4576     */
4577    unlockShowing() {
4578      this.removeClass('vjs-lock-showing');
4579    }
4580
4581    /**
4582     * Get the value of an attribute on the `Component`s element.
4583     *
4584     * @param {string} attribute
4585     *        Name of the attribute to get the value from.
4586     *
4587     * @return {string|null}
4588     *         - The value of the attribute that was asked for.
4589     *         - Can be an empty string on some browsers if the attribute does not exist
4590     *           or has no value
4591     *         - Most browsers will return null if the attribute does not exist or has
4592     *           no value.
4593     *
4594     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4595     */
4596    getAttribute(attribute) {
4597      return getAttribute(this.el_, attribute);
4598    }
4599
4600    /**
4601     * Set the value of an attribute on the `Component`'s element
4602     *
4603     * @param {string} attribute
4604     *        Name of the attribute to set.
4605     *
4606     * @param {string} value
4607     *        Value to set the attribute to.
4608     *
4609     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4610     */
4611    setAttribute(attribute, value) {
4612      setAttribute(this.el_, attribute, value);
4613    }
4614
4615    /**
4616     * Remove an attribute from the `Component`s element.
4617     *
4618     * @param {string} attribute
4619     *        Name of the attribute to remove.
4620     *
4621     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4622     */
4623    removeAttribute(attribute) {
4624      removeAttribute(this.el_, attribute);
4625    }
4626
4627    /**
4628     * Get or set the width of the component based upon the CSS styles.
4629     * See {@link Component#dimension} for more detailed information.
4630     *
4631     * @param {number|string} [num]
4632     *        The width that you want to set postfixed with '%', 'px' or nothing.
4633     *
4634     * @param {boolean} [skipListeners]
4635     *        Skip the componentresize event trigger
4636     *
4637     * @return {number|undefined}
4638     *         The width when getting, zero if there is no width
4639     */
4640    width(num, skipListeners) {
4641      return this.dimension('width', num, skipListeners);
4642    }
4643
4644    /**
4645     * Get or set the height of the component based upon the CSS styles.
4646     * See {@link Component#dimension} for more detailed information.
4647     *
4648     * @param {number|string} [num]
4649     *        The height that you want to set postfixed with '%', 'px' or nothing.
4650     *
4651     * @param {boolean} [skipListeners]
4652     *        Skip the componentresize event trigger
4653     *
4654     * @return {number|undefined}
4655     *         The height when getting, zero if there is no height
4656     */
4657    height(num, skipListeners) {
4658      return this.dimension('height', num, skipListeners);
4659    }
4660
4661    /**
4662     * Set both the width and height of the `Component` element at the same time.
4663     *
4664     * @param  {number|string} width
4665     *         Width to set the `Component`s element to.
4666     *
4667     * @param  {number|string} height
4668     *         Height to set the `Component`s element to.
4669     */
4670    dimensions(width, height) {
4671      // Skip componentresize listeners on width for optimization
4672      this.width(width, true);
4673      this.height(height);
4674    }
4675
4676    /**
4677     * Get or set width or height of the `Component` element. This is the shared code
4678     * for the {@link Component#width} and {@link Component#height}.
4679     *
4680     * Things to know:
4681     * - If the width or height in an number this will return the number postfixed with 'px'.
4682     * - If the width/height is a percent this will return the percent postfixed with '%'
4683     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4684     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4685     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
vendor: 21,506 bytes, lines 4685-5272
4685sing-width/}
4686     *   for more information
4687     * - If you want the computed style of the component, use {@link Component#currentWidth}
4688     *   and {@link {Component#currentHeight}
4689     *
4690     * @fires Component#componentresize
4691     *
4692     * @param {string} widthOrHeight
4693     8        'width' or 'height'
4694     *
4695     * @param  {number|string} [num]
4696     8         New dimension
4697     *
4698     * @param  {boolean} [skipListeners]
4699     *         Skip componentresize event trigger
4700     *
4701     * @return {number|undefined}
4702     *         The dimension when getting or 0 if unset
4703     */
4704    dimension(widthOrHeight, num, skipListeners) {
4705      if (num !== undefined) {
4706        // Set to zero if null or literally NaN (NaN !== NaN)
4707        if (num === null || num !== num) {
4708          num = 0;
4709        }
4710
4711        // Check if using css width/height (% or px) and adjust
4712        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4713          this.el_.style[widthOrHeight] = num;
4714        } else if (num === 'auto') {
4715          this.el_.style[widthOrHeight] = '';
4716        } else {
4717          this.el_.style[widthOrHeight] = num + 'px';
4718        }
4719
4720        // skipListeners allows us to avoid triggering the resize event when setting both width and height
4721        if (!skipListeners) {
4722          /**
4723           * Triggered when a component is resized.
4724           *
4725           * @event Component#componentresize
4726           * @type {Event}
4727           */
4728          this.trigger('componentresize');
4729        }
4730        return;
4731      }
4732
4733      // Not setting a value, so getting it
4734      // Make sure element exists
4735      if (!this.el_) {
4736        return 0;
4737      }
4738
4739      // Get dimension value from style
4740      const val = this.el_.style[widthOrHeight];
4741      const pxIndex = val.indexOf('px');
4742      if (pxIndex !== -1) {
4743        // Return the pixel value with no 'px'
4744        return parseInt(val.slice(0, pxIndex), 10);
4745      }
4746
4747      // No px so using % or no style was set, so falling back to offsetWidth/height
4748      // If component has display:none, offset will return 0
4749      // TODO: handle display:none and no dimension style using px
4750      return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
4751    }
4752
4753    /**
4754     * Get the computed width or the height of the component's element.
4755     *
4756     * Uses `window.getComputedStyle`.
4757     *
4758     * @param {string} widthOrHeight
4759     *        A string containing 'width' or 'height'. Whichever one you want to get.
4760     *
4761     * @return {number}
4762     *         The dimension that gets asked for or 0 if nothing was set
4763     *         for that dimension.
4764     */
4765    currentDimension(widthOrHeight) {
4766      let computedWidthOrHeight = 0;
4767      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4768        throw new Error('currentDimension only accepts width or height value');
4769      }
4770      computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
4771
4772      // remove 'px' from variable and parse as integer
4773      computedWidthOrHeight = parseFloat(computedWidthOrHeight);
4774
4775      // if the computed value is still 0, it's possible that the browser is lying
4776      // and we want to check the offset values.
4777      // This code also runs wherever getComputedStyle doesn't exist.
4778      if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4779        const rule = `offset${toTitleCase$1(widthOrHeight)}`;
4780        computedWidthOrHeight = this.el_[rule];
4781      }
4782      return computedWidthOrHeight;
4783    }
4784
4785    /**
4786     * An object that contains width and height values of the `Component`s
4787     * computed style. Uses `window.getComputedStyle`.
4788     *
4789     * @typedef {Object} Component~DimensionObject
4790     *
4791     * @property {number} width
4792     *           The width of the `Component`s computed style.
4793     *
4794     * @property {number} height
4795     *           The height of the `Component`s computed style.
4796     */
4797
4798    /**
4799     * Get an object that contains computed width and height values of the
4800     * component's element.
4801     *
4802     * Uses `window.getComputedStyle`.
4803     *
4804     * @return {Component~DimensionObject}
4805     *         The computed dimensions of the component's element.
4806     */
4807    currentDimensions() {
4808      return {
4809        width: this.currentDimension('width'),
4810        height: this.currentDimension('height')
4811      };
4812    }
4813
4814    /**
4815     * Get the computed width of the component's element.
4816     *
4817     * Uses `window.getComputedStyle`.
4818     *
4819     * @return {number}
4820     *         The computed width of the component's element.
4821     */
4822    currentWidth() {
4823      return this.currentDimension('width');
4824    }
4825
4826    /**
4827     * Get the computed height of the component's element.
4828     *
4829     * Uses `window.getComputedStyle`.
4830     *
4831     * @return {number}
4832     *         The computed height of the component's element.
4833     */
4834    currentHeight() {
4835      return this.currentDimension('height');
4836    }
4837
4838    /**
4839     * Set the focus to this component
4840     */
4841    focus() {
4842      this.el_.focus();
4843    }
4844
4845    /**
4846     * Remove the focus from this component
4847     */
4848    blur() {
4849      this.el_.blur();
4850    }
4851
4852    /**
4853     * When this Component receives a `keydown` event which it does not process,
4854     *  it passes the event to the Player for handling.
4855     *
4856     * @param {KeyboardEvent} event
4857     *        The `keydown` event that caused this function to be called.
4858     */
4859    handleKeyDown(event) {
4860      if (this.player_) {
4861        // We only stop propagation here because we want unhandled events to fall
4862        // back to the browser. Exclude Tab for focus trapping.
4863        if (!keycode.isEventKey(event, 'Tab')) {
4864          event.stopPropagation();
4865        }
4866        this.player_.handleKeyDown(event);
4867      }
4868    }
4869
4870    /**
4871     * Many components used to have a `handleKeyPress` method, which was poorly
4872     * named because it listened to a `keydown` event. This method name now
4873     * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4874     * will not see their method calls stop working.
4875     *
4876     * @param {KeyboardEvent} event
4877     *        The event that caused this function to be called.
4878     */
4879    handleKeyPress(event) {
4880      this.handleKeyDown(event);
4881    }
4882
4883    /**
4884     * Emit a 'tap' events when touch event support gets detected. This gets used to
4885     * support toggling the controls through a tap on the video. They get enabled
4886     * because every sub-component would have extra overhead otherwise.
4887     *
4888     * @protected
4889     * @fires Component#tap
4890     * @listens Component#touchstart
4891     * @listens Component#touchmove
4892     * @listens Component#touchleave
4893     * @listens Component#touchcancel
4894     * @listens Component#touchend
4895      */
4896    emitTapEvents() {
4897      // Track the start time so we can determine how long the touch lasted
4898      let touchStart = 0;
4899      let firstTouch = null;
4900
4901      // Maximum movement allowed during a touch event to still be considered a tap
4902      // Other popular libs use anywhere from 2 (hammer.js) to 15,
4903      // so 10 seems like a nice, round number.
4904      const tapMovementThreshold = 10;
4905
4906      // The maximum length a touch can be while still being considered a tap
4907      const touchTimeThreshold = 200;
4908      let couldBeTap;
4909      this.on('touchstart', function (event) {
4910        // If more than one finger, don't consider treating this as a click
4911        if (event.touches.length === 1) {
4912          // Copy pageX/pageY from the object
4913          firstTouch = {
4914            pageX: event.touches[0].pageX,
4915            pageY: event.touches[0].pageY
4916          };
4917          // Record start time so we can detect a tap vs. "touch and hold"
4918          touchStart = window.performance.now();
4919          // Reset couldBeTap tracking
4920          couldBeTap = true;
4921        }
4922      });
4923      this.on('touchmove', function (event) {
4924        // If more than one finger, don't consider treating this as a click
4925        if (event.touches.length > 1) {
4926          couldBeTap = false;
4927        } else if (firstTouch) {
4928          // Some devices will throw touchmoves for all but the slightest of taps.
4929          // So, if we moved only a small distance, this could still be a tap
4930          const xdiff = event.touches[0].pageX - firstTouch.pageX;
4931          const ydiff = event.touches[0].pageY - firstTouch.pageY;
4932          const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4933          if (touchDistance > tapMovementThreshold) {
4934            couldBeTap = false;
4935          }
4936        }
4937      });
4938      const noTap = function () {
4939        couldBeTap = false;
4940      };
4941
4942      // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4943      this.on('touchleave', noTap);
4944      this.on('touchcancel', noTap);
4945
4946      // When the touch ends, measure how long it took and trigger the appropriate
4947      // event
4948      this.on('touchend', function (event) {
4949        firstTouch = null;
4950        // Proceed only if the touchmove/leave/cancel event didn't happen
4951        if (couldBeTap === true) {
4952          // Measure how long the touch lasted
4953          const touchTime = window.performance.now() - touchStart;
4954
4955          // Make sure the touch was less than the threshold to be considered a tap
4956          if (touchTime < touchTimeThreshold) {
4957            // Don't let browser turn this into a click
4958            event.preventDefault();
4959            /**
4960             * Triggered when a `Component` is tapped.
4961             *
4962             * @event Component#tap
4963             * @type {MouseEvent}
4964             */
4965            this.trigger('tap');
4966            // It may be good to copy the touchend event object and change the
4967            // type to tap, if the other event properties aren't exact after
4968            // Events.fixEvent runs (e.g. event.target)
4969          }
4970        }
4971      });
4972    }
4973
4974    /**
4975     * This function reports user activity whenever touch events happen. This can get
4976     * turned off by any sub-components that wants touch events to act another way.
4977     *
4978     * Report user touch activity when touch events occur. User activity gets used to
4979     * determine when controls should show/hide. It is simple when it comes to mouse
4980     * events, because any mouse event should show the controls. So we capture mouse
4981     * events that bubble up to the player and report activity when that happens.
4982     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4983     * controls. So touch events can't help us at the player level either.
4984     *
4985     * User activity gets checked asynchronously. So what could happen is a tap event
4986     * on the video turns the controls off. Then the `touchend` event bubbles up to
4987     * the player. Which, if it reported user activity, would turn the controls right
4988     * back on. We also don't want to completely block touch events from bubbling up.
4989     * Furthermore a `touchmove` event and anything other than a tap, should not turn
4990     * controls back on.
4991     *
4992     * @listens Component#touchstart
4993     * @listens Component#touchmove
4994     * @listens Component#touchend
4995     * @listens Component#touchcancel
4996     */
4997    enableTouchActivity() {
4998      // Don't continue if the root player doesn't support reporting user activity
4999      if (!this.player() || !this.player().reportUserActivity) {
5000        return;
5001      }
5002
5003      // listener for reporting that the user is active
5004      const report = bind_(this.player(), this.player().reportUserActivity);
5005      let touchHolding;
5006      this.on('touchstart', function () {
5007        report();
5008        // For as long as the they are touching the device or have their mouse down,
5009        // we consider them active even if they're not moving their finger or mouse.
5010        // So we want to continue to update that they are active
5011        this.clearInterval(touchHolding);
5012        // report at the same interval as activityCheck
5013        touchHolding = this.setInterval(report, 250);
5014      });
5015      const touchEnd = function (event) {
5016        report();
5017        // stop the interval that maintains activity if the touch is holding
5018        this.clearInterval(touchHolding);
5019      };
5020      this.on('touchmove', report);
5021      this.on('touchend', touchEnd);
5022      this.on('touchcancel', touchEnd);
5023    }
5024
5025    /**
5026     * A callback that has no parameters and is bound into `Component`s context.
5027     *
5028     * @callback Component~GenericCallback
5029     * @this Component
5030     */
5031
5032    /**
5033     * Creates a function that runs after an `x` millisecond timeout. This function is a
5034     * wrapper around `window.setTimeout`. There are a few reasons to use this one
5035     * instead though:
5036     * 1. It gets cleared via  {@link Component#clearTimeout} when
5037     *    {@link Component#dispose} gets called.
5038     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
5039     *
5040     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
5041     *         will cause its dispose listener not to get cleaned up! Please use
5042     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
5043     *
5044     * @param {Component~GenericCallback} fn
5045     *        The function that will be run after `timeout`.
5046     *
5047     * @param {number} timeout
5048     *        Timeout in milliseconds to delay before executing the specified function.
5049     *
5050     * @return {number}
5051     *         Returns a timeout ID that gets used to identify the timeout. It can also
5052     *         get used in {@link Component#clearTimeout} to clear the timeout that
5053     *         was set.
5054     *
5055     * @listens Component#dispose
5056     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
5057     */
5058    setTimeout(fn, timeout) {
5059      // declare as variables so they are properly available in timeout function
5060      // eslint-disable-next-line
5061      var timeoutId;
5062      fn = bind_(this, fn);
5063      this.clearTimersOnDispose_();
5064      timeoutId = window.setTimeout(() => {
5065        if (this.setTimeoutIds_.has(timeoutId)) {
5066          this.setTimeoutIds_.delete(timeoutId);
5067        }
5068        fn();
5069      }, timeout);
5070      this.setTimeoutIds_.add(timeoutId);
5071      return timeoutId;
5072    }
5073
5074    /**
5075     * Clears a timeout that gets created via `window.setTimeout` or
5076     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
5077     * use this function instead of `window.clearTimout`. If you don't your dispose
5078     * listener will not get cleaned up until {@link Component#dispose}!
5079     *
5080     * @param {number} timeoutId
5081     *        The id of the timeout to clear. The return value of
5082     *        {@link Component#setTimeout} or `window.setTimeout`.
5083     *
5084     * @return {number}
5085     *         Returns the timeout id that was cleared.
5086     *
5087     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
5088     */
5089    clearTimeout(timeoutId) {
5090      if (this.setTimeoutIds_.has(timeoutId)) {
5091        this.setTimeoutIds_.delete(timeoutId);
5092        window.clearTimeout(timeoutId);
5093      }
5094      return timeoutId;
5095    }
5096
5097    /**
5098     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
5099     * around `window.setInterval`. There are a few reasons to use this one instead though.
5100     * 1. It gets cleared via  {@link Component#clearInterval} when
5101     *    {@link Component#dispose} gets called.
5102     * 2. The function callback will be a {@link Component~GenericCallback}
5103     *
5104     * @param {Component~GenericCallback} fn
5105     *        The function to run every `x` seconds.
5106     *
5107     * @param {number} interval
5108     *        Execute the specified function every `x` milliseconds.
5109     *
5110     * @return {number}
5111     *         Returns an id that can be used to identify the interval. It can also be be used in
5112     *         {@link Component#clearInterval} to clear the interval.
5113     *
5114     * @listens Component#dispose
5115     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
5116     */
5117    setInterval(fn, interval) {
5118      fn = bind_(this, fn);
5119      this.clearTimersOnDispose_();
5120      const intervalId = window.setInterval(fn, interval);
5121      this.setIntervalIds_.add(intervalId);
5122      return intervalId;
5123    }
5124
5125    /**
5126     * Clears an interval that gets created via `window.setInterval` or
5127     * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
5128     * use this function instead of `window.clearInterval`. If you don't your dispose
5129     * listener will not get cleaned up until {@link Component#dispose}!
5130     *
5131     * @param {number} intervalId
5132     *        The id of the interval to clear. The return value of
5133     *        {@link Component#setInterval} or `window.setInterval`.
5134     *
5135     * @return {number}
5136     *         Returns the interval id that was cleared.
5137     *
5138     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
5139     */
5140    clearInterval(intervalId) {
5141      if (this.setIntervalIds_.has(intervalId)) {
5142        this.setIntervalIds_.delete(intervalId);
5143        window.clearInterval(intervalId);
5144      }
5145      return intervalId;
5146    }
5147
5148    /**
5149     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
5150     * with a few extra bonuses:
5151     *
5152     * - Supports browsers that do not support rAF by falling back to
5153     *   {@link Component#setTimeout}.
5154     *
5155     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
5156     *   bound to the component).
5157     *
5158     * - Automatic cancellation of the rAF callback is handled if the component
5159     *   is disposed before it is called.
5160     *
5161     * @param  {Component~GenericCallback} fn
5162     *         A function that will be bound to this component and executed just
5163     *         before the browser's next repaint.
5164     *
5165     * @return {number}
5166     *         Returns an rAF ID that gets used to identify the timeout. It can
5167     *         also be used in {@link Component#cancelAnimationFrame} to cancel
5168     *         the animation frame callback.
5169     *
5170     * @listens Component#dispose
5171     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
5172     */
5173    requestAnimationFrame(fn) {
5174      this.clearTimersOnDispose_();
5175
5176      // declare as variables so they are properly available in rAF function
5177      // eslint-disable-next-line
5178      var id;
5179      fn = bind_(this, fn);
5180      id = window.requestAnimationFrame(() => {
5181        if (this.rafIds_.has(id)) {
5182          this.rafIds_.delete(id);
5183        }
5184        fn();
5185      });
5186      this.rafIds_.add(id);
5187      return id;
5188    }
5189
5190    /**
5191     * Request an animation frame, but only one named animation
5192     * frame will be queued. Another will never be added until
5193     * the previous one finishes.
5194     *
5195     * @param {string} name
5196     *        The name to give this requestAnimationFrame
5197     *
5198     * @param  {Component~GenericCallback} fn
5199     *         A function that will be bound to this component and executed just
5200     *         before the browser's next repaint.
5201     */
5202    requestNamedAnimationFrame(name, fn) {
5203      if (this.namedRafs_.has(name)) {
5204        return;
5205      }
5206      this.clearTimersOnDispose_();
5207      fn = bind_(this, fn);
5208      const id = this.requestAnimationFrame(() => {
5209        fn();
5210        if (this.namedRafs_.has(name)) {
5211          this.namedRafs_.delete(name);
5212        }
5213      });
5214      this.namedRafs_.set(name, id);
5215      return name;
5216    }
5217
5218    /**
5219     * Cancels a current named animation frame if it exists.
5220     *
5221     * @param {string} name
5222     *        The name of the requestAnimationFrame to cancel.
5223     */
5224    cancelNamedAnimationFrame(name) {
5225      if (!this.namedRafs_.has(name)) {
5226        return;
5227      }
5228      this.cancelAnimationFrame(this.namedRafs_.get(name));
5229      this.namedRafs_.delete(name);
5230    }
5231
5232    /**
5233     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
5234     * (rAF).
5235     *
5236     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
5237     * use this function instead of `window.cancelAnimationFrame`. If you don't,
5238     * your dispose listener will not get cleaned up until {@link Component#dispose}!
5239     *
5240     * @param {number} id
5241     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
5242     *
5243     * @return {number}
5244     *         Returns the rAF ID that was cleared.
5245     *
5246     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
5247     */
5248    cancelAnimationFrame(id) {
5249      if (this.rafIds_.has(id)) {
5250        this.rafIds_.delete(id);
5251        window.cancelAnimationFrame(id);
5252      }
5253      return id;
5254    }
5255
5256    /**
5257     * A function to setup `requestAnimationFrame`, `setTimeout`,
5258     * and `setInterval`, clearing on dispose.
5259     *
5260     * > Previously each timer added and removed dispose listeners on it's own.
5261     * For better performance it was decided to batch them all, and use `Set`s
5262     * to track outstanding timer ids.
5263     *
5264     * @private
5265     */
5266    clearTimersOnDispose_() {
5267      if (this.clearingTimersOnDispose_) {
5268        return;
5269      }
5270      this.clearingTimersOnDispose_ = true;
5271      this.one('dispose', () => {
5272        [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
vendor: 8,709 bytes, lines 5272-5529
5272h(([idName, cancelName]) => {
5273          // for a `Set` key will actually be the value again
5274          // so forEach((val, val) =>` but for maps we want to use
5275          // the key.
5276          this[idName].forEach((val, key) => this[cancelName](key));
5277        });
5278        this.clearingTimersOnDispose_ = false;
5279      });
5280    }
5281
5282    /**
5283     * Register a `Component` with `videojs` given the name and the component.
5284     *
5285     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
5286     *         should be registered using {@link Tech.registerTech} or
5287     *         {@link videojs:videojs.registerTech}.
5288     *
5289     * > NOTE: This function can also be seen on videojs as
5290     *         {@link videojs:videojs.registerComponent}.
5291     *
5292     * @param {string} name
5293     *        The name of the `Component` to register.
5294     *
5295     * @param {Component} ComponentToRegister
5296     *        The `Component` class to register.
5297     *
5298     * @return {Component}
5299     *         The `Component` that was registered.
5300     */
5301    static registerComponent(name, ComponentToRegister) {
5302      if (typeof name !== 'string' || !name) {
5303        throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
5304      }
5305      const Tech = Component$1.getComponent('Tech');
5306
5307      // We need to make sure this check is only done if Tech has been registered.
5308      const isTech = Tech && Tech.isTech(ComponentToRegister);
5309      const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
5310      if (isTech || !isComp) {
5311        let reason;
5312        if (isTech) {
5313          reason = 'techs must be registered using Tech.registerTech()';
5314        } else {
5315          reason = 'must be a Component subclass';
5316        }
5317        throw new Error(`Illegal component, "${name}"; ${reason}.`);
5318      }
5319      name = toTitleCase$1(name);
5320      if (!Component$1.components_) {
5321        Component$1.components_ = {};
5322      }
5323      const Player = Component$1.getComponent('Player');
5324      if (name === 'Player' && Player && Player.players) {
5325        const players = Player.players;
5326        const playerNames = Object.keys(players);
5327
5328        // If we have players that were disposed, then their name will still be
5329        // in Players.players. So, we must loop through and verify that the value
5330        // for each item is not null. This allows registration of the Player component
5331        // after all players have been disposed or before any were created.
5332        if (players && playerNames.length > 0 && playerNames.map(pname => players[pname]).every(Boolean)) {
5333          throw new Error('Can not register Player component after player has been created.');
5334        }
5335      }
5336      Component$1.components_[name] = ComponentToRegister;
5337      Component$1.components_[toLowerCase(name)] = ComponentToRegister;
5338      return ComponentToRegister;
5339    }
5340
5341    /**
5342     * Get a `Component` based on the name it was registered with.
5343     *
5344     * @param {string} name
5345     *        The Name of the component to get.
5346     *
5347     * @return {typeof Component}
5348     *         The `Component` that got registered under the given name.
5349     */
5350    static getComponent(name) {
5351      if (!name || !Component$1.components_) {
5352        return;
5353      }
5354      return Component$1.components_[name];
5355    }
5356  }
5357  Component$1.registerComponent('Component', Component$1);
5358
5359  /**
5360   * @file time.js
5361   * @module time
5362   */
5363
5364  /**
5365   * Returns the time for the specified index at the start or end
5366   * of a TimeRange object.
5367   *
5368   * @typedef    {Function} TimeRangeIndex
5369   *
5370   * @param      {number} [index=0]
5371   *             The range number to return the time for.
5372   *
5373   * @return     {number}
5374   *             The time offset at the specified index.
5375   *
5376   * @deprecated The index argument must be provided.
5377   *             In the future, leaving it out will throw an error.
5378   */
5379
5380  /**
5381   * An object that contains ranges of time, which mimics {@link TimeRanges}.
5382   *
5383   * @typedef  {Object} TimeRange
5384   *
5385   * @property {number} length
5386   *           The number of time ranges represented by this object.
5387   *
5388   * @property {module:time~TimeRangeIndex} start
5389   *           Returns the time offset at which a specified time range begins.
5390   *
5391   * @property {module:time~TimeRangeIndex} end
5392   *           Returns the time offset at which a specified time range ends.
5393   *
5394   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5395   */
5396
5397  /**
5398   * Check if any of the time ranges are over the maximum index.
5399   *
5400   * @private
5401   * @param   {string} fnName
5402   *          The function name to use for logging
5403   *
5404   * @param   {number} index
5405   *          The index to check
5406   *
5407   * @param   {number} maxIndex
5408   *          The maximum possible index
5409   *
5410   * @throws  {Error} if the timeRanges provided are over the maxIndex
5411   */
5412  function rangeCheck(fnName, index, maxIndex) {
5413    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5414      throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`);
5415    }
5416  }
5417
5418  /**
5419   * Get the time for the specified index at the start or end
5420   * of a TimeRange object.
5421   *
5422   * @private
5423   * @param      {string} fnName
5424   *             The function name to use for logging
5425   *
5426   * @param      {string} valueIndex
5427   *             The property that should be used to get the time. should be
5428   *             'start' or 'end'
5429   *
5430   * @param      {Array} ranges
5431   *             An array of time ranges
5432   *
5433   * @param      {Array} [rangeIndex=0]
5434   *             The index to start the search at
5435   *
5436   * @return     {number}
5437   *             The time that offset at the specified index.
5438   *
5439   * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5440   * @throws     {Error} if rangeIndex is more than the length of ranges
5441   */
5442  function getRange(fnName, valueIndex, ranges, rangeIndex) {
5443    rangeCheck(fnName, rangeIndex, ranges.length - 1);
5444    return ranges[rangeIndex][valueIndex];
5445  }
5446
5447  /**
5448   * Create a time range object given ranges of time.
5449   *
5450   * @private
5451   * @param   {Array} [ranges]
5452   *          An array of time ranges.
5453   *
5454   * @return  {TimeRange}
5455   */
5456  function createTimeRangesObj(ranges) {
5457    let timeRangesObj;
5458    if (ranges === undefined || ranges.length === 0) {
5459      timeRangesObj = {
5460        length: 0,
5461        start() {
5462          throw new Error('This TimeRanges object is empty');
5463        },
5464        end() {
5465          throw new Error('This TimeRanges object is empty');
5466        }
5467      };
5468    } else {
5469      timeRangesObj = {
5470        length: ranges.length,
5471        start: getRange.bind(null, 'start', 0, ranges),
5472        end: getRange.bind(null, 'end', 1, ranges)
5473      };
5474    }
5475    if (window.Symbol && window.Symbol.iterator) {
5476      timeRangesObj[window.Symbol.iterator] = () => (ranges || []).values();
5477    }
5478    return timeRangesObj;
5479  }
5480
5481  /**
5482   * Create a `TimeRange` object which mimics an
5483   * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5484   *
5485   * @param {number|Array[]} start
5486   *        The start of a single range (a number) or an array of ranges (an
5487   *        array of arrays of two numbers each).
5488   *
5489   * @param {number} end
5490   *        The end of a single range. Cannot be used with the array form of
5491   *        the `start` argument.
5492   *
5493   * @return {TimeRange}
5494   */
5495  function createTimeRanges$1(start, end) {
5496    if (Array.isArray(start)) {
5497      return createTimeRangesObj(start);
5498    } else if (start === undefined || end === undefined) {
5499      return createTimeRangesObj();
5500    }
5501    return createTimeRangesObj([[start, end]]);
5502  }
5503
5504  /**
5505   * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
5506   * seconds) will force a number of leading zeros to cover the length of the
5507   * guide.
5508   *
5509   * @private
5510   * @param  {number} seconds
5511   *         Number of seconds to be turned into a string
5512   *
5513   * @param  {number} guide
5514   *         Number (in seconds) to model the string after
5515   *
5516   * @return {string}
5517   *         Time formatted as H:MM:SS or M:SS
5518   */
5519  const defaultImplementation = function (seconds, guide) {
5520    seconds = seconds < 0 ? 0 : seconds;
5521    let s = Math.floor(seconds % 60);
5522    let m = Math.floor(seconds / 60 % 60);
5523    let h = Math.floor(seconds / 3600);
5524    const gm = Math.floor(guide / 60 % 60);
5525    const gh = Math.floor(guide / 3600);
5526
5527    // handle invalid times
5528    if (isNaN(seconds) || seconds === Infinity) {
5529      // '-' is false for all relational operators (e.g. <, >
vendor: 4,311 bytes, lines 5529-5668
5529=) so this setting
5530      // will add the minimum number of fields specified by the guide
5531      h = m = s = '-';
5532    }
5533
5534    // Check if we need to show hours
5535    h = h > 0 || gh > 0 ? h + ':' : '';
5536
5537    // If hours are showing, we may need to add a leading zero.
5538    // Always show at least one digit of minutes.
5539    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
5540
5541    // Check if leading zero is need for seconds
5542    s = s < 10 ? '0' + s : s;
5543    return h + m + s;
5544  };
5545
5546  // Internal pointer to the current implementation.
5547  let implementation = defaultImplementation;
5548
5549  /**
5550   * Replaces the default formatTime implementation with a custom implementation.
5551   *
5552   * @param {Function} customImplementation
5553   *        A function which will be used in place of the default formatTime
5554   *        implementation. Will receive the current time in seconds and the
5555   *        guide (in seconds) as arguments.
5556   */
5557  function setFormatTime(customImplementation) {
5558    implementation = customImplementation;
5559  }
5560
5561  /**
5562   * Resets formatTime to the default implementation.
5563   */
5564  function resetFormatTime() {
5565    implementation = defaultImplementation;
5566  }
5567
5568  /**
5569   * Delegates to either the default time formatting function or a custom
5570   * function supplied via `setFormatTime`.
5571   *
5572   * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
5573   * guide (in seconds) will force a number of leading zeros to cover the
5574   * length of the guide.
5575   *
5576   * @example  formatTime(125, 600) === "02:05"
5577   * @param    {number} seconds
5578   *           Number of seconds to be turned into a string
5579   *
5580   * @param    {number} guide
5581   *           Number (in seconds) to model the string after
5582   *
5583   * @return   {string}
5584   *           Time formatted as H:MM:SS or M:SS
5585   */
5586  function formatTime(seconds, guide = seconds) {
5587    return implementation(seconds, guide);
5588  }
5589
5590  var Time = /*#__PURE__*/Object.freeze({
5591    __proto__: null,
5592    createTimeRanges: createTimeRanges$1,
5593    createTimeRange: createTimeRanges$1,
5594    setFormatTime: setFormatTime,
5595    resetFormatTime: resetFormatTime,
5596    formatTime: formatTime
5597  });
5598
5599  /**
5600   * @file buffer.js
5601   * @module buffer
5602   */
5603
5604  /**
5605   * Compute the percentage of the media that has been buffered.
5606   *
5607   * @param { import('./time').TimeRange } buffered
5608   *        The current `TimeRanges` object representing buffered time ranges
5609   *
5610   * @param {number} duration
5611   *        Total duration of the media
5612   *
5613   * @return {number}
5614   *         Percent buffered of the total duration in decimal form.
5615   */
5616  function bufferedPercent(buffered, duration) {
5617    let bufferedDuration = 0;
5618    let start;
5619    let end;
5620    if (!duration) {
5621      return 0;
5622    }
5623    if (!buffered || !buffered.length) {
5624      buffered = createTimeRanges$1(0, 0);
5625    }
5626    for (let i = 0; i < buffered.length; i++) {
5627      start = buffered.start(i);
5628      end = buffered.end(i);
5629
5630      // buffered end can be bigger than duration by a very small fraction
5631      if (end > duration) {
5632        end = duration;
5633      }
5634      bufferedDuration += end - start;
5635    }
5636    return bufferedDuration / duration;
5637  }
5638
5639  /**
5640   * @file media-error.js
5641   */
5642
5643  /**
5644   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5645   *
5646   * @param {number|string|Object|MediaError} value
5647   *        This can be of multiple types:
5648   *        - number: should be a standard error code
5649   *        - string: an error message (the code will be 0)
5650   *        - Object: arbitrary properties
5651   *        - `MediaError` (native): used to populate a video.js `MediaError` object
5652   *        - `MediaError` (video.js): will return itself if it's already a
5653   *          video.js `MediaError` object.
5654   *
5655   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5656   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5657   *
5658   * @class MediaError
5659   */
5660  function MediaError(value) {
5661    // Allow redundant calls to this constructor to avoid having `instanceof`
5662    // checks peppered around the code.
5663    if (value instanceof MediaError) {
5664      return value;
5665    }
5666    if (typeof value === 'number') {
5667      this.code = value;
5668    } else if (typeof value === 'string') {
5669      // default code is zero, so this is a custom error
5670      this.message = value;
5671    } else if (isObject$1(value)) {
5672      // We assign the `code` property manually because native `MediaError` objects
5673      // do not expose it as an own/enumerable property of the object.
5674      if (typeof value.code === 'number') {
5675        this.code = value.code;
5676      }
5677      Object.assign(this, value);
5678    }
5679    if (!this.message) {
5680      this.message = MediaError.defaultMessages[this.code] || '';
5681    }
5682  }
5683
5684  /**
5685   * The error code that refers two one of the defined `MediaError` types
5686   *
5687   * @type {Number}
5688   */
5689  MediaError.prototype.code = 0;
5690
5691  /**
5692   * An optional message that to show with the error. Message is not part of the HTML5
5693   * video spec but allows for more informative custom errors.
5694   *
5695   * @type {String}
5696   */
5697  MediaError.prototype.message = '';
5698
5699  /**
5700   * An optional status code that can be set by plugins to allow even more detail about
5701   * the error. For example a plugin might provide a specific HTTP status code and an
5702   * error message for that code. Then when the plugin gets that error this class will
5703   * know how to display an error message for it. This allows a custom message to show
5704   * up on the `Player` error overlay.
5705   *
5706   * @type {Array}
5707   */
5708  MediaError.prototype.status = null;
5709
5710  /**
5711   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5712   * specification listed under {@link MediaError} for more information.
5713   *
5714   * @enum {array}
5715   * @readonly
5716   * @property {string} 0 - MEDIA_ERR_CUSTOM
5717   * @property {string} 1 - MEDIA_ERR_ABORTED
5718   * @property {string} 2 - MEDIA_ERR_NETWORK
5719   * @property {string} 3 - MEDIA_ERR_DECODE
5720   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5721   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5722   */
5723  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5724
5725  /**
5726   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5727   *
5728   * @type {Array}
5729   * @constant
5730   */
5731  MediaError.defaultMessages = {
5732    1: 'You aborted the media playback',
5733    2: 'A network error caused the media download to fail part-way.',
5734    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5735    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5736    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5737  };
5738
5739  // Add types as properties on MediaError
5740  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5741  for (let errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5742    MediaError[MediaError.errorTypes[errNum]] = errNum;
5743    // values should be accessible on both the class and instance
5744    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5745  }
5746
5747  var tuple = SafeParseTuple;
5748  function SafeParseTuple(obj, reviver) {
5749    var json;
5750    var error = null;
5751    try {
5752      json = JSON.parse(obj, reviver);
5753    } catch (err) {
5754      error = err;
5755    }
5756    return [error, json];
5757  }
5758
5759  /**
5760   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5761   *
5762   * @param  {Object}  value
5763   *         An object that may or may not be `Promise`-like.
5764   *
5765   * @return {boolean}
5766   *         Whether or not the object is `Promise`-like.
5767   */
5768  function isPromise(value) {
5769    return value !== undefined && value !== null && typeof value.then === 'function';
5770  }
5771
5772  /**
5773   * Silence a Promise-like object.
5774   *
5775   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5776   * play promise" rejection error messages.
5777   *
5778   * @param  {Object} value
5779   *         An object that may or may not be `Promise`-like.
5780   */
5781  function silencePromise(value) {
5782    if (isPromise(value)) {
5783      value.then(null, e => {});
5784    }
5785  }
5786
5787  /**
5788   * @file text-track-list-converter.js Utilities for capturing text track state and
5789   * re-creating tracks based on a capture.
5790   *
5791   * @module text-track-list-converter
5792   */
5793
5794  /**
5795   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5796   * represents the {@link TextTrack}'s state.
5797   *
5798   * @param {TextTrack} track
5799   *        The text track to query.
5800   *
5801   * @return {Object}
5802   *         A serializable javascript representation of the TextTrack.
5803   * @private
5804   */
5805  const trackToJson_ = function (track) {
5806    const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => {
5807      if (track[prop]) {
5808        acc[prop] = track[prop];
5809      }
5810      return acc;
5811    }, {
vendor: 4,253 bytes, lines 5812-5932
5812      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5813        return {
5814          startTime: cue.startTime,
5815          endTime: cue.endTime,
5816          text: cue.text,
5817          id: cue.id
5818        };
5819      })
5820    });
5821    return ret;
5822  };
5823
5824  /**
5825   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5826   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5827   * {@link text-track-list-converter:jsonToTextTracks}.
5828   *
5829   * @param { import('../tech/tech').default } tech
5830   *        The tech object to query
5831   *
5832   * @return {Array}
5833   *         A serializable javascript representation of the {@link Tech}s
5834   *         {@link TextTrackList}.
5835   */
5836  const textTracksToJson = function (tech) {
5837    const trackEls = tech.$$('track');
5838    const trackObjs = Array.prototype.map.call(trackEls, t => t.track);
5839    const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5840      const json = trackToJson_(trackEl.track);
5841      if (trackEl.src) {
5842        json.src = trackEl.src;
5843      }
5844      return json;
5845    });
5846    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5847      return trackObjs.indexOf(track) === -1;
5848    }).map(trackToJson_));
5849  };
5850
5851  /**
5852   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5853   * object {@link TextTrack} representations.
5854   *
5855   * @param {Array} json
5856   *        An array of `TextTrack` representation objects, like those that would be
5857   *        produced by `textTracksToJson`.
5858   *
5859   * @param {Tech} tech
5860   *        The `Tech` to create the `TextTrack`s on.
5861   */
5862  const jsonToTextTracks = function (json, tech) {
5863    json.forEach(function (track) {
5864      const addedTrack = tech.addRemoteTextTrack(track).track;
5865      if (!track.src && track.cues) {
5866        track.cues.forEach(cue => addedTrack.addCue(cue));
5867      }
5868    });
5869    return tech.textTracks();
5870  };
5871  var textTrackConverter = {
5872    textTracksToJson,
5873    jsonToTextTracks,
5874    trackToJson_
5875  };
5876
5877  /**
5878   * @file modal-dialog.js
5879   */
5880  const MODAL_CLASS_NAME = 'vjs-modal-dialog';
5881
5882  /**
5883   * The `ModalDialog` displays over the video and its controls, which blocks
5884   * interaction with the player until it is closed.
5885   *
5886   * Modal dialogs include a "Close" button and will close when that button
5887   * is activated - or when ESC is pressed anywhere.
5888   *
5889   * @extends Component
5890   */
5891  class ModalDialog extends Component$1 {
5892    /**
5893     * Create an instance of this class.
5894     *
5895     * @param { import('./player').default } player
5896     *        The `Player` that this class should be attached to.
5897     *
5898     * @param {Object} [options]
5899     *        The key/value store of player options.
5900     *
5901     * @param { import('./utils/dom').ContentDescriptor} [options.content=undefined]
5902     *        Provide customized content for this modal.
5903     *
5904     * @param {string} [options.description]
5905     *        A text description for the modal, primarily for accessibility.
5906     *
5907     * @param {boolean} [options.fillAlways=false]
5908     *        Normally, modals are automatically filled only the first time
5909     *        they open. This tells the modal to refresh its content
5910     *        every time it opens.
5911     *
5912     * @param {string} [options.label]
5913     *        A text label for the modal, primarily for accessibility.
5914     *
5915     * @param {boolean} [options.pauseOnOpen=true]
5916     *        If `true`, playback will will be paused if playing when
5917     *        the modal opens, and resumed when it closes.
5918     *
5919     * @param {boolean} [options.temporary=true]
5920     *        If `true`, the modal can only be opened once; it will be
5921     *        disposed as soon as it's closed.
5922     *
5923     * @param {boolean} [options.uncloseable=false]
5924     *        If `true`, the user will not be able to close the modal
5925     *        through the UI in the normal ways. Programmatic closing is
5926     *        still possible.
5927     */
5928    constructor(player, options) {
5929      super(player, options);
5930      this.handleKeyDown_ = e => this.handleKeyDown(e);
5931      this.close_ = e => this.close(e);
5932      this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
vendor: 4,595 bytes, lines 5933-6094
5933      this.closeable(!this.options_.uncloseable);
5934      this.content(this.options_.content);
5935
5936      // Make sure the contentEl is defined AFTER any children are initialized
5937      // because we only want the contents of the modal in the contentEl
5938      // (not the UI elements like the close button).
5939      this.contentEl_ = createEl('div', {
5940        className: `${MODAL_CLASS_NAME}-content`
5941      }, {
5942        role: 'document'
5943      });
5944      this.descEl_ = createEl('p', {
5945        className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
5946        id: this.el().getAttribute('aria-describedby')
5947      });
5948      textContent(this.descEl_, this.description());
5949      this.el_.appendChild(this.descEl_);
5950      this.el_.appendChild(this.contentEl_);
5951    }
5952
5953    /**
5954     * Create the `ModalDialog`'s DOM element
5955     *
5956     * @return {Element}
5957     *         The DOM element that gets created.
5958     */
5959    createEl() {
5960      return super.createEl('div', {
5961        className: this.buildCSSClass(),
5962        tabIndex: -1
5963      }, {
5964        'aria-describedby': `${this.id()}_description`,
5965        'aria-hidden': 'true',
5966        'aria-label': this.label(),
5967        'role': 'dialog'
5968      });
5969    }
5970    dispose() {
5971      this.contentEl_ = null;
5972      this.descEl_ = null;
5973      this.previouslyActiveEl_ = null;
5974      super.dispose();
5975    }
5976
5977    /**
5978     * Builds the default DOM `className`.
5979     *
5980     * @return {string}
5981     *         The DOM `className` for this object.
5982     */
5983    buildCSSClass() {
5984      return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
5985    }
5986
5987    /**
5988     * Returns the label string for this modal. Primarily used for accessibility.
5989     *
5990     * @return {string}
5991     *         the localized or raw label of this modal.
5992     */
5993    label() {
5994      return this.localize(this.options_.label || 'Modal Window');
5995    }
5996
5997    /**
5998     * Returns the description string for this modal. Primarily used for
5999     * accessibility.
6000     *
6001     * @return {string}
6002     *         The localized or raw description of this modal.
6003     */
6004    description() {
6005      let desc = this.options_.description || this.localize('This is a modal window.');
6006
6007      // Append a universal closeability message if the modal is closeable.
6008      if (this.closeable()) {
6009        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
6010      }
6011      return desc;
6012    }
6013
6014    /**
6015     * Opens the modal.
6016     *
6017     * @fires ModalDialog#beforemodalopen
6018     * @fires ModalDialog#modalopen
6019     */
6020    open() {
6021      if (!this.opened_) {
6022        const player = this.player();
6023
6024        /**
6025          * Fired just before a `ModalDialog` is opened.
6026          *
6027          * @event ModalDialog#beforemodalopen
6028          * @type {Event}
6029          */
6030        this.trigger('beforemodalopen');
6031        this.opened_ = true;
6032
6033        // Fill content if the modal has never opened before and
6034        // never been filled.
6035        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
6036          this.fill();
6037        }
6038
6039        // If the player was playing, pause it and take note of its previously
6040        // playing state.
6041        this.wasPlaying_ = !player.paused();
6042        if (this.options_.pauseOnOpen && this.wasPlaying_) {
6043          player.pause();
6044        }
6045        this.on('keydown', this.handleKeyDown_);
6046
6047        // Hide controls and note if they were enabled.
6048        this.hadControls_ = player.controls();
6049        player.controls(false);
6050        this.show();
6051        this.conditionalFocus_();
6052        this.el().setAttribute('aria-hidden', 'false');
6053
6054        /**
6055          * Fired just after a `ModalDialog` is opened.
6056          *
6057          * @event ModalDialog#modalopen
6058          * @type {Event}
6059          */
6060        this.trigger('modalopen');
6061        this.hasBeenOpened_ = true;
6062      }
6063    }
6064
6065    /**
6066     * If the `ModalDialog` is currently open or closed.
6067     *
6068     * @param  {boolean} [value]
6069     *         If given, it will open (`true`) or close (`false`) the modal.
6070     *
6071     * @return {boolean}
6072     *         the current open state of the modaldialog
6073     */
6074    opened(value) {
6075      if (typeof value === 'boolean') {
6076        this[value ? 'open' : 'close']();
6077      }
6078      return this.opened_;
6079    }
6080
6081    /**
6082     * Closes the modal, does nothing if the `ModalDialog` is
6083     * not open.
6084     *
6085     * @fires ModalDialog#beforemodalclose
6086     * @fires ModalDialog#modalclose
6087     */
6088    close() {
6089      if (!this.opened_) {
6090        return;
6091      }
6092      const player = this.player();
6093
6094      /**
vendor: 30,943 bytes, lines 6095-7231
6095        * Fired just before a `ModalDialog` is closed.
6096        *
6097        * @event ModalDialog#beforemodalclose
6098        * @type {Event}
6099        */
6100      this.trigger('beforemodalclose');
6101      this.opened_ = false;
6102      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6103        player.play();
6104      }
6105      this.off('keydown', this.handleKeyDown_);
6106      if (this.hadControls_) {
6107        player.controls(true);
6108      }
6109      this.hide();
6110      this.el().setAttribute('aria-hidden', 'true');
6111
6112      /**
6113        * Fired just after a `ModalDialog` is closed.
6114        *
6115        * @event ModalDialog#modalclose
6116        * @type {Event}
6117        */
6118      this.trigger('modalclose');
6119      this.conditionalBlur_();
6120      if (this.options_.temporary) {
6121        this.dispose();
6122      }
6123    }
6124
6125    /**
6126     * Check to see if the `ModalDialog` is closeable via the UI.
6127     *
6128     * @param  {boolean} [value]
6129     *         If given as a boolean, it will set the `closeable` option.
6130     *
6131     * @return {boolean}
6132     *         Returns the final value of the closable option.
6133     */
6134    closeable(value) {
6135      if (typeof value === 'boolean') {
6136        const closeable = this.closeable_ = !!value;
6137        let close = this.getChild('closeButton');
6138
6139        // If this is being made closeable and has no close button, add one.
6140        if (closeable && !close) {
6141          // The close button should be a child of the modal - not its
6142          // content element, so temporarily change the content element.
6143          const temp = this.contentEl_;
6144          this.contentEl_ = this.el_;
6145          close = this.addChild('closeButton', {
6146            controlText: 'Close Modal Dialog'
6147          });
6148          this.contentEl_ = temp;
6149          this.on(close, 'close', this.close_);
6150        }
6151
6152        // If this is being made uncloseable and has a close button, remove it.
6153        if (!closeable && close) {
6154          this.off(close, 'close', this.close_);
6155          this.removeChild(close);
6156          close.dispose();
6157        }
6158      }
6159      return this.closeable_;
6160    }
6161
6162    /**
6163     * Fill the modal's content element with the modal's "content" option.
6164     * The content element will be emptied before this change takes place.
6165     */
6166    fill() {
6167      this.fillWith(this.content());
6168    }
6169
6170    /**
6171     * Fill the modal's content element with arbitrary content.
6172     * The content element will be emptied before this change takes place.
6173     *
6174     * @fires ModalDialog#beforemodalfill
6175     * @fires ModalDialog#modalfill
6176     *
6177     * @param { import('./utils/dom').ContentDescriptor} [content]
6178     *        The same rules apply to this as apply to the `content` option.
6179     */
6180    fillWith(content) {
6181      const contentEl = this.contentEl();
6182      const parentEl = contentEl.parentNode;
6183      const nextSiblingEl = contentEl.nextSibling;
6184
6185      /**
6186        * Fired just before a `ModalDialog` is filled with content.
6187        *
6188        * @event ModalDialog#beforemodalfill
6189        * @type {Event}
6190        */
6191      this.trigger('beforemodalfill');
6192      this.hasBeenFilled_ = true;
6193
6194      // Detach the content element from the DOM before performing
6195      // manipulation to avoid modifying the live DOM multiple times.
6196      parentEl.removeChild(contentEl);
6197      this.empty();
6198      insertContent(contentEl, content);
6199      /**
6200       * Fired just after a `ModalDialog` is filled with content.
6201       *
6202       * @event ModalDialog#modalfill
6203       * @type {Event}
6204       */
6205      this.trigger('modalfill');
6206
6207      // Re-inject the re-filled content element.
6208      if (nextSiblingEl) {
6209        parentEl.insertBefore(contentEl, nextSiblingEl);
6210      } else {
6211        parentEl.appendChild(contentEl);
6212      }
6213
6214      // make sure that the close button is last in the dialog DOM
6215      const closeButton = this.getChild('closeButton');
6216      if (closeButton) {
6217        parentEl.appendChild(closeButton.el_);
6218      }
6219    }
6220
6221    /**
6222     * Empties the content element. This happens anytime the modal is filled.
6223     *
6224     * @fires ModalDialog#beforemodalempty
6225     * @fires ModalDialog#modalempty
6226     */
6227    empty() {
6228      /**
6229      * Fired just before a `ModalDialog` is emptied.
6230      *
6231      * @event ModalDialog#beforemodalempty
6232      * @type {Event}
6233      */
6234      this.trigger('beforemodalempty');
6235      emptyEl(this.contentEl());
6236
6237      /**
6238      * Fired just after a `ModalDialog` is emptied.
6239      *
6240      * @event ModalDialog#modalempty
6241      * @type {Event}
6242      */
6243      this.trigger('modalempty');
6244    }
6245
6246    /**
6247     * Gets or sets the modal content, which gets normalized before being
6248     * rendered into the DOM.
6249     *
6250     * This does not update the DOM or fill the modal, but it is called during
6251     * that process.
6252     *
6253     * @param  { import('./utils/dom').ContentDescriptor} [value]
6254     *         If defined, sets the internal content value to be used on the
6255     *         next call(s) to `fill`. This value is normalized before being
6256     *         inserted. To "clear" the internal content value, pass `null`.
6257     *
6258     * @return { import('./utils/dom').ContentDescriptor}
6259     *         The current content of the modal dialog
6260     */
6261    content(value) {
6262      if (typeof value !== 'undefined') {
6263        this.content_ = value;
6264      }
6265      return this.content_;
6266    }
6267
6268    /**
6269     * conditionally focus the modal dialog if focus was previously on the player.
6270     *
6271     * @private
6272     */
6273    conditionalFocus_() {
6274      const activeEl = document.activeElement;
6275      const playerEl = this.player_.el_;
6276      this.previouslyActiveEl_ = null;
6277      if (playerEl.contains(activeEl) || playerEl === activeEl) {
6278        this.previouslyActiveEl_ = activeEl;
6279        this.focus();
6280      }
6281    }
6282
6283    /**
6284     * conditionally blur the element and refocus the last focused element
6285     *
6286     * @private
6287     */
6288    conditionalBlur_() {
6289      if (this.previouslyActiveEl_) {
6290        this.previouslyActiveEl_.focus();
6291        this.previouslyActiveEl_ = null;
6292      }
6293    }
6294
6295    /**
6296     * Keydown handler. Attached when modal is focused.
6297     *
6298     * @listens keydown
6299     */
6300    handleKeyDown(event) {
6301      // Do not allow keydowns to reach out of the modal dialog.
6302      event.stopPropagation();
6303      if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6304        event.preventDefault();
6305        this.close();
6306        return;
6307      }
6308
6309      // exit early if it isn't a tab key
6310      if (!keycode.isEventKey(event, 'Tab')) {
6311        return;
6312      }
6313      const focusableEls = this.focusableEls_();
6314      const activeEl = this.el_.querySelector(':focus');
6315      let focusIndex;
6316      for (let i = 0; i < focusableEls.length; i++) {
6317        if (activeEl === focusableEls[i]) {
6318          focusIndex = i;
6319          break;
6320        }
6321      }
6322      if (document.activeElement === this.el_) {
6323        focusIndex = 0;
6324      }
6325      if (event.shiftKey && focusIndex === 0) {
6326        focusableEls[focusableEls.length - 1].focus();
6327        event.preventDefault();
6328      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6329        focusableEls[0].focus();
6330        event.preventDefault();
6331      }
6332    }
6333
6334    /**
6335     * get all focusable elements
6336     *
6337     * @private
6338     */
6339    focusableEls_() {
6340      const allChildren = this.el_.querySelectorAll('*');
6341      return Array.prototype.filter.call(allChildren, child => {
6342        return (child instanceof window.HTMLAnchorElement || child instanceof window.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window.HTMLInputElement || child instanceof window.HTMLSelectElement || child instanceof window.HTMLTextAreaElement || child instanceof window.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window.HTMLIFrameElement || child instanceof window.HTMLObjectElement || child instanceof window.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
6343      });
6344    }
6345  }
6346
6347  /**
6348   * Default options for `ModalDialog` default options.
6349   *
6350   * @type {Object}
6351   * @private
6352   */
6353  ModalDialog.prototype.options_ = {
6354    pauseOnOpen: true,
6355    temporary: true
6356  };
6357  Component$1.registerComponent('ModalDialog', ModalDialog);
6358
6359  /**
6360   * @file track-list.js
6361   */
6362
6363  /**
6364   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6365   * {@link VideoTrackList}
6366   *
6367   * @extends EventTarget
6368   */
6369  class TrackList extends EventTarget$2 {
6370    /**
6371     * Create an instance of this class
6372     *
6373     * @param { import('./track').default[] } tracks
6374     *        A list of tracks to initialize the list with.
6375     *
6376     * @abstract
6377     */
6378    constructor(tracks = []) {
6379      super();
6380      this.tracks_ = [];
6381
6382      /**
6383       * @memberof TrackList
6384       * @member {number} length
6385       *         The current number of `Track`s in the this Trackist.
6386       * @instance
6387       */
6388      Object.defineProperty(this, 'length', {
6389        get() {
6390          return this.tracks_.length;
6391        }
6392      });
6393      for (let i = 0; i < tracks.length; i++) {
6394        this.addTrack(tracks[i]);
6395      }
6396    }
6397
6398    /**
6399     * Add a {@link Track} to the `TrackList`
6400     *
6401     * @param { import('./track').default } track
6402     *        The audio, video, or text track to add to the list.
6403     *
6404     * @fires TrackList#addtrack
6405     */
6406    addTrack(track) {
6407      const index = this.tracks_.length;
6408      if (!('' + index in this)) {
6409        Object.defineProperty(this, index, {
6410          get() {
6411            return this.tracks_[index];
6412          }
6413        });
6414      }
6415
6416      // Do not add duplicate tracks
6417      if (this.tracks_.indexOf(track) === -1) {
6418        this.tracks_.push(track);
6419        /**
6420         * Triggered when a track is added to a track list.
6421         *
6422         * @event TrackList#addtrack
6423         * @type {Event}
6424         * @property {Track} track
6425         *           A reference to track that was added.
6426         */
6427        this.trigger({
6428          track,
6429          type: 'addtrack',
6430          target: this
6431        });
6432      }
6433
6434      /**
6435       * Triggered when a track label is changed.
6436       *
6437       * @event TrackList#addtrack
6438       * @type {Event}
6439       * @property {Track} track
6440       *           A reference to track that was added.
6441       */
6442      track.labelchange_ = () => {
6443        this.trigger({
6444          track,
6445          type: 'labelchange',
6446          target: this
6447        });
6448      };
6449      if (isEvented(track)) {
6450        track.addEventListener('labelchange', track.labelchange_);
6451      }
6452    }
6453
6454    /**
6455     * Remove a {@link Track} from the `TrackList`
6456     *
6457     * @param { import('./track').default } rtrack
6458     *        The audio, video, or text track to remove from the list.
6459     *
6460     * @fires TrackList#removetrack
6461     */
6462    removeTrack(rtrack) {
6463      let track;
6464      for (let i = 0, l = this.length; i < l; i++) {
6465        if (this[i] === rtrack) {
6466          track = this[i];
6467          if (track.off) {
6468            track.off();
6469          }
6470          this.tracks_.splice(i, 1);
6471          break;
6472        }
6473      }
6474      if (!track) {
6475        return;
6476      }
6477
6478      /**
6479       * Triggered when a track is removed from track list.
6480       *
6481       * @event TrackList#removetrack
6482       * @type {Event}
6483       * @property {Track} track
6484       *           A reference to track that was removed.
6485       */
6486      this.trigger({
6487        track,
6488        type: 'removetrack',
6489        target: this
6490      });
6491    }
6492
6493    /**
6494     * Get a Track from the TrackList by a tracks id
6495     *
6496     * @param {string} id - the id of the track to get
6497     * @method getTrackById
6498     * @return { import('./track').default }
6499     * @private
6500     */
6501    getTrackById(id) {
6502      let result = null;
6503      for (let i = 0, l = this.length; i < l; i++) {
6504        const track = this[i];
6505        if (track.id === id) {
6506          result = track;
6507          break;
6508        }
6509      }
6510      return result;
6511    }
6512  }
6513
6514  /**
6515   * Triggered when a different track is selected/enabled.
6516   *
6517   * @event TrackList#change
6518   * @type {Event}
6519   */
6520
6521  /**
6522   * Events that can be called with on + eventName. See {@link EventHandler}.
6523   *
6524   * @property {Object} TrackList#allowedEvents_
6525   * @protected
6526   */
6527  TrackList.prototype.allowedEvents_ = {
6528    change: 'change',
6529    addtrack: 'addtrack',
6530    removetrack: 'removetrack',
6531    labelchange: 'labelchange'
6532  };
6533
6534  // emulate attribute EventHandler support to allow for feature detection
6535  for (const event in TrackList.prototype.allowedEvents_) {
6536    TrackList.prototype['on' + event] = null;
6537  }
6538
6539  /**
6540   * @file audio-track-list.js
6541   */
6542
6543  /**
6544   * Anywhere we call this function we diverge from the spec
6545   * as we only support one enabled audiotrack at a time
6546   *
6547   * @param {AudioTrackList} list
6548   *        list to work on
6549   *
6550   * @param { import('./audio-track').default } track
6551   *        The track to skip
6552   *
6553   * @private
6554   */
6555  const disableOthers$1 = function (list, track) {
6556    for (let i = 0; i < list.length; i++) {
6557      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6558        continue;
6559      }
6560      // another audio track is enabled, disable it
6561      list[i].enabled = false;
6562    }
6563  };
6564
6565  /**
6566   * The current list of {@link AudioTrack} for a media file.
6567   *
6568   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6569   * @extends TrackList
6570   */
6571  class AudioTrackList extends TrackList {
6572    /**
6573     * Create an instance of this class.
6574     *
6575     * @param { import('./audio-track').default[] } [tracks=[]]
6576     *        A list of `AudioTrack` to instantiate the list with.
6577     */
6578    constructor(tracks = []) {
6579      // make sure only 1 track is enabled
6580      // sorted from last index to first index
6581      for (let i = tracks.length - 1; i >= 0; i--) {
6582        if (tracks[i].enabled) {
6583          disableOthers$1(tracks, tracks[i]);
6584          break;
6585        }
6586      }
6587      super(tracks);
6588      this.changing_ = false;
6589    }
6590
6591    /**
6592     * Add an {@link AudioTrack} to the `AudioTrackList`.
6593     *
6594     * @param { import('./audio-track').default } track
6595     *        The AudioTrack to add to the list
6596     *
6597     * @fires TrackList#addtrack
6598     */
6599    addTrack(track) {
6600      if (track.enabled) {
6601        disableOthers$1(this, track);
6602      }
6603      super.addTrack(track);
6604      // native tracks don't have this
6605      if (!track.addEventListener) {
6606        return;
6607      }
6608      track.enabledChange_ = () => {
6609        // when we are disabling other tracks (since we don't support
6610        // more than one track at a time) we will set changing_
6611        // to true so that we don't trigger additional change events
6612        if (this.changing_) {
6613          return;
6614        }
6615        this.changing_ = true;
6616        disableOthers$1(this, track);
6617        this.changing_ = false;
6618        this.trigger('change');
6619      };
6620
6621      /**
6622       * @listens AudioTrack#enabledchange
6623       * @fires TrackList#change
6624       */
6625      track.addEventListener('enabledchange', track.enabledChange_);
6626    }
6627    removeTrack(rtrack) {
6628      super.removeTrack(rtrack);
6629      if (rtrack.removeEventListener && rtrack.enabledChange_) {
6630        rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
6631        rtrack.enabledChange_ = null;
6632      }
6633    }
6634  }
6635
6636  /**
6637   * @file video-track-list.js
6638   */
6639
6640  /**
6641   * Un-select all other {@link VideoTrack}s that are selected.
6642   *
6643   * @param {VideoTrackList} list
6644   *        list to work on
6645   *
6646   * @param { import('./video-track').default } track
6647   *        The track to skip
6648   *
6649   * @private
6650   */
6651  const disableOthers = function (list, track) {
6652    for (let i = 0; i < list.length; i++) {
6653      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6654        continue;
6655      }
6656      // another video track is enabled, disable it
6657      list[i].selected = false;
6658    }
6659  };
6660
6661  /**
6662   * The current list of {@link VideoTrack} for a video.
6663   *
6664   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6665   * @extends TrackList
6666   */
6667  class VideoTrackList extends TrackList {
6668    /**
6669     * Create an instance of this class.
6670     *
6671     * @param {VideoTrack[]} [tracks=[]]
6672     *        A list of `VideoTrack` to instantiate the list with.
6673     */
6674    constructor(tracks = []) {
6675      // make sure only 1 track is enabled
6676      // sorted from last index to first index
6677      for (let i = tracks.length - 1; i >= 0; i--) {
6678        if (tracks[i].selected) {
6679          disableOthers(tracks, tracks[i]);
6680          break;
6681        }
6682      }
6683      super(tracks);
6684      this.changing_ = false;
6685
6686      /**
6687       * @member {number} VideoTrackList#selectedIndex
6688       *         The current index of the selected {@link VideoTrack`}.
6689       */
6690      Object.defineProperty(this, 'selectedIndex', {
6691        get() {
6692          for (let i = 0; i < this.length; i++) {
6693            if (this[i].selected) {
6694              return i;
6695            }
6696          }
6697          return -1;
6698        },
6699        set() {}
6700      });
6701    }
6702
6703    /**
6704     * Add a {@link VideoTrack} to the `VideoTrackList`.
6705     *
6706     * @param { import('./video-track').default } track
6707     *        The VideoTrack to add to the list
6708     *
6709     * @fires TrackList#addtrack
6710     */
6711    addTrack(track) {
6712      if (track.selected) {
6713        disableOthers(this, track);
6714      }
6715      super.addTrack(track);
6716      // native tracks don't have this
6717      if (!track.addEventListener) {
6718        return;
6719      }
6720      track.selectedChange_ = () => {
6721        if (this.changing_) {
6722          return;
6723        }
6724        this.changing_ = true;
6725        disableOthers(this, track);
6726        this.changing_ = false;
6727        this.trigger('change');
6728      };
6729
6730      /**
6731       * @listens VideoTrack#selectedchange
6732       * @fires TrackList#change
6733       */
6734      track.addEventListener('selectedchange', track.selectedChange_);
6735    }
6736    removeTrack(rtrack) {
6737      super.removeTrack(rtrack);
6738      if (rtrack.removeEventListener && rtrack.selectedChange_) {
6739        rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
6740        rtrack.selectedChange_ = null;
6741      }
6742    }
6743  }
6744
6745  /**
6746   * @file text-track-list.js
6747   */
6748
6749  /**
6750   * The current list of {@link TextTrack} for a media file.
6751   *
6752   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6753   * @extends TrackList
6754   */
6755  class TextTrackList extends TrackList {
6756    /**
6757     * Add a {@link TextTrack} to the `TextTrackList`
6758     *
6759     * @param { import('./text-track').default } track
6760     *        The text track to add to the list.
6761     *
6762     * @fires TrackList#addtrack
6763     */
6764    addTrack(track) {
6765      super.addTrack(track);
6766      if (!this.queueChange_) {
6767        this.queueChange_ = () => this.queueTrigger('change');
6768      }
6769      if (!this.triggerSelectedlanguagechange) {
6770        this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
6771      }
6772
6773      /**
6774       * @listens TextTrack#modechange
6775       * @fires TrackList#change
6776       */
6777      track.addEventListener('modechange', this.queueChange_);
6778      const nonLanguageTextTrackKind = ['metadata', 'chapters'];
6779      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6780        track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
6781      }
6782    }
6783    removeTrack(rtrack) {
6784      super.removeTrack(rtrack);
6785
6786      // manually remove the event handlers we added
6787      if (rtrack.removeEventListener) {
6788        if (this.queueChange_) {
6789          rtrack.removeEventListener('modechange', this.queueChange_);
6790        }
6791        if (this.selectedlanguagechange_) {
6792          rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
6793        }
6794      }
6795    }
6796  }
6797
6798  /**
6799   * @file html-track-element-list.js
6800   */
6801
6802  /**
6803   * The current list of {@link HtmlTrackElement}s.
6804   */
6805  class HtmlTrackElementList {
6806    /**
6807     * Create an instance of this class.
6808     *
6809     * @param {HtmlTrackElement[]} [tracks=[]]
6810     *        A list of `HtmlTrackElement` to instantiate the list with.
6811     */
6812    constructor(trackElements = []) {
6813      this.trackElements_ = [];
6814
6815      /**
6816       * @memberof HtmlTrackElementList
6817       * @member {number} length
6818       *         The current number of `Track`s in the this Trackist.
6819       * @instance
6820       */
6821      Object.defineProperty(this, 'length', {
6822        get() {
6823          return this.trackElements_.length;
6824        }
6825      });
6826      for (let i = 0, length = trackElements.length; i < length; i++) {
6827        this.addTrackElement_(trackElements[i]);
6828      }
6829    }
6830
6831    /**
6832     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6833     *
6834     * @param {HtmlTrackElement} trackElement
6835     *        The track element to add to the list.
6836     *
6837     * @private
6838     */
6839    addTrackElement_(trackElement) {
6840      const index = this.trackElements_.length;
6841      if (!('' + index in this)) {
6842        Object.defineProperty(this, index, {
6843          get() {
6844            return this.trackElements_[index];
6845          }
6846        });
6847      }
6848
6849      // Do not add duplicate elements
6850      if (this.trackElements_.indexOf(trackElement) === -1) {
6851        this.trackElements_.push(trackElement);
6852      }
6853    }
6854
6855    /**
6856     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6857     * {@link TextTrack}.
6858     *
6859     * @param {TextTrack} track
6860     *        The track associated with a track element.
6861     *
6862     * @return {HtmlTrackElement|undefined}
6863     *         The track element that was found or undefined.
6864     *
6865     * @private
6866     */
6867    getTrackElementByTrack_(track) {
6868      let trackElement_;
6869      for (let i = 0, length = this.trackElements_.length; i < length; i++) {
6870        if (track === this.trackElements_[i].track) {
6871          trackElement_ = this.trackElements_[i];
6872          break;
6873        }
6874      }
6875      return trackElement_;
6876    }
6877
6878    /**
6879     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6880     *
6881     * @param {HtmlTrackElement} trackElement
6882     *        The track element to remove from the list.
6883     *
6884     * @private
6885     */
6886    removeTrackElement_(trackElement) {
6887      for (let i = 0, length = this.trackElements_.length; i < length; i++) {
6888        if (trackElement === this.trackElements_[i]) {
6889          if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
6890            this.trackElements_[i].track.off();
6891          }
6892          if (typeof this.trackElements_[i].off === 'function') {
6893            this.trackElements_[i].off();
6894          }
6895          this.trackElements_.splice(i, 1);
6896          break;
6897        }
6898      }
6899    }
6900  }
6901
6902  /**
6903   * @file text-track-cue-list.js
6904   */
6905
6906  /**
6907   * @typedef {Object} TextTrackCueList~TextTrackCue
6908   *
6909   * @property {string} id
6910   *           The unique id for this text track cue
6911   *
6912   * @property {number} startTime
6913   *           The start time for this text track cue
6914   *
6915   * @property {number} endTime
6916   *           The end time for this text track cue
6917   *
6918   * @property {boolean} pauseOnExit
6919   *           Pause when the end time is reached if true.
6920   *
6921   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6922   */
6923
6924  /**
6925   * A List of TextTrackCues.
6926   *
6927   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6928   */
6929  class TextTrackCueList {
6930    /**
6931     * Create an instance of this class..
6932     *
6933     * @param {Array} cues
6934     *        A list of cues to be initialized with
6935     */
6936    constructor(cues) {
6937      TextTrackCueList.prototype.setCues_.call(this, cues);
6938
6939      /**
6940       * @memberof TextTrackCueList
6941       * @member {number} length
6942       *         The current number of `TextTrackCue`s in the TextTrackCueList.
6943       * @instance
6944       */
6945      Object.defineProperty(this, 'length', {
6946        get() {
6947          return this.length_;
6948        }
6949      });
6950    }
6951
6952    /**
6953     * A setter for cues in this list. Creates getters
6954     * an an index for the cues.
6955     *
6956     * @param {Array} cues
6957     *        An array of cues to set
6958     *
6959     * @private
6960     */
6961    setCues_(cues) {
6962      const oldLength = this.length || 0;
6963      let i = 0;
6964      const l = cues.length;
6965      this.cues_ = cues;
6966      this.length_ = cues.length;
6967      const defineProp = function (index) {
6968        if (!('' + index in this)) {
6969          Object.defineProperty(this, '' + index, {
6970            get() {
6971              return this.cues_[index];
6972            }
6973          });
6974        }
6975      };
6976      if (oldLength < l) {
6977        i = oldLength;
6978        for (; i < l; i++) {
6979          defineProp.call(this, i);
6980        }
6981      }
6982    }
6983
6984    /**
6985     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6986     *
6987     * @param {string} id
6988     *        The id of the cue that should be searched for.
6989     *
6990     * @return {TextTrackCueList~TextTrackCue|null}
6991     *         A single cue or null if none was found.
6992     */
6993    getCueById(id) {
6994      let result = null;
6995      for (let i = 0, l = this.length; i < l; i++) {
6996        const cue = this[i];
6997        if (cue.id === id) {
6998          result = cue;
6999          break;
7000        }
7001      }
7002      return result;
7003    }
7004  }
7005
7006  /**
7007   * @file track-kinds.js
7008   */
7009
7010  /**
7011   * All possible `VideoTrackKind`s
7012   *
7013   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7014   * @typedef VideoTrack~Kind
7015   * @enum
7016   */
7017  const VideoTrackKind = {
7018    alternative: 'alternative',
7019    captions: 'captions',
7020    main: 'main',
7021    sign: 'sign',
7022    subtitles: 'subtitles',
7023    commentary: 'commentary'
7024  };
7025
7026  /**
7027   * All possible `AudioTrackKind`s
7028   *
7029   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7030   * @typedef AudioTrack~Kind
7031   * @enum
7032   */
7033  const AudioTrackKind = {
7034    'alternative': 'alternative',
7035    'descriptions': 'descriptions',
7036    'main': 'main',
7037    'main-desc': 'main-desc',
7038    'translation': 'translation',
7039    'commentary': 'commentary'
7040  };
7041
7042  /**
7043   * All possible `TextTrackKind`s
7044   *
7045   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7046   * @typedef TextTrack~Kind
7047   * @enum
7048   */
7049  const TextTrackKind = {
7050    subtitles: 'subtitles',
7051    captions: 'captions',
7052    descriptions: 'descriptions',
7053    chapters: 'chapters',
7054    metadata: 'metadata'
7055  };
7056
7057  /**
7058   * All possible `TextTrackMode`s
7059   *
7060   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7061   * @typedef TextTrack~Mode
7062   * @enum
7063   */
7064  const TextTrackMode = {
7065    disabled: 'disabled',
7066    hidden: 'hidden',
7067    showing: 'showing'
7068  };
7069
7070  /**
7071   * @file track.js
7072   */
7073
7074  /**
7075   * A Track class that contains all of the common functionality for {@link AudioTrack},
7076   * {@link VideoTrack}, and {@link TextTrack}.
7077   *
7078   * > Note: This class should not be used directly
7079   *
7080   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7081   * @extends EventTarget
7082   * @abstract
7083   */
7084  class Track extends EventTarget$2 {
7085    /**
7086     * Create an instance of this class.
7087     *
7088     * @param {Object} [options={}]
7089     *        Object of option names and values
7090     *
7091     * @param {string} [options.kind='']
7092     *        A valid kind for the track type you are creating.
7093     *
7094     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7095     *        A unique id for this AudioTrack.
7096     *
7097     * @param {string} [options.label='']
7098     *        The menu label for this track.
7099     *
7100     * @param {string} [options.language='']
7101     *        A valid two character language code.
7102     *
7103     * @abstract
7104     */
7105    constructor(options = {}) {
7106      super();
7107      const trackProps = {
7108        id: options.id || 'vjs_track_' + newGUID(),
7109        kind: options.kind || '',
7110        language: options.language || ''
7111      };
7112      let label = options.label || '';
7113
7114      /**
7115       * @memberof Track
7116       * @member {string} id
7117       *         The id of this track. Cannot be changed after creation.
7118       * @instance
7119       *
7120       * @readonly
7121       */
7122
7123      /**
7124       * @memberof Track
7125       * @member {string} kind
7126       *         The kind of track that this is. Cannot be changed after creation.
7127       * @instance
7128       *
7129       * @readonly
7130       */
7131
7132      /**
7133       * @memberof Track
7134       * @member {string} language
7135       *         The two letter language code for this track. Cannot be changed after
7136       *         creation.
7137       * @instance
7138       *
7139       * @readonly
7140       */
7141
7142      for (const key in trackProps) {
7143        Object.defineProperty(this, key, {
7144          get() {
7145            return trackProps[key];
7146          },
7147          set() {}
7148        });
7149      }
7150
7151      /**
7152       * @memberof Track
7153       * @member {string} label
7154       *         The label of this track. Cannot be changed after creation.
7155       * @instance
7156       *
7157       * @fires Track#labelchange
7158       */
7159      Object.defineProperty(this, 'label', {
7160        get() {
7161          return label;
7162        },
7163        set(newLabel) {
7164          if (newLabel !== label) {
7165            label = newLabel;
7166
7167            /**
7168             * An event that fires when label changes on this track.
7169             *
7170             * > Note: This is not part of the spec!
7171             *
7172             * @event Track#labelchange
7173             * @type {Event}
7174             */
7175            this.trigger('labelchange');
7176          }
7177        }
7178      });
7179    }
7180  }
7181
7182  /**
7183   * @file url.js
7184   * @module url
7185   */
7186
7187  /**
7188   * @typedef {Object} url:URLObject
7189   *
7190   * @property {string} protocol
7191   *           The protocol of the url that was parsed.
7192   *
7193   * @property {string} hostname
7194   *           The hostname of the url that was parsed.
7195   *
7196   * @property {string} port
7197   *           The port of the url that was parsed.
7198   *
7199   * @property {string} pathname
7200   *           The pathname of the url that was parsed.
7201   *
7202   * @property {string} search
7203   *           The search query of the url that was parsed.
7204   *
7205   * @property {string} hash
7206   *           The hash of the url that was parsed.
7207   *
7208   * @property {string} host
7209   *           The host of the url that was parsed.
7210   */
7211
7212  /**
7213   * Resolve and parse the elements of a URL.
7214   *
7215   * @function
7216   * @param    {String} url
7217   *           The url to parse
7218   *
7219   * @return   {url:URLObject}
7220   *           An object of url details
7221   */
7222  const parseUrl = function (url) {
7223    // This entire method can be replace with URL once we are able to drop IE11
7224
7225    const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
7226
7227    // add the url to an anchor and let the browser parse the URL
7228    const a = document.createElement('a');
7229    a.href = url;
7230
7231    // Copy the specific URL properties to a new object
7232    // This is also needed for IE because the anchor loses its
7233    // properties when it's removed from the dom
7234    const details = {};
7235    for (let i = 0; i < props.length; i++) {
7236      details[props[i]] = a[props[i]];
7237    }
7238
7239    // IE adds the port to the host property unlike everyone else. If
7240    // a port identifier is added for standard ports, strip it.
7241    if (details.protocol === 'http:') {
7242      details.host = details.host.replace(/:80$/, '');
7243    }
7244    if (details.protocol === 'https:') {
7245      details.host = details.host.replace(/:443$/, '');
7246    }
7247    if (!details.protocol) {
7248      details.protocol = window.location.protocol;
7249    }
7250
7251    /* istanbul ignore if */
7252    if (!details.host) {
7253      details.host = window.location.host;
7254    }
7255    return details;
7256  };
7257
7258  /**
7259   * Get absolute version of relative URL.
7260   *
7261   * @function
7262   * @param    {string} url
7263   *           URL to make absolute
7264   *
7265   * @return   {string}
7266   *           Absolute URL
7267   *
7268   * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7269   */
7270  const getAbsoluteURL = function (url) {
7271    // Check if absolute URL
7272    if (!url.match(/^https?:\/\//)) {
7273      // Add the url to an anchor and let the browser parse it to convert to an absolute url
7274      const a = document.createElement('a');
7275      a.href = url;
7276      url = a.href;
7277    }
7278    return url;
7279  };
7280
7281  /**
7282   * Returns the extension of the passed file name. It will return an empty string
7283   * if passed an invalid path.
7284   *
7285   * @function
7286   * @param    {string} path
7287   *           The fileName path like '/path/to/file.mp4'
7288   *
7289   * @return  {string}
7290   *           The extension in lower case or an empty string if no
7291   *           extension could be found.
7292   */
7293  const getFileExtension = function (path) {
7294    if (typeof path === 'string') {
7295      const splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7296      const pathParts = splitPathRe.exec(path);
7297      if (pathParts) {
7298        return pathParts.pop().toLowerCase();
7299      }
7300    }
7301    return '';
7302  };
7303
7304  /**
7305   * Returns whether the url passed is a cross domain request or not.
7306   *
7307   * @function
7308   * @param    {string} url
7309   *           The url to check.
7310   *
7311   * @param    {Object} [winLoc]
7312   *           the domain to check the url against, defaults to window.location
7313   *
7314   * @param    {string} [winLoc.protocol]
7315   *           The window location protocol defaults to window.location.protocol
7316   *
7317   * @param    {string} [winLoc.host]
7318   *           The window location host defaults to window.location.host
7319   *
7320   * @return   {boolean}
7321   *           Whether it is a cross domain request or not.
7322   */
7323  const isCrossOrigin = function (url, winLoc = window.location) {
7324    const urlInfo = parseUrl(url);
7325
7326    // IE8 protocol relative urls will return ':' for protocol
7327    const srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
7328
7329    // Check if url is for another domain/origin
7330    // IE8 doesn't know location.origin, so we won't rely on it here
7331    const crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7332    return crossOrigin;
7333  };
7334
7335  var Url = /*#__PURE__*/Object.freeze({
7336    __proto__: null,
7337    parseUrl: parseUrl,
7338    getAbsoluteURL: getAbsoluteURL,
7339    getFileExtension: getFileExtension,
7340    isCrossOrigin: isCrossOrigin
7341  });
7342
7343  var win;
7344  if (typeof window !== "undefined") {
7345    win = window;
7346  } else if (typeof commonjsGlobal !== "undefined") {
7347    win = commonjsGlobal;
7348  } else if (typeof self !== "undefined") {
7349    win = self;
7350  } else {
7351    win = {};
7352  }
7353  var window_1 = win;
7354
7355  var _extends_1 = createCommonjsModule(function (module) {
7356    function _extends() {
7357      module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {
7358        for (var i = 1; i < arguments.length; i++) {
7359          var source = arguments[i];
7360          for (var key in source) {
7361            if (Object.prototype.hasOwnProperty.call(source, key)) {
7362              target[key] = source[key];
7363            }
7364          }
7365        }
7366        return target;
7367      }, module.exports.__esModule = true, module.exports["default"] = module.exports;
7368      return _extends.apply(this, arguments);
7369    }
7370    module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
7371  });
7372  var _extends$1 = unwrapExports(_extends_1);
7373
7374  var isFunction_1 = isFunction;
7375  var toString = Object.prototype.toString;
7376  function isFunction(fn) {
7377    if (!fn) {
7378      return false;
vendor: 4,288 bytes, lines 7379-7510
7379    }
7380    var string = toString.call(fn);
7381    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
7382    // IE8 and below
7383    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7384  }
7385
7386  var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
7387    if (decodeResponseBody === void 0) {
7388      decodeResponseBody = false;
7389    }
7390    return function (err, response, responseBody) {
7391      // if the XHR failed, return that error
7392      if (err) {
7393        callback(err);
7394        return;
7395      } // if the HTTP status code is 4xx or 5xx, the request also failed
7396
7397      if (response.statusCode >= 400 && response.statusCode <= 599) {
7398        var cause = responseBody;
7399        if (decodeResponseBody) {
7400          if (window_1.TextDecoder) {
7401            var charset = getCharset(response.headers && response.headers['content-type']);
7402            try {
7403              cause = new TextDecoder(charset).decode(responseBody);
7404            } catch (e) {}
7405          } else {
7406            cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
7407          }
7408        }
7409        callback({
7410          cause: cause
7411        });
7412        return;
7413      } // otherwise, request succeeded
7414
7415      callback(null, responseBody);
7416    };
7417  };
7418  function getCharset(contentTypeHeader) {
7419    if (contentTypeHeader === void 0) {
7420      contentTypeHeader = '';
7421    }
7422    return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
7423      var _contentType$split = contentType.split('='),
7424        type = _contentType$split[0],
7425        value = _contentType$split[1];
7426      if (type.trim() === 'charset') {
7427        return value.trim();
7428      }
7429      return charset;
7430    }, 'utf-8');
7431  }
7432  var httpHandler = httpResponseHandler;
7433
7434  createXHR.httpHandler = httpHandler;
7435  /**
7436   * @license
7437   * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
7438   * Copyright (c) 2014 David Björklund
7439   * Available under the MIT license
7440   * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
7441   */
7442
7443  var parseHeaders = function parseHeaders(headers) {
7444    var result = {};
7445    if (!headers) {
7446      return result;
7447    }
7448    headers.trim().split('\n').forEach(function (row) {
7449      var index = row.indexOf(':');
7450      var key = row.slice(0, index).trim().toLowerCase();
7451      var value = row.slice(index + 1).trim();
7452      if (typeof result[key] === 'undefined') {
7453        result[key] = value;
7454      } else if (Array.isArray(result[key])) {
7455        result[key].push(value);
7456      } else {
7457        result[key] = [result[key], value];
7458      }
7459    });
7460    return result;
7461  };
7462  var lib = createXHR; // Allow use of default import syntax in TypeScript
7463
7464  var default_1 = createXHR;
7465  createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop$1;
7466  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
7467  forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
7468    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7469      options = initParams(uri, options, callback);
7470      options.method = method.toUpperCase();
7471      return _createXHR(options);
7472    };
7473  });
7474  function forEachArray(array, iterator) {
7475    for (var i = 0; i < array.length; i++) {
7476      iterator(array[i]);
7477    }
7478  }
7479  function isEmpty(obj) {
7480    for (var i in obj) {
7481      if (obj.hasOwnProperty(i)) return false;
7482    }
7483    return true;
7484  }
7485  function initParams(uri, options, callback) {
7486    var params = uri;
7487    if (isFunction_1(options)) {
7488      callback = options;
7489      if (typeof uri === "string") {
7490        params = {
7491          uri: uri
7492        };
7493      }
7494    } else {
7495      params = _extends_1({}, options, {
7496        uri: uri
7497      });
7498    }
7499    params.callback = callback;
7500    return params;
7501  }
7502  function createXHR(uri, options, callback) {
7503    options = initParams(uri, options, callback);
7504    return _createXHR(options);
7505  }
7506  function _createXHR(options) {
7507    if (typeof options.callback === "undefined") {
7508      throw new Error("callback argument missing");
7509    }
7510    var called = false;
vendor: 4,654 bytes, lines 7511-7651
7511    var callback = function cbOnce(err, response, body) {
7512      if (!called) {
7513        called = true;
7514        options.callback(err, response, body);
7515      }
7516    };
7517    function readystatechange() {
7518      if (xhr.readyState === 4) {
7519        setTimeout(loadFunc, 0);
7520      }
7521    }
7522    function getBody() {
7523      // Chrome with requestType=blob throws errors arround when even testing access to responseText
7524      var body = undefined;
7525      if (xhr.response) {
7526        body = xhr.response;
7527      } else {
7528        body = xhr.responseText || getXml(xhr);
7529      }
7530      if (isJson) {
7531        try {
7532          body = JSON.parse(body);
7533        } catch (e) {}
7534      }
7535      return body;
7536    }
7537    function errorFunc(evt) {
7538      clearTimeout(timeoutTimer);
7539      if (!(evt instanceof Error)) {
7540        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7541      }
7542      evt.statusCode = 0;
7543      return callback(evt, failureResponse);
7544    } // will load the data & process the response in a special response object
7545
7546    function loadFunc() {
7547      if (aborted) return;
7548      var status;
7549      clearTimeout(timeoutTimer);
7550      if (options.useXDR && xhr.status === undefined) {
7551        //IE8 CORS GET successful response doesn't have a status field, but body is fine
7552        status = 200;
7553      } else {
7554        status = xhr.status === 1223 ? 204 : xhr.status;
7555      }
7556      var response = failureResponse;
7557      var err = null;
7558      if (status !== 0) {
7559        response = {
7560          body: getBody(),
7561          statusCode: status,
7562          method: method,
7563          headers: {},
7564          url: uri,
7565          rawRequest: xhr
7566        };
7567        if (xhr.getAllResponseHeaders) {
7568          //remember xhr can in fact be XDR for CORS in IE
7569          response.headers = parseHeaders(xhr.getAllResponseHeaders());
7570        }
7571      } else {
7572        err = new Error("Internal XMLHttpRequest Error");
7573      }
7574      return callback(err, response, response.body);
7575    }
7576    var xhr = options.xhr || null;
7577    if (!xhr) {
7578      if (options.cors || options.useXDR) {
7579        xhr = new createXHR.XDomainRequest();
7580      } else {
7581        xhr = new createXHR.XMLHttpRequest();
7582      }
7583    }
7584    var key;
7585    var aborted;
7586    var uri = xhr.url = options.uri || options.url;
7587    var method = xhr.method = options.method || "GET";
7588    var body = options.body || options.data;
7589    var headers = xhr.headers = options.headers || {};
7590    var sync = !!options.sync;
7591    var isJson = false;
7592    var timeoutTimer;
7593    var failureResponse = {
7594      body: undefined,
7595      headers: {},
7596      statusCode: 0,
7597      method: method,
7598      url: uri,
7599      rawRequest: xhr
7600    };
7601    if ("json" in options && options.json !== false) {
7602      isJson = true;
7603      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7604
7605      if (method !== "GET" && method !== "HEAD") {
7606        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7607
7608        body = JSON.stringify(options.json === true ? body : options.json);
7609      }
7610    }
7611    xhr.onreadystatechange = readystatechange;
7612    xhr.onload = loadFunc;
7613    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
7614
7615    xhr.onprogress = function () {// IE must die
7616    };
7617    xhr.onabort = function () {
7618      aborted = true;
7619    };
7620    xhr.ontimeout = errorFunc;
7621    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
7622
7623    if (!sync) {
7624      xhr.withCredentials = !!options.withCredentials;
7625    } // Cannot set timeout with sync request
7626    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7627    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7628
7629    if (!sync && options.timeout > 0) {
7630      timeoutTimer = setTimeout(function () {
7631        if (aborted) return;
7632        aborted = true; //IE9 may still call readystatechange
7633
7634        xhr.abort("timeout");
7635        var e = new Error("XMLHttpRequest timeout");
7636        e.code = "ETIMEDOUT";
7637        errorFunc(e);
7638      }, options.timeout);
7639    }
7640    if (xhr.setRequestHeader) {
7641      for (key in headers) {
7642        if (headers.hasOwnProperty(key)) {
7643          xhr.setRequestHeader(key, headers[key]);
7644        }
7645      }
7646    } else if (options.headers && !isEmpty(options.headers)) {
7647      throw new Error("Headers cannot be set on an XDomainRequest object");
7648    }
7649    if ("responseType" in options) {
7650      xhr.responseType = options.responseType;
7651    }
vendor: 5,048 bytes, lines 7652-7812
7652    if ("beforeSend" in options && typeof options.beforeSend === "function") {
7653      options.beforeSend(xhr);
7654    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7655    // XMLHttpRequest spec says to pass null as body to indicate no body
7656    // See https://github.com/naugtur/xhr/issues/100.
7657
7658    xhr.send(body || null);
7659    return xhr;
7660  }
7661  function getXml(xhr) {
7662    // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
7663    // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
7664    try {
7665      if (xhr.responseType === "document") {
7666        return xhr.responseXML;
7667      }
7668      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7669      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7670        return xhr.responseXML;
7671      }
7672    } catch (e) {}
7673    return null;
7674  }
7675  function noop$1() {}
7676  lib.default = default_1;
7677
7678  /**
7679   * @file text-track.js
7680   */
7681
7682  /**
7683   * Takes a webvtt file contents and parses it into cues
7684   *
7685   * @param {string} srcContent
7686   *        webVTT file contents
7687   *
7688   * @param {TextTrack} track
7689   *        TextTrack to add cues to. Cues come from the srcContent.
7690   *
7691   * @private
7692   */
7693  const parseCues = function (srcContent, track) {
7694    const parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder());
7695    const errors = [];
7696    parser.oncue = function (cue) {
7697      track.addCue(cue);
7698    };
7699    parser.onparsingerror = function (error) {
7700      errors.push(error);
7701    };
7702    parser.onflush = function () {
7703      track.trigger({
7704        type: 'loadeddata',
7705        target: track
7706      });
7707    };
7708    parser.parse(srcContent);
7709    if (errors.length > 0) {
7710      if (window.console && window.console.groupCollapsed) {
7711        window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
7712      }
7713      errors.forEach(error => log$1.error(error));
7714      if (window.console && window.console.groupEnd) {
7715        window.console.groupEnd();
7716      }
7717    }
7718    parser.flush();
7719  };
7720
7721  /**
7722   * Load a `TextTrack` from a specified url.
7723   *
7724   * @param {string} src
7725   *        Url to load track from.
7726   *
7727   * @param {TextTrack} track
7728   *        Track to add cues to. Comes from the content at the end of `url`.
7729   *
7730   * @private
7731   */
7732  const loadTrack = function (src, track) {
7733    const opts = {
7734      uri: src
7735    };
7736    const crossOrigin = isCrossOrigin(src);
7737    if (crossOrigin) {
7738      opts.cors = crossOrigin;
7739    }
7740    const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
7741    if (withCredentials) {
7742      opts.withCredentials = withCredentials;
7743    }
7744    lib(opts, bind_(this, function (err, response, responseBody) {
7745      if (err) {
7746        return log$1.error(err, response);
7747      }
7748      track.loaded_ = true;
7749
7750      // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7751      // NOTE: this is only used for the alt/video.novtt.js build
7752      if (typeof window.WebVTT !== 'function') {
7753        if (track.tech_) {
7754          // to prevent use before define eslint error, we define loadHandler
7755          // as a let here
7756          track.tech_.any(['vttjsloaded', 'vttjserror'], event => {
7757            if (event.type === 'vttjserror') {
7758              log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
7759              return;
7760            }
7761            return parseCues(responseBody, track);
7762          });
7763        }
7764      } else {
7765        parseCues(responseBody, track);
7766      }
7767    }));
7768  };
7769
7770  /**
7771   * A representation of a single `TextTrack`.
7772   *
7773   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7774   * @extends Track
7775   */
7776  class TextTrack extends Track {
7777    /**
7778     * Create an instance of this class.
7779     *
7780     * @param {Object} options={}
7781     *        Object of option names and values
7782     *
7783     * @param { import('../tech/tech').default } options.tech
7784     *        A reference to the tech that owns this TextTrack.
7785     *
7786     * @param {TextTrack~Kind} [options.kind='subtitles']
7787     *        A valid text track kind.
7788     *
7789     * @param {TextTrack~Mode} [options.mode='disabled']
7790     *        A valid text track mode.
7791     *
7792     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7793     *        A unique id for this TextTrack.
7794     *
7795     * @param {string} [options.label='']
7796     *        The menu label for this track.
7797     *
7798     * @param {string} [options.language='']
7799     *        A valid two character language code.
7800     *
7801     * @param {string} [options.srclang='']
7802     *        A valid two character language code. An alternative, but deprioritized
7803     *        version of `options.language`
7804     *
7805     * @param {string} [options.src]
7806     *        A url to TextTrack cues.
7807     *
7808     * @param {boolean} [options.default]
7809     *        If this track should default to on or off.
7810     */
7811    constructor(options = {}) {
7812      if (!options.tech) {
vendor: 13,413 bytes, lines 7813-8240
7813        throw new Error('A tech was not provided.');
7814      }
7815      const settings = merge$2(options, {
7816        kind: TextTrackKind[options.kind] || 'subtitles',
7817        language: options.language || options.srclang || ''
7818      });
7819      let mode = TextTrackMode[settings.mode] || 'disabled';
7820      const default_ = settings.default;
7821      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7822        mode = 'hidden';
7823      }
7824      super(settings);
7825      this.tech_ = settings.tech;
7826      this.cues_ = [];
7827      this.activeCues_ = [];
7828      this.preload_ = this.tech_.preloadTextTracks !== false;
7829      const cues = new TextTrackCueList(this.cues_);
7830      const activeCues = new TextTrackCueList(this.activeCues_);
7831      let changed = false;
7832      this.timeupdateHandler = bind_(this, function (event = {}) {
7833        if (this.tech_.isDisposed()) {
7834          return;
7835        }
7836        if (!this.tech_.isReady_) {
7837          if (event.type !== 'timeupdate') {
7838            this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7839          }
7840          return;
7841        }
7842
7843        // Accessing this.activeCues for the side-effects of updating itself
7844        // due to its nature as a getter function. Do not remove or cues will
7845        // stop updating!
7846        // Use the setter to prevent deletion from uglify (pure_getters rule)
7847        this.activeCues = this.activeCues;
7848        if (changed) {
7849          this.trigger('cuechange');
7850          changed = false;
7851        }
7852        if (event.type !== 'timeupdate') {
7853          this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7854        }
7855      });
7856      const disposeHandler = () => {
7857        this.stopTracking();
7858      };
7859      this.tech_.one('dispose', disposeHandler);
7860      if (mode !== 'disabled') {
7861        this.startTracking();
7862      }
7863      Object.defineProperties(this, {
7864        /**
7865         * @memberof TextTrack
7866         * @member {boolean} default
7867         *         If this track was set to be on or off by default. Cannot be changed after
7868         *         creation.
7869         * @instance
7870         *
7871         * @readonly
7872         */
7873        default: {
7874          get() {
7875            return default_;
7876          },
7877          set() {}
7878        },
7879        /**
7880         * @memberof TextTrack
7881         * @member {string} mode
7882         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7883         *         not be set if setting to an invalid mode.
7884         * @instance
7885         *
7886         * @fires TextTrack#modechange
7887         */
7888        mode: {
7889          get() {
7890            return mode;
7891          },
7892          set(newMode) {
7893            if (!TextTrackMode[newMode]) {
7894              return;
7895            }
7896            if (mode === newMode) {
7897              return;
7898            }
7899            mode = newMode;
7900            if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
7901              // On-demand load.
7902              loadTrack(this.src, this);
7903            }
7904            this.stopTracking();
7905            if (mode !== 'disabled') {
7906              this.startTracking();
7907            }
7908            /**
7909             * An event that fires when mode changes on this track. This allows
7910             * the TextTrackList that holds this track to act accordingly.
7911             *
7912             * > Note: This is not part of the spec!
7913             *
7914             * @event TextTrack#modechange
7915             * @type {Event}
7916             */
7917            this.trigger('modechange');
7918          }
7919        },
7920        /**
7921         * @memberof TextTrack
7922         * @member {TextTrackCueList} cues
7923         *         The text track cue list for this TextTrack.
7924         * @instance
7925         */
7926        cues: {
7927          get() {
7928            if (!this.loaded_) {
7929              return null;
7930            }
7931            return cues;
7932          },
7933          set() {}
7934        },
7935        /**
7936         * @memberof TextTrack
7937         * @member {TextTrackCueList} activeCues
7938         *         The list text track cues that are currently active for this TextTrack.
7939         * @instance
7940         */
7941        activeCues: {
7942          get() {
7943            if (!this.loaded_) {
7944              return null;
7945            }
7946
7947            // nothing to do
7948            if (this.cues.length === 0) {
7949              return activeCues;
7950            }
7951            const ct = this.tech_.currentTime();
7952            const active = [];
7953            for (let i = 0, l = this.cues.length; i < l; i++) {
7954              const cue = this.cues[i];
7955              if (cue.startTime <= ct && cue.endTime >= ct) {
7956                active.push(cue);
7957              }
7958            }
7959            changed = false;
7960            if (active.length !== this.activeCues_.length) {
7961              changed = true;
7962            } else {
7963              for (let i = 0; i < active.length; i++) {
7964                if (this.activeCues_.indexOf(active[i]) === -1) {
7965                  changed = true;
7966                }
7967              }
7968            }
7969            this.activeCues_ = active;
7970            activeCues.setCues_(this.activeCues_);
7971            return activeCues;
7972          },
7973          // /!\ Keep this setter empty (see the timeupdate handler above)
7974          set() {}
7975        }
7976      });
7977      if (settings.src) {
7978        this.src = settings.src;
7979        if (!this.preload_) {
7980          // Tracks will load on-demand.
7981          // Act like we're loaded for other purposes.
7982          this.loaded_ = true;
7983        }
7984        if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
7985          loadTrack(this.src, this);
7986        }
7987      } else {
7988        this.loaded_ = true;
7989      }
7990    }
7991    startTracking() {
7992      // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
7993      this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7994      // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
7995      this.tech_.on('timeupdate', this.timeupdateHandler);
7996    }
7997    stopTracking() {
7998      if (this.rvf_) {
7999        this.tech_.cancelVideoFrameCallback(this.rvf_);
8000        this.rvf_ = undefined;
8001      }
8002      this.tech_.off('timeupdate', this.timeupdateHandler);
8003    }
8004
8005    /**
8006     * Add a cue to the internal list of cues.
8007     *
8008     * @param {TextTrack~Cue} cue
8009     *        The cue to add to our internal list
8010     */
8011    addCue(originalCue) {
8012      let cue = originalCue;
8013
8014      // Testing if the cue is a VTTCue in a way that survives minification
8015      if (!('getCueAsHTML' in cue)) {
8016        cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8017        for (const prop in originalCue) {
8018          if (!(prop in cue)) {
8019            cue[prop] = originalCue[prop];
8020          }
8021        }
8022
8023        // make sure that `id` is copied over
8024        cue.id = originalCue.id;
8025        cue.originalCue_ = originalCue;
8026      }
8027      const tracks = this.tech_.textTracks();
8028      for (let i = 0; i < tracks.length; i++) {
8029        if (tracks[i] !== this) {
8030          tracks[i].removeCue(cue);
8031        }
8032      }
8033      this.cues_.push(cue);
8034      this.cues.setCues_(this.cues_);
8035    }
8036
8037    /**
8038     * Remove a cue from our internal list
8039     *
8040     * @param {TextTrack~Cue} removeCue
8041     *        The cue to remove from our internal list
8042     */
8043    removeCue(removeCue) {
8044      let i = this.cues_.length;
8045      while (i--) {
8046        const cue = this.cues_[i];
8047        if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) {
8048          this.cues_.splice(i, 1);
8049          this.cues.setCues_(this.cues_);
8050          break;
8051        }
8052      }
8053    }
8054  }
8055
8056  /**
8057   * cuechange - One or more cues in the track have become active or stopped being active.
8058   * @protected
8059   */
8060  TextTrack.prototype.allowedEvents_ = {
8061    cuechange: 'cuechange'
8062  };
8063
8064  /**
8065   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8066   * only one `AudioTrack` in the list will be enabled at a time.
8067   *
8068   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8069   * @extends Track
8070   */
8071  class AudioTrack extends Track {
8072    /**
8073     * Create an instance of this class.
8074     *
8075     * @param {Object} [options={}]
8076     *        Object of option names and values
8077     *
8078     * @param {AudioTrack~Kind} [options.kind='']
8079     *        A valid audio track kind
8080     *
8081     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8082     *        A unique id for this AudioTrack.
8083     *
8084     * @param {string} [options.label='']
8085     *        The menu label for this track.
8086     *
8087     * @param {string} [options.language='']
8088     *        A valid two character language code.
8089     *
8090     * @param {boolean} [options.enabled]
8091     *        If this track is the one that is currently playing. If this track is part of
8092     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8093     */
8094    constructor(options = {}) {
8095      const settings = merge$2(options, {
8096        kind: AudioTrackKind[options.kind] || ''
8097      });
8098      super(settings);
8099      let enabled = false;
8100
8101      /**
8102       * @memberof AudioTrack
8103       * @member {boolean} enabled
8104       *         If this `AudioTrack` is enabled or not. When setting this will
8105       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8106       * @instance
8107       *
8108       * @fires VideoTrack#selectedchange
8109       */
8110      Object.defineProperty(this, 'enabled', {
8111        get() {
8112          return enabled;
8113        },
8114        set(newEnabled) {
8115          // an invalid or unchanged value
8116          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8117            return;
8118          }
8119          enabled = newEnabled;
8120
8121          /**
8122           * An event that fires when enabled changes on this track. This allows
8123           * the AudioTrackList that holds this track to act accordingly.
8124           *
8125           * > Note: This is not part of the spec! Native tracks will do
8126           *         this internally without an event.
8127           *
8128           * @event AudioTrack#enabledchange
8129           * @type {Event}
8130           */
8131          this.trigger('enabledchange');
8132        }
8133      });
8134
8135      // if the user sets this track to selected then
8136      // set selected to that true value otherwise
8137      // we keep it false
8138      if (settings.enabled) {
8139        this.enabled = settings.enabled;
8140      }
8141      this.loaded_ = true;
8142    }
8143  }
8144
8145  /**
8146   * A representation of a single `VideoTrack`.
8147   *
8148   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8149   * @extends Track
8150   */
8151  class VideoTrack extends Track {
8152    /**
8153     * Create an instance of this class.
8154     *
8155     * @param {Object} [options={}]
8156     *        Object of option names and values
8157     *
8158     * @param {string} [options.kind='']
8159     *        A valid {@link VideoTrack~Kind}
8160     *
8161     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8162     *        A unique id for this AudioTrack.
8163     *
8164     * @param {string} [options.label='']
8165     *        The menu label for this track.
8166     *
8167     * @param {string} [options.language='']
8168     *        A valid two character language code.
8169     *
8170     * @param {boolean} [options.selected]
8171     *        If this track is the one that is currently playing.
8172     */
8173    constructor(options = {}) {
8174      const settings = merge$2(options, {
8175        kind: VideoTrackKind[options.kind] || ''
8176      });
8177      super(settings);
8178      let selected = false;
8179
8180      /**
8181       * @memberof VideoTrack
8182       * @member {boolean} selected
8183       *         If this `VideoTrack` is selected or not. When setting this will
8184       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8185       * @instance
8186       *
8187       * @fires VideoTrack#selectedchange
8188       */
8189      Object.defineProperty(this, 'selected', {
8190        get() {
8191          return selected;
8192        },
8193        set(newSelected) {
8194          // an invalid or unchanged value
8195          if (typeof newSelected !== 'boolean' || newSelected === selected) {
8196            return;
8197          }
8198          selected = newSelected;
8199
8200          /**
8201           * An event that fires when selected changes on this track. This allows
8202           * the VideoTrackList that holds this track to act accordingly.
8203           *
8204           * > Note: This is not part of the spec! Native tracks will do
8205           *         this internally without an event.
8206           *
8207           * @event VideoTrack#selectedchange
8208           * @type {Event}
8209           */
8210          this.trigger('selectedchange');
8211        }
8212      });
8213
8214      // if the user sets this track to selected then
8215      // set selected to that true value otherwise
8216      // we keep it false
8217      if (settings.selected) {
8218        this.selected = settings.selected;
8219      }
8220    }
8221  }
8222
8223  /**
8224   * @file html-track-element.js
8225   */
8226
8227  /**
8228   * A single track represented in the DOM.
8229   *
8230   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8231   * @extends EventTarget
8232   */
8233  class HTMLTrackElement extends EventTarget$2 {
8234    /**
8235     * Create an instance of this class.
8236     *
8237     * @param {Object} options={}
8238     *        Object of option names and values
8239     *
8240     * @param { import('../tech/tech').default 
vendor: 62,173 bytes, lines 8240-10112
8240} options.tech
8241     *        A reference to the tech that owns this HTMLTrackElement.
8242     *
8243     * @param {TextTrack~Kind} [options.kind='subtitles']
8244     *        A valid text track kind.
8245     *
8246     * @param {TextTrack~Mode} [options.mode='disabled']
8247     *        A valid text track mode.
8248     *
8249     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8250     *        A unique id for this TextTrack.
8251     *
8252     * @param {string} [options.label='']
8253     *        The menu label for this track.
8254     *
8255     * @param {string} [options.language='']
8256     *        A valid two character language code.
8257     *
8258     * @param {string} [options.srclang='']
8259     *        A valid two character language code. An alternative, but deprioritized
8260     *        version of `options.language`
8261     *
8262     * @param {string} [options.src]
8263     *        A url to TextTrack cues.
8264     *
8265     * @param {boolean} [options.default]
8266     *        If this track should default to on or off.
8267     */
8268    constructor(options = {}) {
8269      super();
8270      let readyState;
8271      const track = new TextTrack(options);
8272      this.kind = track.kind;
8273      this.src = track.src;
8274      this.srclang = track.language;
8275      this.label = track.label;
8276      this.default = track.default;
8277      Object.defineProperties(this, {
8278        /**
8279         * @memberof HTMLTrackElement
8280         * @member {HTMLTrackElement~ReadyState} readyState
8281         *         The current ready state of the track element.
8282         * @instance
8283         */
8284        readyState: {
8285          get() {
8286            return readyState;
8287          }
8288        },
8289        /**
8290         * @memberof HTMLTrackElement
8291         * @member {TextTrack} track
8292         *         The underlying TextTrack object.
8293         * @instance
8294         *
8295         */
8296        track: {
8297          get() {
8298            return track;
8299          }
8300        }
8301      });
8302      readyState = HTMLTrackElement.NONE;
8303
8304      /**
8305       * @listens TextTrack#loadeddata
8306       * @fires HTMLTrackElement#load
8307       */
8308      track.addEventListener('loadeddata', () => {
8309        readyState = HTMLTrackElement.LOADED;
8310        this.trigger({
8311          type: 'load',
8312          target: this
8313        });
8314      });
8315    }
8316  }
8317
8318  /**
8319   * @protected
8320   */
8321  HTMLTrackElement.prototype.allowedEvents_ = {
8322    load: 'load'
8323  };
8324
8325  /**
8326   * The text track not loaded state.
8327   *
8328   * @type {number}
8329   * @static
8330   */
8331  HTMLTrackElement.NONE = 0;
8332
8333  /**
8334   * The text track loading state.
8335   *
8336   * @type {number}
8337   * @static
8338   */
8339  HTMLTrackElement.LOADING = 1;
8340
8341  /**
8342   * The text track loaded state.
8343   *
8344   * @type {number}
8345   * @static
8346   */
8347  HTMLTrackElement.LOADED = 2;
8348
8349  /**
8350   * The text track failed to load state.
8351   *
8352   * @type {number}
8353   * @static
8354   */
8355  HTMLTrackElement.ERROR = 3;
8356
8357  /*
8358   * This file contains all track properties that are used in
8359   * player.js, tech.js, html5.js and possibly other techs in the future.
8360   */
8361
8362  const NORMAL = {
8363    audio: {
8364      ListClass: AudioTrackList,
8365      TrackClass: AudioTrack,
8366      capitalName: 'Audio'
8367    },
8368    video: {
8369      ListClass: VideoTrackList,
8370      TrackClass: VideoTrack,
8371      capitalName: 'Video'
8372    },
8373    text: {
8374      ListClass: TextTrackList,
8375      TrackClass: TextTrack,
8376      capitalName: 'Text'
8377    }
8378  };
8379  Object.keys(NORMAL).forEach(function (type) {
8380    NORMAL[type].getterName = `${type}Tracks`;
8381    NORMAL[type].privateName = `${type}Tracks_`;
8382  });
8383  const REMOTE = {
8384    remoteText: {
8385      ListClass: TextTrackList,
8386      TrackClass: TextTrack,
8387      capitalName: 'RemoteText',
8388      getterName: 'remoteTextTracks',
8389      privateName: 'remoteTextTracks_'
8390    },
8391    remoteTextEl: {
8392      ListClass: HtmlTrackElementList,
8393      TrackClass: HTMLTrackElement,
8394      capitalName: 'RemoteTextTrackEls',
8395      getterName: 'remoteTextTrackEls',
8396      privateName: 'remoteTextTrackEls_'
8397    }
8398  };
8399  const ALL = Object.assign({}, NORMAL, REMOTE);
8400  REMOTE.names = Object.keys(REMOTE);
8401  NORMAL.names = Object.keys(NORMAL);
8402  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8403
8404  var minDoc = {};
8405
8406  var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
8407  var doccy;
8408  if (typeof document !== 'undefined') {
8409    doccy = document;
8410  } else {
8411    doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
8412    if (!doccy) {
8413      doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
8414    }
8415  }
8416  var document_1 = doccy;
8417
8418  /**
8419   * Copyright 2013 vtt.js Contributors
8420   *
8421   * Licensed under the Apache License, Version 2.0 (the "License");
8422   * you may not use this file except in compliance with the License.
8423   * You may obtain a copy of the License at
8424   *
8425   *   http://www.apache.org/licenses/LICENSE-2.0
8426   *
8427   * Unless required by applicable law or agreed to in writing, software
8428   * distributed under the License is distributed on an "AS IS" BASIS,
8429   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8430   * See the License for the specific language governing permissions and
8431   * limitations under the License.
8432   */
8433
8434  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
8435  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
8436
8437  var _objCreate = Object.create || function () {
8438    function F() {}
8439    return function (o) {
8440      if (arguments.length !== 1) {
8441        throw new Error('Object.create shim only accepts one parameter.');
8442      }
8443      F.prototype = o;
8444      return new F();
8445    };
8446  }();
8447
8448  // Creates a new ParserError object from an errorData object. The errorData
8449  // object should have default code and message properties. The default message
8450  // property can be overriden by passing in a message parameter.
8451  // See ParsingError.Errors below for acceptable errors.
8452  function ParsingError(errorData, message) {
8453    this.name = "ParsingError";
8454    this.code = errorData.code;
8455    this.message = message || errorData.message;
8456  }
8457  ParsingError.prototype = _objCreate(Error.prototype);
8458  ParsingError.prototype.constructor = ParsingError;
8459
8460  // ParsingError metadata for acceptable ParsingErrors.
8461  ParsingError.Errors = {
8462    BadSignature: {
8463      code: 0,
8464      message: "Malformed WebVTT signature."
8465    },
8466    BadTimeStamp: {
8467      code: 1,
8468      message: "Malformed time stamp."
8469    }
8470  };
8471
8472  // Try to parse input as a time stamp.
8473  function parseTimeStamp(input) {
8474    function computeSeconds(h, m, s, f) {
8475      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8476    }
8477    var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
8478    if (!m) {
8479      return null;
8480    }
8481    if (m[3]) {
8482      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8483      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8484    } else if (m[1] > 59) {
8485      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8486      // First position is hours as it's over 59.
8487      return computeSeconds(m[1], m[2], 0, m[4]);
8488    } else {
8489      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8490      return computeSeconds(0, m[1], m[2], m[4]);
8491    }
8492  }
8493
8494  // A settings object holds key/value pairs and will ignore anything but the first
8495  // assignment to a specific key.
8496  function Settings() {
8497    this.values = _objCreate(null);
8498  }
8499  Settings.prototype = {
8500    // Only accept the first assignment to any key.
8501    set: function (k, v) {
8502      if (!this.get(k) && v !== "") {
8503        this.values[k] = v;
8504      }
8505    },
8506    // Return the value for a key, or a default value.
8507    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8508    // a number of possible default values as properties where 'defaultKey' is
8509    // the key of the property that will be chosen; otherwise it's assumed to be
8510    // a single value.
8511    get: function (k, dflt, defaultKey) {
8512      if (defaultKey) {
8513        return this.has(k) ? this.values[k] : dflt[defaultKey];
8514      }
8515      return this.has(k) ? this.values[k] : dflt;
8516    },
8517    // Check whether we have a value for a key.
8518    has: function (k) {
8519      return k in this.values;
8520    },
8521    // Accept a setting if its one of the given alternatives.
8522    alt: function (k, v, a) {
8523      for (var n = 0; n < a.length; ++n) {
8524        if (v === a[n]) {
8525          this.set(k, v);
8526          break;
8527        }
8528      }
8529    },
8530    // Accept a setting if its a valid (signed) integer.
8531    integer: function (k, v) {
8532      if (/^-?\d+$/.test(v)) {
8533        // integer
8534        this.set(k, parseInt(v, 10));
8535      }
8536    },
8537    // Accept a setting if its a valid percentage.
8538    percent: function (k, v) {
8539      if (v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8540        v = parseFloat(v);
8541        if (v >= 0 && v <= 100) {
8542          this.set(k, v);
8543          return true;
8544        }
8545      }
8546      return false;
8547    }
8548  };
8549
8550  // Helper function to parse input into groups separated by 'groupDelim', and
8551  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8552  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8553    var groups = groupDelim ? input.split(groupDelim) : [input];
8554    for (var i in groups) {
8555      if (typeof groups[i] !== "string") {
8556        continue;
8557      }
8558      var kv = groups[i].split(keyValueDelim);
8559      if (kv.length !== 2) {
8560        continue;
8561      }
8562      var k = kv[0].trim();
8563      var v = kv[1].trim();
8564      callback(k, v);
8565    }
8566  }
8567  function parseCue(input, cue, regionList) {
8568    // Remember the original input if we need to throw an error.
8569    var oInput = input;
8570    // 4.1 WebVTT timestamp
8571    function consumeTimeStamp() {
8572      var ts = parseTimeStamp(input);
8573      if (ts === null) {
8574        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8575      }
8576      // Remove time stamp from input.
8577      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8578      return ts;
8579    }
8580
8581    // 4.4.2 WebVTT cue settings
8582    function consumeCueSettings(input, cue) {
8583      var settings = new Settings();
8584      parseOptions(input, function (k, v) {
8585        switch (k) {
8586          case "region":
8587            // Find the last region we parsed with the same region id.
8588            for (var i = regionList.length - 1; i >= 0; i--) {
8589              if (regionList[i].id === v) {
8590                settings.set(k, regionList[i].region);
8591                break;
8592              }
8593            }
8594            break;
8595          case "vertical":
8596            settings.alt(k, v, ["rl", "lr"]);
8597            break;
8598          case "line":
8599            var vals = v.split(","),
8600              vals0 = vals[0];
8601            settings.integer(k, vals0);
8602            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8603            settings.alt(k, vals0, ["auto"]);
8604            if (vals.length === 2) {
8605              settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
8606            }
8607            break;
8608          case "position":
8609            vals = v.split(",");
8610            settings.percent(k, vals[0]);
8611            if (vals.length === 2) {
8612              settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
8613            }
8614            break;
8615          case "size":
8616            settings.percent(k, v);
8617            break;
8618          case "align":
8619            settings.alt(k, v, ["start", "center", "end", "left", "right"]);
8620            break;
8621        }
8622      }, /:/, /\s/);
8623
8624      // Apply default values for any missing fields.
8625      cue.region = settings.get("region", null);
8626      cue.vertical = settings.get("vertical", "");
8627      try {
8628        cue.line = settings.get("line", "auto");
8629      } catch (e) {}
8630      cue.lineAlign = settings.get("lineAlign", "start");
8631      cue.snapToLines = settings.get("snapToLines", true);
8632      cue.size = settings.get("size", 100);
8633      // Safari still uses the old middle value and won't accept center
8634      try {
8635        cue.align = settings.get("align", "center");
8636      } catch (e) {
8637        cue.align = settings.get("align", "middle");
8638      }
8639      try {
8640        cue.position = settings.get("position", "auto");
8641      } catch (e) {
8642        cue.position = settings.get("position", {
8643          start: 0,
8644          left: 0,
8645          center: 50,
8646          middle: 50,
8647          end: 100,
8648          right: 100
8649        }, cue.align);
8650      }
8651      cue.positionAlign = settings.get("positionAlign", {
8652        start: "start",
8653        left: "start",
8654        center: "center",
8655        middle: "center",
8656        end: "end",
8657        right: "end"
8658      }, cue.align);
8659    }
8660    function skipWhitespace() {
8661      input = input.replace(/^\s+/, "");
8662    }
8663
8664    // 4.1 WebVTT cue timings.
8665    skipWhitespace();
8666    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8667    skipWhitespace();
8668    if (input.substr(0, 3) !== "-->") {
8669      // (3) next characters must match "-->"
8670      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8671    }
8672    input = input.substr(3);
8673    skipWhitespace();
8674    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8675
8676    // 4.1 WebVTT cue settings list.
8677    skipWhitespace();
8678    consumeCueSettings(input, cue);
8679  }
8680
8681  // When evaluating this file as part of a Webpack bundle for server
8682  // side rendering, `document` is an empty object.
8683  var TEXTAREA_ELEMENT = document_1.createElement && document_1.createElement("textarea");
8684  var TAG_NAME = {
8685    c: "span",
8686    i: "i",
8687    b: "b",
8688    u: "u",
8689    ruby: "ruby",
8690    rt: "rt",
8691    v: "span",
8692    lang: "span"
8693  };
8694
8695  // 5.1 default text color
8696  // 5.2 default text background color is equivalent to text color with bg_ prefix
8697  var DEFAULT_COLOR_CLASS = {
8698    white: 'rgba(255,255,255,1)',
8699    lime: 'rgba(0,255,0,1)',
8700    cyan: 'rgba(0,255,255,1)',
8701    red: 'rgba(255,0,0,1)',
8702    yellow: 'rgba(255,255,0,1)',
8703    magenta: 'rgba(255,0,255,1)',
8704    blue: 'rgba(0,0,255,1)',
8705    black: 'rgba(0,0,0,1)'
8706  };
8707  var TAG_ANNOTATION = {
8708    v: "title",
8709    lang: "lang"
8710  };
8711  var NEEDS_PARENT = {
8712    rt: "ruby"
8713  };
8714
8715  // Parse content into a document fragment.
8716  function parseContent(window, input) {
8717    function nextToken() {
8718      // Check for end-of-string.
8719      if (!input) {
8720        return null;
8721      }
8722
8723      // Consume 'n' characters from the input.
8724      function consume(result) {
8725        input = input.substr(result.length);
8726        return result;
8727      }
8728      var m = input.match(/^([^<]*)(<[^>]*>?)?/);
8729      // If there is some text before the next tag, return it, otherwise return
8730      // the tag.
8731      return consume(m[1] ? m[1] : m[2]);
8732    }
8733    function unescape(s) {
8734      TEXTAREA_ELEMENT.innerHTML = s;
8735      s = TEXTAREA_ELEMENT.textContent;
8736      TEXTAREA_ELEMENT.textContent = "";
8737      return s;
8738    }
8739    function shouldAdd(current, element) {
8740      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8741    }
8742
8743    // Create an element for this tag.
8744    function createElement(type, annotation) {
8745      var tagName = TAG_NAME[type];
8746      if (!tagName) {
8747        return null;
8748      }
8749      var element = window.document.createElement(tagName);
8750      var name = TAG_ANNOTATION[type];
8751      if (name && annotation) {
8752        element[name] = annotation.trim();
8753      }
8754      return element;
8755    }
8756    var rootDiv = window.document.createElement("div"),
8757      current = rootDiv,
8758      t,
8759      tagStack = [];
8760    while ((t = nextToken()) !== null) {
8761      if (t[0] === '<') {
8762        if (t[1] === "/") {
8763          // If the closing tag matches, move back up to the parent node.
8764          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8765            tagStack.pop();
8766            current = current.parentNode;
8767          }
8768          // Otherwise just ignore the end tag.
8769          continue;
8770        }
8771        var ts = parseTimeStamp(t.substr(1, t.length - 2));
8772        var node;
8773        if (ts) {
8774          // Timestamps are lead nodes as well.
8775          node = window.document.createProcessingInstruction("timestamp", ts);
8776          current.appendChild(node);
8777          continue;
8778        }
8779        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
8780        // If we can't parse the tag, skip to the next tag.
8781        if (!m) {
8782          continue;
8783        }
8784        // Try to construct an element, and ignore the tag if we couldn't.
8785        node = createElement(m[1], m[3]);
8786        if (!node) {
8787          continue;
8788        }
8789        // Determine if the tag should be added based on the context of where it
8790        // is placed in the cuetext.
8791        if (!shouldAdd(current, node)) {
8792          continue;
8793        }
8794        // Set the class list (as a list of classes, separated by space).
8795        if (m[2]) {
8796          var classes = m[2].split('.');
8797          classes.forEach(function (cl) {
8798            var bgColor = /^bg_/.test(cl);
8799            // slice out `bg_` if it's a background color
8800            var colorName = bgColor ? cl.slice(3) : cl;
8801            if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
8802              var propName = bgColor ? 'background-color' : 'color';
8803              var propValue = DEFAULT_COLOR_CLASS[colorName];
8804              node.style[propName] = propValue;
8805            }
8806          });
8807          node.className = classes.join(' ');
8808        }
8809        // Append the node to the current node, and enter the scope of the new
8810        // node.
8811        tagStack.push(m[1]);
8812        current.appendChild(node);
8813        current = node;
8814        continue;
8815      }
8816
8817      // Text nodes are leaf nodes.
8818      current.appendChild(window.document.createTextNode(unescape(t)));
8819    }
8820    return rootDiv;
8821  }
8822
8823  // This is a list of all the Unicode characters that have a strong
8824  // right-to-left category. What this means is that these characters are
8825  // written right-to-left for sure. It was generated by pulling all the strong
8826  // right-to-left characters out of the Unicode data table. That table can
8827  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8828  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]];
8829  function isStrongRTLChar(charCode) {
8830    for (var i = 0; i < strongRTLRanges.length; i++) {
8831      var currentRange = strongRTLRanges[i];
8832      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8833        return true;
8834      }
8835    }
8836    return false;
8837  }
8838  function determineBidi(cueDiv) {
8839    var nodeStack = [],
8840      text = "",
8841      charCode;
8842    if (!cueDiv || !cueDiv.childNodes) {
8843      return "ltr";
8844    }
8845    function pushNodes(nodeStack, node) {
8846      for (var i = node.childNodes.length - 1; i >= 0; i--) {
8847        nodeStack.push(node.childNodes[i]);
8848      }
8849    }
8850    function nextTextNode(nodeStack) {
8851      if (!nodeStack || !nodeStack.length) {
8852        return null;
8853      }
8854      var node = nodeStack.pop(),
8855        text = node.textContent || node.innerText;
8856      if (text) {
8857        // TODO: This should match all unicode type B characters (paragraph
8858        // separator characters). See issue #115.
8859        var m = text.match(/^.*(\n|\r)/);
8860        if (m) {
8861          nodeStack.length = 0;
8862          return m[0];
8863        }
8864        return text;
8865      }
8866      if (node.tagName === "ruby") {
8867        return nextTextNode(nodeStack);
8868      }
8869      if (node.childNodes) {
8870        pushNodes(nodeStack, node);
8871        return nextTextNode(nodeStack);
8872      }
8873    }
8874    pushNodes(nodeStack, cueDiv);
8875    while (text = nextTextNode(nodeStack)) {
8876      for (var i = 0; i < text.length; i++) {
8877        charCode = text.charCodeAt(i);
8878        if (isStrongRTLChar(charCode)) {
8879          return "rtl";
8880        }
8881      }
8882    }
8883    return "ltr";
8884  }
8885  function computeLinePos(cue) {
8886    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8887      return cue.line;
8888    }
8889    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8890      return -1;
8891    }
8892    var track = cue.track,
8893      trackList = track.textTrackList,
8894      count = 0;
8895    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8896      if (trackList[i].mode === "showing") {
8897        count++;
8898      }
8899    }
8900    return ++count * -1;
8901  }
8902  function StyleBox() {}
8903
8904  // Apply styles to a div. If there is no div passed then it defaults to the
8905  // div on 'this'.
8906  StyleBox.prototype.applyStyles = function (styles, div) {
8907    div = div || this.div;
8908    for (var prop in styles) {
8909      if (styles.hasOwnProperty(prop)) {
8910        div.style[prop] = styles[prop];
8911      }
8912    }
8913  };
8914  StyleBox.prototype.formatStyle = function (val, unit) {
8915    return val === 0 ? 0 : val + unit;
8916  };
8917
8918  // Constructs the computed display state of the cue (a div). Places the div
8919  // into the overlay which should be a block level element (usually a div).
8920  function CueStyleBox(window, cue, styleOptions) {
8921    StyleBox.call(this);
8922    this.cue = cue;
8923
8924    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
8925    // have inline positioning and will function as the cue background box.
8926    this.cueDiv = parseContent(window, cue.text);
8927    var styles = {
8928      color: "rgba(255, 255, 255, 1)",
8929      backgroundColor: "rgba(0, 0, 0, 0.8)",
8930      position: "relative",
8931      left: 0,
8932      right: 0,
8933      top: 0,
8934      bottom: 0,
8935      display: "inline",
8936      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8937      unicodeBidi: "plaintext"
8938    };
8939    this.applyStyles(styles, this.cueDiv);
8940
8941    // Create an absolutely positioned div that will be used to position the cue
8942    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
8943    // mirrors of them except middle instead of center on Safari.
8944    this.div = window.document.createElement("div");
8945    styles = {
8946      direction: determineBidi(this.cueDiv),
8947      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8948      unicodeBidi: "plaintext",
8949      textAlign: cue.align === "middle" ? "center" : cue.align,
8950      font: styleOptions.font,
8951      whiteSpace: "pre-line",
8952      position: "absolute"
8953    };
8954    this.applyStyles(styles);
8955    this.div.appendChild(this.cueDiv);
8956
8957    // Calculate the distance from the reference edge of the viewport to the text
8958    // position of the cue box. The reference edge will be resolved later when
8959    // the box orientation styles are applied.
8960    var textPos = 0;
8961    switch (cue.positionAlign) {
8962      case "start":
8963      case "line-left":
8964        textPos = cue.position;
8965        break;
8966      case "center":
8967        textPos = cue.position - cue.size / 2;
8968        break;
8969      case "end":
8970      case "line-right":
8971        textPos = cue.position - cue.size;
8972        break;
8973    }
8974
8975    // Horizontal box orientation; textPos is the distance from the left edge of the
8976    // area to the left edge of the box and cue.size is the distance extending to
8977    // the right from there.
8978    if (cue.vertical === "") {
8979      this.applyStyles({
8980        left: this.formatStyle(textPos, "%"),
8981        width: this.formatStyle(cue.size, "%")
8982      });
8983      // Vertical box orientation; textPos is the distance from the top edge of the
8984      // area to the top edge of the box and cue.size is the height extending
8985      // downwards from there.
8986    } else {
8987      this.applyStyles({
8988        top: this.formatStyle(textPos, "%"),
8989        height: this.formatStyle(cue.size, "%")
8990      });
8991    }
8992    this.move = function (box) {
8993      this.applyStyles({
8994        top: this.formatStyle(box.top, "px"),
8995        bottom: this.formatStyle(box.bottom, "px"),
8996        left: this.formatStyle(box.left, "px"),
8997        right: this.formatStyle(box.right, "px"),
8998        height: this.formatStyle(box.height, "px"),
8999        width: this.formatStyle(box.width, "px")
9000      });
9001    };
9002  }
9003  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9004  CueStyleBox.prototype.constructor = CueStyleBox;
9005
9006  // Represents the co-ordinates of an Element in a way that we can easily
9007  // compute things with such as if it overlaps or intersects with another Element.
9008  // Can initialize it with either a StyleBox or another BoxPosition.
9009  function BoxPosition(obj) {
9010    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9011    // was passed in and we need to copy the results of 'getBoundingClientRect'
9012    // as the object returned is readonly. All co-ordinate values are in reference
9013    // to the viewport origin (top left).
9014    var lh, height, width, top;
9015    if (obj.div) {
9016      height = obj.div.offsetHeight;
9017      width = obj.div.offsetWidth;
9018      top = obj.div.offsetTop;
9019      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9020      obj = obj.div.getBoundingClientRect();
9021      // In certain cases the outter div will be slightly larger then the sum of
9022      // the inner div's lines. This could be due to bold text, etc, on some platforms.
9023      // In this case we should get the average line height and use that. This will
9024      // result in the desired behaviour.
9025      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9026    }
9027    this.left = obj.left;
9028    this.right = obj.right;
9029    this.top = obj.top || top;
9030    this.height = obj.height || height;
9031    this.bottom = obj.bottom || top + (obj.height || height);
9032    this.width = obj.width || width;
9033    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9034  }
9035
9036  // Move the box along a particular axis. Optionally pass in an amount to move
9037  // the box. If no amount is passed then the default is the line height of the
9038  // box.
9039  BoxPosition.prototype.move = function (axis, toMove) {
9040    toMove = toMove !== undefined ? toMove : this.lineHeight;
9041    switch (axis) {
9042      case "+x":
9043        this.left += toMove;
9044        this.right += toMove;
9045        break;
9046      case "-x":
9047        this.left -= toMove;
9048        this.right -= toMove;
9049        break;
9050      case "+y":
9051        this.top += toMove;
9052        this.bottom += toMove;
9053        break;
9054      case "-y":
9055        this.top -= toMove;
9056        this.bottom -= toMove;
9057        break;
9058    }
9059  };
9060
9061  // Check if this box overlaps another box, b2.
9062  BoxPosition.prototype.overlaps = function (b2) {
9063    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9064  };
9065
9066  // Check if this box overlaps any other boxes in boxes.
9067  BoxPosition.prototype.overlapsAny = function (boxes) {
9068    for (var i = 0; i < boxes.length; i++) {
9069      if (this.overlaps(boxes[i])) {
9070        return true;
9071      }
9072    }
9073    return false;
9074  };
9075
9076  // Check if this box is within another box.
9077  BoxPosition.prototype.within = function (container) {
9078    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9079  };
9080
9081  // Check if this box is entirely within the container or it is overlapping
9082  // on the edge opposite of the axis direction passed. For example, if "+x" is
9083  // passed and the box is overlapping on the left edge of the container, then
9084  // return true.
9085  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9086    switch (axis) {
9087      case "+x":
9088        return this.left < container.left;
9089      case "-x":
9090        return this.right > container.right;
9091      case "+y":
9092        return this.top < container.top;
9093      case "-y":
9094        return this.bottom > container.bottom;
9095    }
9096  };
9097
9098  // Find the percentage of the area that this box is overlapping with another
9099  // box.
9100  BoxPosition.prototype.intersectPercentage = function (b2) {
9101    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9102      y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9103      intersectArea = x * y;
9104    return intersectArea / (this.height * this.width);
9105  };
9106
9107  // Convert the positions from this box to CSS compatible positions using
9108  // the reference container's positions. This has to be done because this
9109  // box's positions are in reference to the viewport origin, whereas, CSS
9110  // values are in referecne to their respective edges.
9111  BoxPosition.prototype.toCSSCompatValues = function (reference) {
9112    return {
9113      top: this.top - reference.top,
9114      bottom: reference.bottom - this.bottom,
9115      left: this.left - reference.left,
9116      right: reference.right - this.right,
9117      height: this.height,
9118      width: this.width
9119    };
9120  };
9121
9122  // Get an object that represents the box's position without anything extra.
9123  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9124  BoxPosition.getSimpleBoxPosition = function (obj) {
9125    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9126    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9127    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9128    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9129    var ret = {
9130      left: obj.left,
9131      right: obj.right,
9132      top: obj.top || top,
9133      height: obj.height || height,
9134      bottom: obj.bottom || top + (obj.height || height),
9135      width: obj.width || width
9136    };
9137    return ret;
9138  };
9139
9140  // Move a StyleBox to its specified, or next best, position. The containerBox
9141  // is the box that contains the StyleBox, such as a div. boxPositions are
9142  // a list of other boxes that the styleBox can't overlap with.
9143  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9144    // Find the best position for a cue box, b, on the video. The axis parameter
9145    // is a list of axis, the order of which, it will move the box along. For example:
9146    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9147    // direction. If it doesn't find a good position for it there it will then move
9148    // it along the x axis in the negative direction.
9149    function findBestPosition(b, axis) {
9150      var bestPosition,
9151        specifiedPosition = new BoxPosition(b),
9152        percentage = 1; // Highest possible so the first thing we get is better.
9153
9154      for (var i = 0; i < axis.length; i++) {
9155        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9156          b.move(axis[i]);
9157        }
9158        // We found a spot where we aren't overlapping anything. This is our
9159        // best position.
9160        if (b.within(containerBox)) {
9161          return b;
9162        }
9163        var p = b.intersectPercentage(containerBox);
9164        // If we're outside the container box less then we were on our last try
9165        // then remember this position as the best position.
9166        if (percentage > p) {
9167          bestPosition = new BoxPosition(b);
9168          percentage = p;
9169        }
9170        // Reset the box position to the specified position.
9171        b = new BoxPosition(specifiedPosition);
9172      }
9173      return bestPosition || specifiedPosition;
9174    }
9175    var boxPosition = new BoxPosition(styleBox),
9176      cue = styleBox.cue,
9177      linePos = computeLinePos(cue),
9178      axis = [];
9179
9180    // If we have a line number to align the cue to.
9181    if (cue.snapToLines) {
9182      var size;
9183      switch (cue.vertical) {
9184        case "":
9185          axis = ["+y", "-y"];
9186          size = "height";
9187          break;
9188        case "rl":
9189          axis = ["+x", "-x"];
9190          size = "width";
9191          break;
9192        case "lr":
9193          axis = ["-x", "+x"];
9194          size = "width";
9195          break;
9196      }
9197      var step = boxPosition.lineHeight,
9198        position = step * Math.round(linePos),
9199        maxPosition = containerBox[size] + step,
9200        initialAxis = axis[0];
9201
9202      // If the specified intial position is greater then the max position then
9203      // clamp the box to the amount of steps it would take for the box to
9204      // reach the max position.
9205      if (Math.abs(position) > maxPosition) {
9206        position = position < 0 ? -1 : 1;
9207        position *= Math.ceil(maxPosition / step) * step;
9208      }
9209
9210      // If computed line position returns negative then line numbers are
9211      // relative to the bottom of the video instead of the top. Therefore, we
9212      // need to increase our initial position by the length or width of the
9213      // video, depending on the writing direction, and reverse our axis directions.
9214      if (linePos < 0) {
9215        position += cue.vertical === "" ? containerBox.height : containerBox.width;
9216        axis = axis.reverse();
9217      }
9218
9219      // Move the box to the specified position. This may not be its best
9220      // position.
9221      boxPosition.move(initialAxis, position);
9222    } else {
9223      // If we have a percentage line value for the cue.
9224      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9225      switch (cue.lineAlign) {
9226        case "center":
9227          linePos -= calculatedPercentage / 2;
9228          break;
9229        case "end":
9230          linePos -= calculatedPercentage;
9231          break;
9232      }
9233
9234      // Apply initial line position to the cue box.
9235      switch (cue.vertical) {
9236        case "":
9237          styleBox.applyStyles({
9238            top: styleBox.formatStyle(linePos, "%")
9239          });
9240          break;
9241        case "rl":
9242          styleBox.applyStyles({
9243            left: styleBox.formatStyle(linePos, "%")
9244          });
9245          break;
9246        case "lr":
9247          styleBox.applyStyles({
9248            right: styleBox.formatStyle(linePos, "%")
9249          });
9250          break;
9251      }
9252      axis = ["+y", "-x", "+x", "-y"];
9253
9254      // Get the box position again after we've applied the specified positioning
9255      // to it.
9256      boxPosition = new BoxPosition(styleBox);
9257    }
9258    var bestPosition = findBestPosition(boxPosition, axis);
9259    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9260  }
9261  function WebVTT$1() {
9262    // Nothing
9263  }
9264
9265  // Helper to allow strings to be decoded instead of the default binary utf8 data.
9266  WebVTT$1.StringDecoder = function () {
9267    return {
9268      decode: function (data) {
9269        if (!data) {
9270          return "";
9271        }
9272        if (typeof data !== "string") {
9273          throw new Error("Error - expected string data.");
9274        }
9275        return decodeURIComponent(encodeURIComponent(data));
9276      }
9277    };
9278  };
9279  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9280    if (!window || !cuetext) {
9281      return null;
9282    }
9283    return parseContent(window, cuetext);
9284  };
9285  var FONT_SIZE_PERCENT = 0.05;
9286  var FONT_STYLE = "sans-serif";
9287  var CUE_BACKGROUND_PADDING = "1.5%";
9288
9289  // Runs the processing model over the cues and regions passed to it.
9290  // @param overlay A block level element (usually a div) that the computed cues
9291  //                and regions will be placed into.
9292  WebVTT$1.processCues = function (window, cues, overlay) {
9293    if (!window || !cues || !overlay) {
9294      return null;
9295    }
9296
9297    // Remove all previous children.
9298    while (overlay.firstChild) {
9299      overlay.removeChild(overlay.firstChild);
9300    }
9301    var paddedOverlay = window.document.createElement("div");
9302    paddedOverlay.style.position = "absolute";
9303    paddedOverlay.style.left = "0";
9304    paddedOverlay.style.right = "0";
9305    paddedOverlay.style.top = "0";
9306    paddedOverlay.style.bottom = "0";
9307    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
9308    overlay.appendChild(paddedOverlay);
9309
9310    // Determine if we need to compute the display states of the cues. This could
9311    // be the case if a cue's state has been changed since the last computation or
9312    // if it has not been computed yet.
9313    function shouldCompute(cues) {
9314      for (var i = 0; i < cues.length; i++) {
9315        if (cues[i].hasBeenReset || !cues[i].displayState) {
9316          return true;
9317        }
9318      }
9319      return false;
9320    }
9321
9322    // We don't need to recompute the cues' display states. Just reuse them.
9323    if (!shouldCompute(cues)) {
9324      for (var i = 0; i < cues.length; i++) {
9325        paddedOverlay.appendChild(cues[i].displayState);
9326      }
9327      return;
9328    }
9329    var boxPositions = [],
9330      containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
9331      fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
9332    var styleOptions = {
9333      font: fontSize + "px " + FONT_STYLE
9334    };
9335    (function () {
9336      var styleBox, cue;
9337      for (var i = 0; i < cues.length; i++) {
9338        cue = cues[i];
9339
9340        // Compute the intial position and styles of the cue div.
9341        styleBox = new CueStyleBox(window, cue, styleOptions);
9342        paddedOverlay.appendChild(styleBox.div);
9343
9344        // Move the cue div to it's correct line position.
9345        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
9346
9347        // Remember the computed div so that we don't have to recompute it later
9348        // if we don't have too.
9349        cue.displayState = styleBox.div;
9350        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
9351      }
9352    })();
9353  };
9354  WebVTT$1.Parser = function (window, vttjs, decoder) {
9355    if (!decoder) {
9356      decoder = vttjs;
9357      vttjs = {};
9358    }
9359    if (!vttjs) {
9360      vttjs = {};
9361    }
9362    this.window = window;
9363    this.vttjs = vttjs;
9364    this.state = "INITIAL";
9365    this.buffer = "";
9366    this.decoder = decoder || new TextDecoder("utf8");
9367    this.regionList = [];
9368  };
9369  WebVTT$1.Parser.prototype = {
9370    // If the error is a ParsingError then report it to the consumer if
9371    // possible. If it's not a ParsingError then throw it like normal.
9372    reportOrThrowError: function (e) {
9373      if (e instanceof ParsingError) {
9374        this.onparsingerror && this.onparsingerror(e);
9375      } else {
9376        throw e;
9377      }
9378    },
9379    parse: function (data) {
9380      var self = this;
9381
9382      // If there is no data then we won't decode it, but will just try to parse
9383      // whatever is in buffer already. This may occur in circumstances, for
9384      // example when flush() is called.
9385      if (data) {
9386        // Try to decode the data that we received.
9387        self.buffer += self.decoder.decode(data, {
9388          stream: true
9389        });
9390      }
9391      function collectNextLine() {
9392        var buffer = self.buffer;
9393        var pos = 0;
9394        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
9395          ++pos;
9396        }
9397        var line = buffer.substr(0, pos);
9398        // Advance the buffer early in case we fail below.
9399        if (buffer[pos] === '\r') {
9400          ++pos;
9401        }
9402        if (buffer[pos] === '\n') {
9403          ++pos;
9404        }
9405        self.buffer = buffer.substr(pos);
9406        return line;
9407      }
9408
9409      // 3.4 WebVTT region and WebVTT region settings syntax
9410      function parseRegion(input) {
9411        var settings = new Settings();
9412        parseOptions(input, function (k, v) {
9413          switch (k) {
9414            case "id":
9415              settings.set(k, v);
9416              break;
9417            case "width":
9418              settings.percent(k, v);
9419              break;
9420            case "lines":
9421              settings.integer(k, v);
9422              break;
9423            case "regionanchor":
9424            case "viewportanchor":
9425              var xy = v.split(',');
9426              if (xy.length !== 2) {
9427                break;
9428              }
9429              // We have to make sure both x and y parse, so use a temporary
9430              // settings object here.
9431              var anchor = new Settings();
9432              anchor.percent("x", xy[0]);
9433              anchor.percent("y", xy[1]);
9434              if (!anchor.has("x") || !anchor.has("y")) {
9435                break;
9436              }
9437              settings.set(k + "X", anchor.get("x"));
9438              settings.set(k + "Y", anchor.get("y"));
9439              break;
9440            case "scroll":
9441              settings.alt(k, v, ["up"]);
9442              break;
9443          }
9444        }, /=/, /\s/);
9445
9446        // Create the region, using default values for any values that were not
9447        // specified.
9448        if (settings.has("id")) {
9449          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9450          region.width = settings.get("width", 100);
9451          region.lines = settings.get("lines", 3);
9452          region.regionAnchorX = settings.get("regionanchorX", 0);
9453          region.regionAnchorY = settings.get("regionanchorY", 100);
9454          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9455          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9456          region.scroll = settings.get("scroll", "");
9457          // Register the region.
9458          self.onregion && self.onregion(region);
9459          // Remember the VTTRegion for later in case we parse any VTTCues that
9460          // reference it.
9461          self.regionList.push({
9462            id: settings.get("id"),
9463            region: region
9464          });
9465        }
9466      }
9467
9468      // draft-pantos-http-live-streaming-20
9469      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9470      // 3.5 WebVTT
9471      function parseTimestampMap(input) {
9472        var settings = new Settings();
9473        parseOptions(input, function (k, v) {
9474          switch (k) {
9475            case "MPEGT":
9476              settings.integer(k + 'S', v);
9477              break;
9478            case "LOCA":
9479              settings.set(k + 'L', parseTimeStamp(v));
9480              break;
9481          }
9482        }, /[^\d]:/, /,/);
9483        self.ontimestampmap && self.ontimestampmap({
9484          "MPEGTS": settings.get("MPEGTS"),
9485          "LOCAL": settings.get("LOCAL")
9486        });
9487      }
9488
9489      // 3.2 WebVTT metadata header syntax
9490      function parseHeader(input) {
9491        if (input.match(/X-TIMESTAMP-MAP/)) {
9492          // This line contains HLS X-TIMESTAMP-MAP metadata
9493          parseOptions(input, function (k, v) {
9494            switch (k) {
9495              case "X-TIMESTAMP-MAP":
9496                parseTimestampMap(v);
9497                break;
9498            }
9499          }, /=/);
9500        } else {
9501          parseOptions(input, function (k, v) {
9502            switch (k) {
9503              case "Region":
9504                // 3.3 WebVTT region metadata header syntax
9505                parseRegion(v);
9506                break;
9507            }
9508          }, /:/);
9509        }
9510      }
9511
9512      // 5.1 WebVTT file parsing.
9513      try {
9514        var line;
9515        if (self.state === "INITIAL") {
9516          // We can't start parsing until we have the first line.
9517          if (!/\r\n|\n/.test(self.buffer)) {
9518            return this;
9519          }
9520          line = collectNextLine();
9521          var m = line.match(/^WEBVTT([ \t].*)?$/);
9522          if (!m || !m[0]) {
9523            throw new ParsingError(ParsingError.Errors.BadSignature);
9524          }
9525          self.state = "HEADER";
9526        }
9527        var alreadyCollectedLine = false;
9528        while (self.buffer) {
9529          // We can't parse a line until we have the full line.
9530          if (!/\r\n|\n/.test(self.buffer)) {
9531            return this;
9532          }
9533          if (!alreadyCollectedLine) {
9534            line = collectNextLine();
9535          } else {
9536            alreadyCollectedLine = false;
9537          }
9538          switch (self.state) {
9539            case "HEADER":
9540              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9541              if (/:/.test(line)) {
9542                parseHeader(line);
9543              } else if (!line) {
9544                // An empty line terminates the header and starts the body (cues).
9545                self.state = "ID";
9546              }
9547              continue;
9548            case "NOTE":
9549              // Ignore NOTE blocks.
9550              if (!line) {
9551                self.state = "ID";
9552              }
9553              continue;
9554            case "ID":
9555              // Check for the start of NOTE blocks.
9556              if (/^NOTE($|[ \t])/.test(line)) {
9557                self.state = "NOTE";
9558                break;
9559              }
9560              // 19-29 - Allow any number of line terminators, then initialize new cue values.
9561              if (!line) {
9562                continue;
9563              }
9564              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9565              // Safari still uses the old middle value and won't accept center
9566              try {
9567                self.cue.align = "center";
9568              } catch (e) {
9569                self.cue.align = "middle";
9570              }
9571              self.state = "CUE";
9572              // 30-39 - Check if self line contains an optional identifier or timing data.
9573              if (line.indexOf("-->") === -1) {
9574                self.cue.id = line;
9575                continue;
9576              }
9577            // Process line as start of a cue.
9578            /*falls through*/
9579            case "CUE":
9580              // 40 - Collect cue timings and settings.
9581              try {
9582                parseCue(line, self.cue, self.regionList);
9583              } catch (e) {
9584                self.reportOrThrowError(e);
9585                // In case of an error ignore rest of the cue.
9586                self.cue = null;
9587                self.state = "BADCUE";
9588                continue;
9589              }
9590              self.state = "CUETEXT";
9591              continue;
9592            case "CUETEXT":
9593              var hasSubstring = line.indexOf("-->") !== -1;
9594              // 34 - If we have an empty line then report the cue.
9595              // 35 - If we have the special substring '-->' then report the cue,
9596              // but do not collect the line as we need to process the current
9597              // one as a new cue.
9598              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9599                // We are done parsing self cue.
9600                self.oncue && self.oncue(self.cue);
9601                self.cue = null;
9602                self.state = "ID";
9603                continue;
9604              }
9605              if (self.cue.text) {
9606                self.cue.text += "\n";
9607              }
9608              self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
9609              continue;
9610            case "BADCUE":
9611              // BADCUE
9612              // 54-62 - Collect and discard the remaining cue.
9613              if (!line) {
9614                self.state = "ID";
9615              }
9616              continue;
9617          }
9618        }
9619      } catch (e) {
9620        self.reportOrThrowError(e);
9621
9622        // If we are currently parsing a cue, report what we have.
9623        if (self.state === "CUETEXT" && self.cue && self.oncue) {
9624          self.oncue(self.cue);
9625        }
9626        self.cue = null;
9627        // Enter BADWEBVTT state if header was not parsed correctly otherwise
9628        // another exception occurred so enter BADCUE state.
9629        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9630      }
9631      return this;
9632    },
9633    flush: function () {
9634      var self = this;
9635      try {
9636        // Finish decoding the stream.
9637        self.buffer += self.decoder.decode();
9638        // Synthesize the end of the current cue or region.
9639        if (self.cue || self.state === "HEADER") {
9640          self.buffer += "\n\n";
9641          self.parse();
9642        }
9643        // If we've flushed, parsed, and we're still on the INITIAL state then
9644        // that means we don't have enough of the stream to parse the first
9645        // line.
9646        if (self.state === "INITIAL") {
9647          throw new ParsingError(ParsingError.Errors.BadSignature);
9648        }
9649      } catch (e) {
9650        self.reportOrThrowError(e);
9651      }
9652      self.onflush && self.onflush();
9653      return this;
9654    }
9655  };
9656  var vtt = WebVTT$1;
9657
9658  /**
9659   * Copyright 2013 vtt.js Contributors
9660   *
9661   * Licensed under the Apache License, Version 2.0 (the "License");
9662   * you may not use this file except in compliance with the License.
9663   * You may obtain a copy of the License at
9664   *
9665   *   http://www.apache.org/licenses/LICENSE-2.0
9666   *
9667   * Unless required by applicable law or agreed to in writing, software
9668   * distributed under the License is distributed on an "AS IS" BASIS,
9669   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9670   * See the License for the specific language governing permissions and
9671   * limitations under the License.
9672   */
9673
9674  var autoKeyword = "auto";
9675  var directionSetting = {
9676    "": 1,
9677    "lr": 1,
9678    "rl": 1
9679  };
9680  var alignSetting = {
9681    "start": 1,
9682    "center": 1,
9683    "end": 1,
9684    "left": 1,
9685    "right": 1,
9686    "auto": 1,
9687    "line-left": 1,
9688    "line-right": 1
9689  };
9690  function findDirectionSetting(value) {
9691    if (typeof value !== "string") {
9692      return false;
9693    }
9694    var dir = directionSetting[value.toLowerCase()];
9695    return dir ? value.toLowerCase() : false;
9696  }
9697  function findAlignSetting(value) {
9698    if (typeof value !== "string") {
9699      return false;
9700    }
9701    var align = alignSetting[value.toLowerCase()];
9702    return align ? value.toLowerCase() : false;
9703  }
9704  function VTTCue(startTime, endTime, text) {
9705    /**
9706     * Shim implementation specific properties. These properties are not in
9707     * the spec.
9708     */
9709
9710    // Lets us know when the VTTCue's data has changed in such a way that we need
9711    // to recompute its display state. This lets us compute its display state
9712    // lazily.
9713    this.hasBeenReset = false;
9714
9715    /**
9716     * VTTCue and TextTrackCue properties
9717     * http://dev.w3.org/html5/webvtt/#vttcue-interface
9718     */
9719
9720    var _id = "";
9721    var _pauseOnExit = false;
9722    var _startTime = startTime;
9723    var _endTime = endTime;
9724    var _text = text;
9725    var _region = null;
9726    var _vertical = "";
9727    var _snapToLines = true;
9728    var _line = "auto";
9729    var _lineAlign = "start";
9730    var _position = "auto";
9731    var _positionAlign = "auto";
9732    var _size = 100;
9733    var _align = "center";
9734    Object.defineProperties(this, {
9735      "id": {
9736        enumerable: true,
9737        get: function () {
9738          return _id;
9739        },
9740        set: function (value) {
9741          _id = "" + value;
9742        }
9743      },
9744      "pauseOnExit": {
9745        enumerable: true,
9746        get: function () {
9747          return _pauseOnExit;
9748        },
9749        set: function (value) {
9750          _pauseOnExit = !!value;
9751        }
9752      },
9753      "startTime": {
9754        enumerable: true,
9755        get: function () {
9756          return _startTime;
9757        },
9758        set: function (value) {
9759          if (typeof value !== "number") {
9760            throw new TypeError("Start time must be set to a number.");
9761          }
9762          _startTime = value;
9763          this.hasBeenReset = true;
9764        }
9765      },
9766      "endTime": {
9767        enumerable: true,
9768        get: function () {
9769          return _endTime;
9770        },
9771        set: function (value) {
9772          if (typeof value !== "number") {
9773            throw new TypeError("End time must be set to a number.");
9774          }
9775          _endTime = value;
9776          this.hasBeenReset = true;
9777        }
9778      },
9779      "text": {
9780        enumerable: true,
9781        get: function () {
9782          return _text;
9783        },
9784        set: function (value) {
9785          _text = "" + value;
9786          this.hasBeenReset = true;
9787        }
9788      },
9789      "region": {
9790        enumerable: true,
9791        get: function () {
9792          return _region;
9793        },
9794        set: function (value) {
9795          _region = value;
9796          this.hasBeenReset = true;
9797        }
9798      },
9799      "vertical": {
9800        enumerable: true,
9801        get: function () {
9802          return _vertical;
9803        },
9804        set: function (value) {
9805          var setting = findDirectionSetting(value);
9806          // Have to check for false because the setting an be an empty string.
9807          if (setting === false) {
9808            throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
9809          }
9810          _vertical = setting;
9811          this.hasBeenReset = true;
9812        }
9813      },
9814      "snapToLines": {
9815        enumerable: true,
9816        get: function () {
9817          return _snapToLines;
9818        },
9819        set: function (value) {
9820          _snapToLines = !!value;
9821          this.hasBeenReset = true;
9822        }
9823      },
9824      "line": {
9825        enumerable: true,
9826        get: function () {
9827          return _line;
9828        },
9829        set: function (value) {
9830          if (typeof value !== "number" && value !== autoKeyword) {
9831            throw new SyntaxError("Line: an invalid number or illegal string was specified.");
9832          }
9833          _line = value;
9834          this.hasBeenReset = true;
9835        }
9836      },
9837      "lineAlign": {
9838        enumerable: true,
9839        get: function () {
9840          return _lineAlign;
9841        },
9842        set: function (value) {
9843          var setting = findAlignSetting(value);
9844          if (!setting) {
9845            console.warn("lineAlign: an invalid or illegal string was specified.");
9846          } else {
9847            _lineAlign = setting;
9848            this.hasBeenReset = true;
9849          }
9850        }
9851      },
9852      "position": {
9853        enumerable: true,
9854        get: function () {
9855          return _position;
9856        },
9857        set: function (value) {
9858          if (value < 0 || value > 100) {
9859            throw new Error("Position must be between 0 and 100.");
9860          }
9861          _position = value;
9862          this.hasBeenReset = true;
9863        }
9864      },
9865      "positionAlign": {
9866        enumerable: true,
9867        get: function () {
9868          return _positionAlign;
9869        },
9870        set: function (value) {
9871          var setting = findAlignSetting(value);
9872          if (!setting) {
9873            console.warn("positionAlign: an invalid or illegal string was specified.");
9874          } else {
9875            _positionAlign = setting;
9876            this.hasBeenReset = true;
9877          }
9878        }
9879      },
9880      "size": {
9881        enumerable: true,
9882        get: function () {
9883          return _size;
9884        },
9885        set: function (value) {
9886          if (value < 0 || value > 100) {
9887            throw new Error("Size must be between 0 and 100.");
9888          }
9889          _size = value;
9890          this.hasBeenReset = true;
9891        }
9892      },
9893      "align": {
9894        enumerable: true,
9895        get: function () {
9896          return _align;
9897        },
9898        set: function (value) {
9899          var setting = findAlignSetting(value);
9900          if (!setting) {
9901            throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
9902          }
9903          _align = setting;
9904          this.hasBeenReset = true;
9905        }
9906      }
9907    });
9908
9909    /**
9910     * Other <track> spec defined properties
9911     */
9912
9913    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
9914    this.displayState = undefined;
9915  }
9916
9917  /**
9918   * VTTCue methods
9919   */
9920
9921  VTTCue.prototype.getCueAsHTML = function () {
9922    // Assume WebVTT.convertCueToDOMTree is on the global.
9923    return WebVTT.convertCueToDOMTree(window, this.text);
9924  };
9925  var vttcue = VTTCue;
9926
9927  /**
9928   * Copyright 2013 vtt.js Contributors
9929   *
9930   * Licensed under the Apache License, Version 2.0 (the "License");
9931   * you may not use this file except in compliance with the License.
9932   * You may obtain a copy of the License at
9933   *
9934   *   http://www.apache.org/licenses/LICENSE-2.0
9935   *
9936   * Unless required by applicable law or agreed to in writing, software
9937   * distributed under the License is distributed on an "AS IS" BASIS,
9938   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9939   * See the License for the specific language governing permissions and
9940   * limitations under the License.
9941   */
9942
9943  var scrollSetting = {
9944    "": true,
9945    "up": true
9946  };
9947  function findScrollSetting(value) {
9948    if (typeof value !== "string") {
9949      return false;
9950    }
9951    var scroll = scrollSetting[value.toLowerCase()];
9952    return scroll ? value.toLowerCase() : false;
9953  }
9954  function isValidPercentValue(value) {
9955    return typeof value === "number" && value >= 0 && value <= 100;
9956  }
9957
9958  // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
9959  function VTTRegion() {
9960    var _width = 100;
9961    var _lines = 3;
9962    var _regionAnchorX = 0;
9963    var _regionAnchorY = 100;
9964    var _viewportAnchorX = 0;
9965    var _viewportAnchorY = 100;
9966    var _scroll = "";
9967    Object.defineProperties(this, {
9968      "width": {
9969        enumerable: true,
9970        get: function () {
9971          return _width;
9972        },
9973        set: function (value) {
9974          if (!isValidPercentValue(value)) {
9975            throw new Error("Width must be between 0 and 100.");
9976          }
9977          _width = value;
9978        }
9979      },
9980      "lines": {
9981        enumerable: true,
9982        get: function () {
9983          return _lines;
9984        },
9985        set: function (value) {
9986          if (typeof value !== "number") {
9987            throw new TypeError("Lines must be set to a number.");
9988          }
9989          _lines = value;
9990        }
9991      },
9992      "regionAnchorY": {
9993        enumerable: true,
9994        get: function () {
9995          return _regionAnchorY;
9996        },
9997        set: function (value) {
9998          if (!isValidPercentValue(value)) {
9999            throw new Error("RegionAnchorX must be between 0 and 100.");
10000          }
10001          _regionAnchorY = value;
10002        }
10003      },
10004      "regionAnchorX": {
10005        enumerable: true,
10006        get: function () {
10007          return _regionAnchorX;
10008        },
10009        set: function (value) {
10010          if (!isValidPercentValue(value)) {
10011            throw new Error("RegionAnchorY must be between 0 and 100.");
10012          }
10013          _regionAnchorX = value;
10014        }
10015      },
10016      "viewportAnchorY": {
10017        enumerable: true,
10018        get: function () {
10019          return _viewportAnchorY;
10020        },
10021        set: function (value) {
10022          if (!isValidPercentValue(value)) {
10023            throw new Error("ViewportAnchorY must be between 0 and 100.");
10024          }
10025          _viewportAnchorY = value;
10026        }
10027      },
10028      "viewportAnchorX": {
10029        enumerable: true,
10030        get: function () {
10031          return _viewportAnchorX;
10032        },
10033        set: function (value) {
10034          if (!isValidPercentValue(value)) {
10035            throw new Error("ViewportAnchorX must be between 0 and 100.");
10036          }
10037          _viewportAnchorX = value;
10038        }
10039      },
10040      "scroll": {
10041        enumerable: true,
10042        get: function () {
10043          return _scroll;
10044        },
10045        set: function (value) {
10046          var setting = findScrollSetting(value);
10047          // Have to check for false as an empty string is a legal value.
10048          if (setting === false) {
10049            console.warn("Scroll: an invalid or illegal string was specified.");
10050          } else {
10051            _scroll = setting;
10052          }
10053        }
10054      }
10055    });
10056  }
10057  var vttregion = VTTRegion;
10058
10059  var browserIndex = createCommonjsModule(function (module) {
10060    /**
10061     * Copyright 2013 vtt.js Contributors
10062     *
10063     * Licensed under the Apache License, Version 2.0 (the "License");
10064     * you may not use this file except in compliance with the License.
10065     * You may obtain a copy of the License at
10066     *
10067     *   http://www.apache.org/licenses/LICENSE-2.0
10068     *
10069     * Unless required by applicable law or agreed to in writing, software
10070     * distributed under the License is distributed on an "AS IS" BASIS,
10071     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10072     * See the License for the specific language governing permissions and
10073     * limitations under the License.
10074     */
10075
10076    // Default exports for Node. Export the extended versions of VTTCue and
10077    // VTTRegion in Node since we likely want the capability to convert back and
10078    // forth between JSON. If we don't then it's not that big of a deal since we're
10079    // off browser.
10080
10081    var vttjs = module.exports = {
10082      WebVTT: vtt,
10083      VTTCue: vttcue,
10084      VTTRegion: vttregion
10085    };
10086    window_1.vttjs = vttjs;
10087    window_1.WebVTT = vttjs.WebVTT;
10088    var cueShim = vttjs.VTTCue;
10089    var regionShim = vttjs.VTTRegion;
10090    var nativeVTTCue = window_1.VTTCue;
10091    var nativeVTTRegion = window_1.VTTRegion;
10092    vttjs.shim = function () {
10093      window_1.VTTCue = cueShim;
10094      window_1.VTTRegion = regionShim;
10095    };
10096    vttjs.restore = function () {
10097      window_1.VTTCue = nativeVTTCue;
10098      window_1.VTTRegion = nativeVTTRegion;
10099    };
10100    if (!window_1.VTTCue) {
10101      vttjs.shim();
10102    }
10103  });
10104  browserIndex.WebVTT;
10105  browserIndex.VTTCue;
10106  browserIndex.VTTRegion;
10107
10108  /**
10109   * @file tech.js
10110   */
10111
10112  /**
10112
10113   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10114   * that just contains the src url alone.
10115   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10116     * `var SourceString = 'http://example.com/some-video.mp4';`
10117   *
10118   * @typedef {Object|string} SourceObject
10119   *
10120   * @property {string} src
10121   *           The url to the source
10122   *
10123   * @property {string} type
10124   *           The mime type of the source
10125   */
10126
10127  /**
10128   * A function used by {@link Tech} to create a new {@link TextTrack}.
10129   *
10130   * @private
10131   *
10132   * @param {Tech} self
10133   *        An instance of the Tech class.
10134   *
10135   * @param {string} kind
10136   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10137   *
10138   * @param {string} [label]
10139   *        Label to identify the text track
10140   *
10141   * @param {string} [language]
10142   *        Two letter language abbreviation
10143   *
10144   * @param {Object} [options={}]
10145   *        An object with additional text track options
10146   *
10147   * @return {TextTrack}
10148   *          The text track that was created.
10149   */
10150  function createTrackHelper(self, kind, label, language, options = {}) {
10151    const tracks = self.textTracks();
10152    options.kind = kind;
10153    if (label) {
10154      options.label = label;
10155    }
10156    if (language) {
10157      options.language = language;
10158    }
10159    options.tech = self;
10160    const track = new ALL.text.TrackClass(options);
10161    tracks.addTrack(track);
10162    return track;
10163  }
10164
10165  /**
10166   * This is the base class for media playback technology controllers, such as
10167   * {@link HTML5}
10168   *
10169   * @extends Component
10170   */
10171  class Tech extends Component$1 {
10172    /**
10173    * Create an instance of this Tech.
10174    *
10175    * @param {Object} [options]
10176    *        The key/value store of player options.
10177    *
10178    * @param {Function} [ready]
10179    *        Callback function to call when the `HTML5` Tech is ready.
10180    */
10181    constructor(options = {}, ready = function () {}) {
10182      // we don't want the tech to report user activity automatically.
vendor: 11,419 bytes, lines 10183-10554
10183      // This is done manually in addControlsListeners
10184      options.reportTouchActivity = false;
10185      super(null, options, ready);
10186      this.onDurationChange_ = e => this.onDurationChange(e);
10187      this.trackProgress_ = e => this.trackProgress(e);
10188      this.trackCurrentTime_ = e => this.trackCurrentTime(e);
10189      this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e);
10190      this.disposeSourceHandler_ = e => this.disposeSourceHandler(e);
10191      this.queuedHanders_ = new Set();
10192
10193      // keep track of whether the current source has played at all to
10194      // implement a very limited played()
10195      this.hasStarted_ = false;
10196      this.on('playing', function () {
10197        this.hasStarted_ = true;
10198      });
10199      this.on('loadstart', function () {
10200        this.hasStarted_ = false;
10201      });
10202      ALL.names.forEach(name => {
10203        const props = ALL[name];
10204        if (options && options[props.getterName]) {
10205          this[props.privateName] = options[props.getterName];
10206        }
10207      });
10208
10209      // Manually track progress in cases where the browser/tech doesn't report it.
10210      if (!this.featuresProgressEvents) {
10211        this.manualProgressOn();
10212      }
10213
10214      // Manually track timeupdates in cases where the browser/tech doesn't report it.
10215      if (!this.featuresTimeupdateEvents) {
10216        this.manualTimeUpdatesOn();
10217      }
10218      ['Text', 'Audio', 'Video'].forEach(track => {
10219        if (options[`native${track}Tracks`] === false) {
10220          this[`featuresNative${track}Tracks`] = false;
10221        }
10222      });
10223      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
10224        this.featuresNativeTextTracks = false;
10225      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
10226        this.featuresNativeTextTracks = true;
10227      }
10228      if (!this.featuresNativeTextTracks) {
10229        this.emulateTextTracks();
10230      }
10231      this.preloadTextTracks = options.preloadTextTracks !== false;
10232      this.autoRemoteTextTracks_ = new ALL.text.ListClass();
10233      this.initTrackListeners();
10234
10235      // Turn on component tap events only if not using native controls
10236      if (!options.nativeControlsForTouch) {
10237        this.emitTapEvents();
10238      }
10239      if (this.constructor) {
10240        this.name_ = this.constructor.name || 'Unknown Tech';
10241      }
10242    }
10243
10244    /**
10245     * A special function to trigger source set in a way that will allow player
10246     * to re-trigger if the player or tech are not ready yet.
10247     *
10248     * @fires Tech#sourceset
10249     * @param {string} src The source string at the time of the source changing.
10250     */
10251    triggerSourceset(src) {
10252      if (!this.isReady_) {
10253        // on initial ready we have to trigger source set
10254        // 1ms after ready so that player can watch for it.
10255        this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
10256      }
10257
10258      /**
10259       * Fired when the source is set on the tech causing the media element
10260       * to reload.
10261       *
10262       * @see {@link Player#event:sourceset}
10263       * @event Tech#sourceset
10264       * @type {Event}
10265       */
10266      this.trigger({
10267        src,
10268        type: 'sourceset'
10269      });
10270    }
10271
10272    /* Fallbacks for unsupported event types
10273    ================================================================================ */
10274
10275    /**
10276     * Polyfill the `progress` event for browsers that don't support it natively.
10277     *
10278     * @see {@link Tech#trackProgress}
10279     */
10280    manualProgressOn() {
10281      this.on('durationchange', this.onDurationChange_);
10282      this.manualProgress = true;
10283
10284      // Trigger progress watching when a source begins loading
10285      this.one('ready', this.trackProgress_);
10286    }
10287
10288    /**
10289     * Turn off the polyfill for `progress` events that was created in
10290     * {@link Tech#manualProgressOn}
10291     */
10292    manualProgressOff() {
10293      this.manualProgress = false;
10294      this.stopTrackingProgress();
10295      this.off('durationchange', this.onDurationChange_);
10296    }
10297
10298    /**
10299     * This is used to trigger a `progress` event when the buffered percent changes. It
10300     * sets an interval function that will be called every 500 milliseconds to check if the
10301     * buffer end percent has changed.
10302     *
10303     * > This function is called by {@link Tech#manualProgressOn}
10304     *
10305     * @param {Event} event
10306     *        The `ready` event that caused this to run.
10307     *
10308     * @listens Tech#ready
10309     * @fires Tech#progress
10310     */
10311    trackProgress(event) {
10312      this.stopTrackingProgress();
10313      this.progressInterval = this.setInterval(bind_(this, function () {
10314        // Don't trigger unless buffered amount is greater than last time
10315
10316        const numBufferedPercent = this.bufferedPercent();
10317        if (this.bufferedPercent_ !== numBufferedPercent) {
10318          /**
10319           * See {@link Player#progress}
10320           *
10321           * @event Tech#progress
10322           * @type {Event}
10323           */
10324          this.trigger('progress');
10325        }
10326        this.bufferedPercent_ = numBufferedPercent;
10327        if (numBufferedPercent === 1) {
10328          this.stopTrackingProgress();
10329        }
10330      }), 500);
10331    }
10332
10333    /**
10334     * Update our internal duration on a `durationchange` event by calling
10335     * {@link Tech#duration}.
10336     *
10337     * @param {Event} event
10338     *        The `durationchange` event that caused this to run.
10339     *
10340     * @listens Tech#durationchange
10341     */
10342    onDurationChange(event) {
10343      this.duration_ = this.duration();
10344    }
10345
10346    /**
10347     * Get and create a `TimeRange` object for buffering.
10348     *
10349     * @return { import('../utils/time').TimeRange }
10350     *         The time range object that was created.
10351     */
10352    buffered() {
10353      return createTimeRanges$1(0, 0);
10354    }
10355
10356    /**
10357     * Get the percentage of the current video that is currently buffered.
10358     *
10359     * @return {number}
10360     *         A number from 0 to 1 that represents the decimal percentage of the
10361     *         video that is buffered.
10362     *
10363     */
10364    bufferedPercent() {
10365      return bufferedPercent(this.buffered(), this.duration_);
10366    }
10367
10368    /**
10369     * Turn off the polyfill for `progress` events that was created in
10370     * {@link Tech#manualProgressOn}
10371     * Stop manually tracking progress events by clearing the interval that was set in
10372     * {@link Tech#trackProgress}.
10373     */
10374    stopTrackingProgress() {
10375      this.clearInterval(this.progressInterval);
10376    }
10377
10378    /**
10379     * Polyfill the `timeupdate` event for browsers that don't support it.
10380     *
10381     * @see {@link Tech#trackCurrentTime}
10382     */
10383    manualTimeUpdatesOn() {
10384      this.manualTimeUpdates = true;
10385      this.on('play', this.trackCurrentTime_);
10386      this.on('pause', this.stopTrackingCurrentTime_);
10387    }
10388
10389    /**
10390     * Turn off the polyfill for `timeupdate` events that was created in
10391     * {@link Tech#manualTimeUpdatesOn}
10392     */
10393    manualTimeUpdatesOff() {
10394      this.manualTimeUpdates = false;
10395      this.stopTrackingCurrentTime();
10396      this.off('play', this.trackCurrentTime_);
10397      this.off('pause', this.stopTrackingCurrentTime_);
10398    }
10399
10400    /**
10401     * Sets up an interval function to track current time and trigger `timeupdate` every
10402     * 250 milliseconds.
10403     *
10404     * @listens Tech#play
10405     * @triggers Tech#timeupdate
10406     */
10407    trackCurrentTime() {
10408      if (this.currentTimeInterval) {
10409        this.stopTrackingCurrentTime();
10410      }
10411      this.currentTimeInterval = this.setInterval(function () {
10412        /**
10413         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10414         *
10415         * @event Tech#timeupdate
10416         * @type {Event}
10417         */
10418        this.trigger({
10419          type: 'timeupdate',
10420          target: this,
10421          manuallyTriggered: true
10422        });
10423
10424        // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10425      }, 250);
10426    }
10427
10428    /**
10429     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10430     * `timeupdate` event is no longer triggered.
10431     *
10432     * @listens {Tech#pause}
10433     */
10434    stopTrackingCurrentTime() {
10435      this.clearInterval(this.currentTimeInterval);
10436
10437      // #1002 - if the video ends right before the next timeupdate would happen,
10438      // the progress bar won't make it all the way to the end
10439      this.trigger({
10440        type: 'timeupdate',
10441        target: this,
10442        manuallyTriggered: true
10443      });
10444    }
10445
10446    /**
10447     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10448     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10449     *
10450     * @fires Component#dispose
10451     */
10452    dispose() {
10453      // clear out all tracks because we can't reuse them between techs
10454      this.clearTracks(NORMAL.names);
10455
10456      // Turn off any manual progress or timeupdate tracking
10457      if (this.manualProgress) {
10458        this.manualProgressOff();
10459      }
10460      if (this.manualTimeUpdates) {
10461        this.manualTimeUpdatesOff();
10462      }
10463      super.dispose();
10464    }
10465
10466    /**
10467     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10468     *
10469     * > Note: Techs without source handlers should call this between sources for `video`
10470     *         & `audio` tracks. You don't want to use them between tracks!
10471     *
10472     * @param {string[]|string} types
10473     *        TrackList names to clear, valid names are `video`, `audio`, and
10474     *        `text`.
10475     */
10476    clearTracks(types) {
10477      types = [].concat(types);
10478      // clear out all tracks because we can't reuse them between techs
10479      types.forEach(type => {
10480        const list = this[`${type}Tracks`]() || [];
10481        let i = list.length;
10482        while (i--) {
10483          const track = list[i];
10484          if (type === 'text') {
10485            this.removeRemoteTextTrack(track);
10486          }
10487          list.removeTrack(track);
10488        }
10489      });
10490    }
10491
10492    /**
10493     * Remove any TextTracks added via addRemoteTextTrack that are
10494     * flagged for automatic garbage collection
10495     */
10496    cleanupAutoTextTracks() {
10497      const list = this.autoRemoteTextTracks_ || [];
10498      let i = list.length;
10499      while (i--) {
10500        const track = list[i];
10501        this.removeRemoteTextTrack(track);
10502      }
10503    }
10504
10505    /**
10506     * Reset the tech, which will removes all sources and reset the internal readyState.
10507     *
10508     * @abstract
10509     */
10510    reset() {}
10511
10512    /**
10513     * Get the value of `crossOrigin` from the tech.
10514     *
10515     * @abstract
10516     *
10517     * @see {Html5#crossOrigin}
10518     */
10519    crossOrigin() {}
10520
10521    /**
10522     * Set the value of `crossOrigin` on the tech.
10523     *
10524     * @abstract
10525     *
10526     * @param {string} crossOrigin the crossOrigin value
10527     * @see {Html5#setCrossOrigin}
10528     */
10529    setCrossOrigin() {}
10530
10531    /**
10532     * Get or set an error on the Tech.
10533     *
10534     * @param {MediaError} [err]
10535     *        Error to set on the Tech
10536     *
10537     * @return {MediaError|null}
10538     *         The current error object on the tech, or null if there isn't one.
10539     */
10540    error(err) {
10541      if (err !== undefined) {
10542        this.error_ = new MediaError(err);
10543        this.trigger('error');
10544      }
10545      return this.error_;
10546    }
10547
10548    /**
10549     * Returns the `TimeRange`s that have been played through for the current source.
10550     *
10551     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10552     *         It only checks whether the source has played at all or not.
10553     *
10554     * @return { import('../utils/time').TimeRange 
vendor: 9,042 bytes, lines 10554-10834
10554}
10555     *         - A single time range if this video has played
10556     *         - An empty set of ranges if not.
10557     */
10558    played() {
10559      if (this.hasStarted_) {
10560        return createTimeRanges$1(0, 0);
10561      }
10562      return createTimeRanges$1();
10563    }
10564
10565    /**
10566     * Start playback
10567     *
10568     * @abstract
10569     *
10570     * @see {Html5#play}
10571     */
10572    play() {}
10573
10574    /**
10575     * Set whether we are scrubbing or not
10576     *
10577     * @abstract
10578     * @param {boolean} _isScrubbing
10579     *                  - true for we are currently scrubbing
10580     *                  - false for we are no longer scrubbing
10581     *
10582     * @see {Html5#setScrubbing}
10583     */
10584    setScrubbing(_isScrubbing) {}
10585
10586    /**
10587     * Get whether we are scrubbing or not
10588     *
10589     * @abstract
10590     *
10591     * @see {Html5#scrubbing}
10592     */
10593    scrubbing() {}
10594
10595    /**
10596     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10597     * previously called.
10598     *
10599     * @param {number} _seconds
10600     *        Set the current time of the media to this.
10601     * @fires Tech#timeupdate
10602     */
10603    setCurrentTime(_seconds) {
10604      // improve the accuracy of manual timeupdates
10605      if (this.manualTimeUpdates) {
10606        /**
10607         * A manual `timeupdate` event.
10608         *
10609         * @event Tech#timeupdate
10610         * @type {Event}
10611         */
10612        this.trigger({
10613          type: 'timeupdate',
10614          target: this,
10615          manuallyTriggered: true
10616        });
10617      }
10618    }
10619
10620    /**
10621     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10622     * {@link TextTrackList} events.
10623     *
10624     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10625     *
10626     * @fires Tech#audiotrackchange
10627     * @fires Tech#videotrackchange
10628     * @fires Tech#texttrackchange
10629     */
10630    initTrackListeners() {
10631      /**
10632        * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10633        *
10634        * @event Tech#audiotrackchange
10635        * @type {Event}
10636        */
10637
10638      /**
10639        * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10640        *
10641        * @event Tech#videotrackchange
10642        * @type {Event}
10643        */
10644
10645      /**
10646        * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10647        *
10648        * @event Tech#texttrackchange
10649        * @type {Event}
10650        */
10651      NORMAL.names.forEach(name => {
10652        const props = NORMAL[name];
10653        const trackListChanges = () => {
10654          this.trigger(`${name}trackchange`);
10655        };
10656        const tracks = this[props.getterName]();
10657        tracks.addEventListener('removetrack', trackListChanges);
10658        tracks.addEventListener('addtrack', trackListChanges);
10659        this.on('dispose', () => {
10660          tracks.removeEventListener('removetrack', trackListChanges);
10661          tracks.removeEventListener('addtrack', trackListChanges);
10662        });
10663      });
10664    }
10665
10666    /**
10667     * Emulate TextTracks using vtt.js if necessary
10668     *
10669     * @fires Tech#vttjsloaded
10670     * @fires Tech#vttjserror
10671     */
10672    addWebVttScript_() {
10673      if (window.WebVTT) {
10674        return;
10675      }
10676
10677      // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10678      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10679      // before inserting the WebVTT script
10680      if (document.body.contains(this.el())) {
10681        // load via require if available and vtt.js script location was not passed in
10682        // as an option. novtt builds will turn the above require call into an empty object
10683        // which will cause this if check to always fail.
10684        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10685          this.trigger('vttjsloaded');
10686          return;
10687        }
10688
10689        // load vtt.js via the script location option or the cdn of no location was
10690        // passed in
10691        const script = document.createElement('script');
10692        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10693        script.onload = () => {
10694          /**
10695           * Fired when vtt.js is loaded.
10696           *
10697           * @event Tech#vttjsloaded
10698           * @type {Event}
10699           */
10700          this.trigger('vttjsloaded');
10701        };
10702        script.onerror = () => {
10703          /**
10704           * Fired when vtt.js was not loaded due to an error
10705           *
10706           * @event Tech#vttjsloaded
10707           * @type {Event}
10708           */
10709          this.trigger('vttjserror');
10710        };
10711        this.on('dispose', () => {
10712          script.onload = null;
10713          script.onerror = null;
10714        });
10715        // but have not loaded yet and we set it to true before the inject so that
10716        // we don't overwrite the injected window.WebVTT if it loads right away
10717        window.WebVTT = true;
10718        this.el().parentNode.appendChild(script);
10719      } else {
10720        this.ready(this.addWebVttScript_);
10721      }
10722    }
10723
10724    /**
10725     * Emulate texttracks
10726     *
10727     */
10728    emulateTextTracks() {
10729      const tracks = this.textTracks();
10730      const remoteTracks = this.remoteTextTracks();
10731      const handleAddTrack = e => tracks.addTrack(e.track);
10732      const handleRemoveTrack = e => tracks.removeTrack(e.track);
10733      remoteTracks.on('addtrack', handleAddTrack);
10734      remoteTracks.on('removetrack', handleRemoveTrack);
10735      this.addWebVttScript_();
10736      const updateDisplay = () => this.trigger('texttrackchange');
10737      const textTracksChanges = () => {
10738        updateDisplay();
10739        for (let i = 0; i < tracks.length; i++) {
10740          const track = tracks[i];
10741          track.removeEventListener('cuechange', updateDisplay);
10742          if (track.mode === 'showing') {
10743            track.addEventListener('cuechange', updateDisplay);
10744          }
10745        }
10746      };
10747      textTracksChanges();
10748      tracks.addEventListener('change', textTracksChanges);
10749      tracks.addEventListener('addtrack', textTracksChanges);
10750      tracks.addEventListener('removetrack', textTracksChanges);
10751      this.on('dispose', function () {
10752        remoteTracks.off('addtrack', handleAddTrack);
10753        remoteTracks.off('removetrack', handleRemoveTrack);
10754        tracks.removeEventListener('change', textTracksChanges);
10755        tracks.removeEventListener('addtrack', textTracksChanges);
10756        tracks.removeEventListener('removetrack', textTracksChanges);
10757        for (let i = 0; i < tracks.length; i++) {
10758          const track = tracks[i];
10759          track.removeEventListener('cuechange', updateDisplay);
10760        }
10761      });
10762    }
10763
10764    /**
10765     * Create and returns a remote {@link TextTrack} object.
10766     *
10767     * @param {string} kind
10768     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10769     *
10770     * @param {string} [label]
10771     *        Label to identify the text track
10772     *
10773     * @param {string} [language]
10774     *        Two letter language abbreviation
10775     *
10776     * @return {TextTrack}
10777     *         The TextTrack that gets created.
10778     */
10779    addTextTrack(kind, label, language) {
10780      if (!kind) {
10781        throw new Error('TextTrack kind is required but was not provided');
10782      }
10783      return createTrackHelper(this, kind, label, language);
10784    }
10785
10786    /**
10787     * Create an emulated TextTrack for use by addRemoteTextTrack
10788     *
10789     * This is intended to be overridden by classes that inherit from
10790     * Tech in order to create native or custom TextTracks.
10791     *
10792     * @param {Object} options
10793     *        The object should contain the options to initialize the TextTrack with.
10794     *
10795     * @param {string} [options.kind]
10796     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
10797     *
10798     * @param {string} [options.label].
10799     *        Label to identify the text track
10800     *
10801     * @param {string} [options.language]
10802     *        Two letter language abbreviation.
10803     *
10804     * @return {HTMLTrackElement}
10805     *         The track element that gets created.
10806     */
10807    createRemoteTextTrack(options) {
10808      const track = merge$2(options, {
10809        tech: this
10810      });
10811      return new REMOTE.remoteTextEl.TrackClass(track);
10812    }
10813
10814    /**
10815     * Creates a remote text track object and returns an html track element.
10816     *
10817     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
10818     *
10819     * @param {Object} options
10820     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
10821     *
10822     * @param {boolean} [manualCleanup=false]
10823     *        - When false: the TextTrack will be automatically removed from the video
10824     *          element whenever the source changes
10825     *        - When True: The TextTrack will have to be cleaned up manually
10826     *
10827     * @return {HTMLTrackElement}
10828     *         An Html Track Element.
10829     *
10830     */
10831    addRemoteTextTrack(options = {}, manualCleanup) {
10832      const htmlTrackElement = this.createRemoteTextTrack(options);
10833      if (typeof manualCleanup !== 'boolean') {
10834        manualCleanup = false;
vendor: 7,402 bytes, lines 10835-11095
10835      }
10836
10837      // store HTMLTrackElement and TextTrack to remote list
10838      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
10839      this.remoteTextTracks().addTrack(htmlTrackElement.track);
10840      if (manualCleanup === false) {
10841        // create the TextTrackList if it doesn't exist
10842        this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
10843      }
10844      return htmlTrackElement;
10845    }
10846
10847    /**
10848     * Remove a remote text track from the remote `TextTrackList`.
10849     *
10850     * @param {TextTrack} track
10851     *        `TextTrack` to remove from the `TextTrackList`
10852     */
10853    removeRemoteTextTrack(track) {
10854      const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
10855
10856      // remove HTMLTrackElement and TextTrack from remote list
10857      this.remoteTextTrackEls().removeTrackElement_(trackElement);
10858      this.remoteTextTracks().removeTrack(track);
10859      this.autoRemoteTextTracks_.removeTrack(track);
10860    }
10861
10862    /**
10863     * Gets available media playback quality metrics as specified by the W3C's Media
10864     * Playback Quality API.
10865     *
10866     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
10867     *
10868     * @return {Object}
10869     *         An object with supported media playback quality metrics
10870     *
10871     * @abstract
10872     */
10873    getVideoPlaybackQuality() {
10874      return {};
10875    }
10876
10877    /**
10878     * Attempt to create a floating video window always on top of other windows
10879     * so that users may continue consuming media while they interact with other
10880     * content sites, or applications on their device.
10881     *
10882     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
10883     *
10884     * @return {Promise|undefined}
10885     *         A promise with a Picture-in-Picture window if the browser supports
10886     *         Promises (or one was passed in as an option). It returns undefined
10887     *         otherwise.
10888     *
10889     * @abstract
10890     */
10891    requestPictureInPicture() {
10892      return Promise.reject();
10893    }
10894
10895    /**
10896     * A method to check for the value of the 'disablePictureInPicture' <video> property.
10897     * Defaults to true, as it should be considered disabled if the tech does not support pip
10898     *
10899     * @abstract
10900     */
10901    disablePictureInPicture() {
10902      return true;
10903    }
10904
10905    /**
10906     * A method to set or unset the 'disablePictureInPicture' <video> property.
10907     *
10908     * @abstract
10909     */
10910    setDisablePictureInPicture() {}
10911
10912    /**
10913     * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
10914     *
10915     * @param {function} cb
10916     * @return {number} request id
10917     */
10918    requestVideoFrameCallback(cb) {
10919      const id = newGUID();
10920      if (!this.isReady_ || this.paused()) {
10921        this.queuedHanders_.add(id);
10922        this.one('playing', () => {
10923          if (this.queuedHanders_.has(id)) {
10924            this.queuedHanders_.delete(id);
10925            cb();
10926          }
10927        });
10928      } else {
10929        this.requestNamedAnimationFrame(id, cb);
10930      }
10931      return id;
10932    }
10933
10934    /**
10935     * A fallback implementation of cancelVideoFrameCallback
10936     *
10937     * @param {number} id id of callback to be cancelled
10938     */
10939    cancelVideoFrameCallback(id) {
10940      if (this.queuedHanders_.has(id)) {
10941        this.queuedHanders_.delete(id);
10942      } else {
10943        this.cancelNamedAnimationFrame(id);
10944      }
10945    }
10946
10947    /**
10948     * A method to set a poster from a `Tech`.
10949     *
10950     * @abstract
10951     */
10952    setPoster() {}
10953
10954    /**
10955     * A method to check for the presence of the 'playsinline' <video> attribute.
10956     *
10957     * @abstract
10958     */
10959    playsinline() {}
10960
10961    /**
10962     * A method to set or unset the 'playsinline' <video> attribute.
10963     *
10964     * @abstract
10965     */
10966    setPlaysinline() {}
10967
10968    /**
10969     * Attempt to force override of native audio tracks.
10970     *
10971     * @param {boolean} override - If set to true native audio will be overridden,
10972     * otherwise native audio will potentially be used.
10973     *
10974     * @abstract
10975     */
10976    overrideNativeAudioTracks(override) {}
10977
10978    /**
10979     * Attempt to force override of native video tracks.
10980     *
10981     * @param {boolean} override - If set to true native video will be overridden,
10982     * otherwise native video will potentially be used.
10983     *
10984     * @abstract
10985     */
10986    overrideNativeVideoTracks(override) {}
10987
10988    /**
10989     * Check if the tech can support the given mime-type.
10990     *
10991     * The base tech does not support any type, but source handlers might
10992     * overwrite this.
10993     *
10994     * @param  {string} _type
10995     *         The mimetype to check for support
10996     *
10997     * @return {string}
10998     *         'probably', 'maybe', or empty string
10999     *
11000     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11001     *
11002     * @abstract
11003     */
11004    canPlayType(_type) {
11005      return '';
11006    }
11007
11008    /**
11009     * Check if the type is supported by this tech.
11010     *
11011     * The base tech does not support any type, but source handlers might
11012     * overwrite this.
11013     *
11014     * @param {string} _type
11015     *        The media type to check
11016     * @return {string} Returns the native video element's response
11017     */
11018    static canPlayType(_type) {
11019      return '';
11020    }
11021
11022    /**
11023     * Check if the tech can support the given source
11024     *
11025     * @param {Object} srcObj
11026     *        The source object
11027     * @param {Object} options
11028     *        The options passed to the tech
11029     * @return {string} 'probably', 'maybe', or '' (empty string)
11030     */
11031    static canPlaySource(srcObj, options) {
11032      return Tech.canPlayType(srcObj.type);
11033    }
11034
11035    /*
11036     * Return whether the argument is a Tech or not.
11037     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11038     *
11039     * @param {Object} component
11040     *        The item to check
11041     *
11042     * @return {boolean}
11043     *         Whether it is a tech or not
11044     *         - True if it is a tech
11045     *         - False if it is not
11046     */
11047    static isTech(component) {
11048      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11049    }
11050
11051    /**
11052     * Registers a `Tech` into a shared list for videojs.
11053     *
11054     * @param {string} name
11055     *        Name of the `Tech` to register.
11056     *
11057     * @param {Object} tech
11058     *        The `Tech` class to register.
11059     */
11060    static registerTech(name, tech) {
11061      if (!Tech.techs_) {
11062        Tech.techs_ = {};
11063      }
11064      if (!Tech.isTech(tech)) {
11065        throw new Error(`Tech ${name} must be a Tech`);
11066      }
11067      if (!Tech.canPlayType) {
11068        throw new Error('Techs must have a static canPlayType method on them');
11069      }
11070      if (!Tech.canPlaySource) {
11071        throw new Error('Techs must have a static canPlaySource method on them');
11072      }
11073      name = toTitleCase$1(name);
11074      Tech.techs_[name] = tech;
11075      Tech.techs_[toLowerCase(name)] = tech;
11076      if (name !== 'Tech') {
11077        // camel case the techName for use in techOrder
11078        Tech.defaultTechOrder_.push(name);
11079      }
11080      return tech;
11081    }
11082
11083    /**
11084     * Get a `Tech` from the shared list by name.
11085     *
11086     * @param {string} name
11087     *        `camelCase` or `TitleCase` name of the Tech to get
11088     *
11089     * @return {Tech|undefined}
11090     *         The `Tech` or undefined if there was no tech with the name requested.
11091     */
11092    static getTech(name) {
11093      if (!name) {
11094        return;
11095      }
vendor: 10,237 bytes, lines 11096-11463
11096      if (Tech.techs_ && Tech.techs_[name]) {
11097        return Tech.techs_[name];
11098      }
11099      name = toTitleCase$1(name);
11100      if (window && window.videojs && window.videojs[name]) {
11101        log$1.warn(`The ${name} tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)`);
11102        return window.videojs[name];
11103      }
11104    }
11105  }
11106
11107  /**
11108   * Get the {@link VideoTrackList}
11109   *
11110   * @returns {VideoTrackList}
11111   * @method Tech.prototype.videoTracks
11112   */
11113
11114  /**
11115   * Get the {@link AudioTrackList}
11116   *
11117   * @returns {AudioTrackList}
11118   * @method Tech.prototype.audioTracks
11119   */
11120
11121  /**
11122   * Get the {@link TextTrackList}
11123   *
11124   * @returns {TextTrackList}
11125   * @method Tech.prototype.textTracks
11126   */
11127
11128  /**
11129   * Get the remote element {@link TextTrackList}
11130   *
11131   * @returns {TextTrackList}
11132   * @method Tech.prototype.remoteTextTracks
11133   */
11134
11135  /**
11136   * Get the remote element {@link HtmlTrackElementList}
11137   *
11138   * @returns {HtmlTrackElementList}
11139   * @method Tech.prototype.remoteTextTrackEls
11140   */
11141
11142  ALL.names.forEach(function (name) {
11143    const props = ALL[name];
11144    Tech.prototype[props.getterName] = function () {
11145      this[props.privateName] = this[props.privateName] || new props.ListClass();
11146      return this[props.privateName];
11147    };
11148  });
11149
11150  /**
11151   * List of associated text tracks
11152   *
11153   * @type {TextTrackList}
11154   * @private
11155   * @property Tech#textTracks_
11156   */
11157
11158  /**
11159   * List of associated audio tracks.
11160   *
11161   * @type {AudioTrackList}
11162   * @private
11163   * @property Tech#audioTracks_
11164   */
11165
11166  /**
11167   * List of associated video tracks.
11168   *
11169   * @type {VideoTrackList}
11170   * @private
11171   * @property Tech#videoTracks_
11172   */
11173
11174  /**
11175   * Boolean indicating whether the `Tech` supports volume control.
11176   *
11177   * @type {boolean}
11178   * @default
11179   */
11180  Tech.prototype.featuresVolumeControl = true;
11181
11182  /**
11183   * Boolean indicating whether the `Tech` supports muting volume.
11184   *
11185   * @type {boolean}
11186   * @default
11187   */
11188  Tech.prototype.featuresMuteControl = true;
11189
11190  /**
11191   * Boolean indicating whether the `Tech` supports fullscreen resize control.
11192   * Resizing plugins using request fullscreen reloads the plugin
11193   *
11194   * @type {boolean}
11195   * @default
11196   */
11197  Tech.prototype.featuresFullscreenResize = false;
11198
11199  /**
11200   * Boolean indicating whether the `Tech` supports changing the speed at which the video
11201   * plays. Examples:
11202   *   - Set player to play 2x (twice) as fast
11203   *   - Set player to play 0.5x (half) as fast
11204   *
11205   * @type {boolean}
11206   * @default
11207   */
11208  Tech.prototype.featuresPlaybackRate = false;
11209
11210  /**
11211   * Boolean indicating whether the `Tech` supports the `progress` event.
11212   * This will be used to determine if {@link Tech#manualProgressOn} should be called.
11213   *
11214   * @type {boolean}
11215   * @default
11216   */
11217  Tech.prototype.featuresProgressEvents = false;
11218
11219  /**
11220   * Boolean indicating whether the `Tech` supports the `sourceset` event.
11221   *
11222   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
11223   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
11224   * a new source.
11225   *
11226   * @type {boolean}
11227   * @default
11228   */
11229  Tech.prototype.featuresSourceset = false;
11230
11231  /**
11232   * Boolean indicating whether the `Tech` supports the `timeupdate` event.
11233   * This will be used to determine if {@link Tech#manualTimeUpdates} should be called.
11234   *
11235   * @type {boolean}
11236   * @default
11237   */
11238  Tech.prototype.featuresTimeupdateEvents = false;
11239
11240  /**
11241   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
11242   * This will help us integrate with native `TextTrack`s if the browser supports them.
11243   *
11244   * @type {boolean}
11245   * @default
11246   */
11247  Tech.prototype.featuresNativeTextTracks = false;
11248
11249  /**
11250   * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
11251   *
11252   * @type {boolean}
11253   * @default
11254   */
11255  Tech.prototype.featuresVideoFrameCallback = false;
11256
11257  /**
11258   * A functional mixin for techs that want to use the Source Handler pattern.
11259   * Source handlers are scripts for handling specific formats.
11260   * The source handler pattern is used for adaptive formats (HLS, DASH) that
11261   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
11262   * Example: `Tech.withSourceHandlers.call(MyTech);`
11263   *
11264   * @param {Tech} _Tech
11265   *        The tech to add source handler functions to.
11266   *
11267   * @mixes Tech~SourceHandlerAdditions
11268   */
11269  Tech.withSourceHandlers = function (_Tech) {
11270    /**
11271     * Register a source handler
11272     *
11273     * @param {Function} handler
11274     *        The source handler class
11275     *
11276     * @param {number} [index]
11277     *        Register it at the following index
11278     */
11279    _Tech.registerSourceHandler = function (handler, index) {
11280      let handlers = _Tech.sourceHandlers;
11281      if (!handlers) {
11282        handlers = _Tech.sourceHandlers = [];
11283      }
11284      if (index === undefined) {
11285        // add to the end of the list
11286        index = handlers.length;
11287      }
11288      handlers.splice(index, 0, handler);
11289    };
11290
11291    /**
11292     * Check if the tech can support the given type. Also checks the
11293     * Techs sourceHandlers.
11294     *
11295     * @param {string} type
11296     *         The mimetype to check.
11297     *
11298     * @return {string}
11299     *         'probably', 'maybe', or '' (empty string)
11300     */
11301    _Tech.canPlayType = function (type) {
11302      const handlers = _Tech.sourceHandlers || [];
11303      let can;
11304      for (let i = 0; i < handlers.length; i++) {
11305        can = handlers[i].canPlayType(type);
11306        if (can) {
11307          return can;
11308        }
11309      }
11310      return '';
11311    };
11312
11313    /**
11314     * Returns the first source handler that supports the source.
11315     *
11316     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
11317     *
11318     * @param {SourceObject} source
11319     *        The source object
11320     *
11321     * @param {Object} options
11322     *        The options passed to the tech
11323     *
11324     * @return {SourceHandler|null}
11325     *          The first source handler that supports the source or null if
11326     *          no SourceHandler supports the source
11327     */
11328    _Tech.selectSourceHandler = function (source, options) {
11329      const handlers = _Tech.sourceHandlers || [];
11330      let can;
11331      for (let i = 0; i < handlers.length; i++) {
11332        can = handlers[i].canHandleSource(source, options);
11333        if (can) {
11334          return handlers[i];
11335        }
11336      }
11337      return null;
11338    };
11339
11340    /**
11341     * Check if the tech can support the given source.
11342     *
11343     * @param {SourceObject} srcObj
11344     *        The source object
11345     *
11346     * @param {Object} options
11347     *        The options passed to the tech
11348     *
11349     * @return {string}
11350     *         'probably', 'maybe', or '' (empty string)
11351     */
11352    _Tech.canPlaySource = function (srcObj, options) {
11353      const sh = _Tech.selectSourceHandler(srcObj, options);
11354      if (sh) {
11355        return sh.canHandleSource(srcObj, options);
11356      }
11357      return '';
11358    };
11359
11360    /**
11361     * When using a source handler, prefer its implementation of
11362     * any function normally provided by the tech.
11363     */
11364    const deferrable = ['seekable', 'seeking', 'duration'];
11365
11366    /**
11367     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11368     * function if it exists, with a fallback to the Techs seekable function.
11369     *
11370     * @method _Tech.seekable
11371     */
11372
11373    /**
11374     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11375     * function if it exists, otherwise it will fallback to the techs duration function.
11376     *
11377     * @method _Tech.duration
11378     */
11379
11380    deferrable.forEach(function (fnName) {
11381      const originalFn = this[fnName];
11382      if (typeof originalFn !== 'function') {
11383        return;
11384      }
11385      this[fnName] = function () {
11386        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11387          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11388        }
11389        return originalFn.apply(this, arguments);
11390      };
11391    }, _Tech.prototype);
11392
11393    /**
11394     * Create a function for setting the source using a source object
11395     * and source handlers.
11396     * Should never be called unless a source handler was found.
11397     *
11398     * @param {SourceObject} source
11399     *        A source object with src and type keys
11400     */
11401    _Tech.prototype.setSource = function (source) {
11402      let sh = _Tech.selectSourceHandler(source, this.options_);
11403      if (!sh) {
11404        // Fall back to a native source handler when unsupported sources are
11405        // deliberately set
11406        if (_Tech.nativeSourceHandler) {
11407          sh = _Tech.nativeSourceHandler;
11408        } else {
11409          log$1.error('No source handler found for the current source.');
11410        }
11411      }
11412
11413      // Dispose any existing source handler
11414      this.disposeSourceHandler();
11415      this.off('dispose', this.disposeSourceHandler_);
11416      if (sh !== _Tech.nativeSourceHandler) {
11417        this.currentSource_ = source;
11418      }
11419      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11420      this.one('dispose', this.disposeSourceHandler_);
11421    };
11422
11423    /**
11424     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11425     *
11426     * @listens Tech#dispose
11427     */
11428    _Tech.prototype.disposeSourceHandler = function () {
11429      // if we have a source and get another one
11430      // then we are loading something new
11431      // than clear all of our current tracks
11432      if (this.currentSource_) {
11433        this.clearTracks(['audio', 'video']);
11434        this.currentSource_ = null;
11435      }
11436
11437      // always clean up auto-text tracks
11438      this.cleanupAutoTextTracks();
11439      if (this.sourceHandler_) {
11440        if (this.sourceHandler_.dispose) {
11441          this.sourceHandler_.dispose();
11442        }
11443        this.sourceHandler_ = null;
11444      }
11445    };
11446  };
11447
11448  // The base Tech class needs to be registered as a Component. It is the only
11449  // Tech that can be registered as a Component.
11450  Component$1.registerComponent('Tech', Tech);
11451  Tech.registerTech('Tech', Tech);
11452
11453  /**
11454   * A list of techs that should be added to techOrder on Players
11455   *
11456   * @private
11457   */
11458  Tech.defaultTechOrder_ = [];
11459
11460  /**
11461   * @file middleware.js
11462   * @module middleware
11463   */
vendor: 11,759 bytes, lines 11464-11873
11464  const middlewares = {};
11465  const middlewareInstances = {};
11466  const TERMINATOR = {};
11467
11468  /**
11469   * A middleware object is a plain JavaScript object that has methods that
11470   * match the {@link Tech} methods found in the lists of allowed
11471   * {@link module:middleware.allowedGetters|getters},
11472   * {@link module:middleware.allowedSetters|setters}, and
11473   * {@link module:middleware.allowedMediators|mediators}.
11474   *
11475   * @typedef {Object} MiddlewareObject
11476   */
11477
11478  /**
11479   * A middleware factory function that should return a
11480   * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
11481   *
11482   * This factory will be called for each player when needed, with the player
11483   * passed in as an argument.
11484   *
11485   * @callback MiddlewareFactory
11486   * @param { import('../player').default } player
11487   *        A Video.js player.
11488   */
11489
11490  /**
11491   * Define a middleware that the player should use by way of a factory function
11492   * that returns a middleware object.
11493   *
11494   * @param  {string} type
11495   *         The MIME type to match or `"*"` for all MIME types.
11496   *
11497   * @param  {MiddlewareFactory} middleware
11498   *         A middleware factory function that will be executed for
11499   *         matching types.
11500   */
11501  function use(type, middleware) {
11502    middlewares[type] = middlewares[type] || [];
11503    middlewares[type].push(middleware);
11504  }
11505
11506  /**
11507   * Asynchronously sets a source using middleware by recursing through any
11508   * matching middlewares and calling `setSource` on each, passing along the
11509   * previous returned value each time.
11510   *
11511   * @param  { import('../player').default } player
11512   *         A {@link Player} instance.
11513   *
11514   * @param  {Tech~SourceObject} src
11515   *         A source object.
11516   *
11517   * @param  {Function}
11518   *         The next middleware to run.
11519   */
11520  function setSource(player, src, next) {
11521    player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
11522  }
11523
11524  /**
11525   * When the tech is set, passes the tech to each middleware's `setTech` method.
11526   *
11527   * @param {Object[]} middleware
11528   *        An array of middleware instances.
11529   *
11530   * @param { import('../tech/tech').default } tech
11531   *        A Video.js tech.
11532   */
11533  function setTech(middleware, tech) {
11534    middleware.forEach(mw => mw.setTech && mw.setTech(tech));
11535  }
11536
11537  /**
11538   * Calls a getter on the tech first, through each middleware
11539   * from right to left to the player.
11540   *
11541   * @param  {Object[]} middleware
11542   *         An array of middleware instances.
11543   *
11544   * @param  { import('../tech/tech').default } tech
11545   *         The current tech.
11546   *
11547   * @param  {string} method
11548   *         A method name.
11549   *
11550   * @return {*}
11551   *         The final value from the tech after middleware has intercepted it.
11552   */
11553  function get(middleware, tech, method) {
11554    return middleware.reduceRight(middlewareIterator(method), tech[method]());
11555  }
11556
11557  /**
11558   * Takes the argument given to the player and calls the setter method on each
11559   * middleware from left to right to the tech.
11560   *
11561   * @param  {Object[]} middleware
11562   *         An array of middleware instances.
11563   *
11564   * @param  { import('../tech/tech').default } tech
11565   *         The current tech.
11566   *
11567   * @param  {string} method
11568   *         A method name.
11569   *
11570   * @param  {*} arg
11571   *         The value to set on the tech.
11572   *
11573   * @return {*}
11574   *         The return value of the `method` of the `tech`.
11575   */
11576  function set(middleware, tech, method, arg) {
11577    return tech[method](middleware.reduce(middlewareIterator(method), arg));
11578  }
11579
11580  /**
11581   * Takes the argument given to the player and calls the `call` version of the
11582   * method on each middleware from left to right.
11583   *
11584   * Then, call the passed in method on the tech and return the result unchanged
11585   * back to the player, through middleware, this time from right to left.
11586   *
11587   * @param  {Object[]} middleware
11588   *         An array of middleware instances.
11589   *
11590   * @param  { import('../tech/tech').default } tech
11591   *         The current tech.
11592   *
11593   * @param  {string} method
11594   *         A method name.
11595   *
11596   * @param  {*} arg
11597   *         The value to set on the tech.
11598   *
11599   * @return {*}
11600   *         The return value of the `method` of the `tech`, regardless of the
11601   *         return values of middlewares.
11602   */
11603  function mediate(middleware, tech, method, arg = null) {
11604    const callMethod = 'call' + toTitleCase$1(method);
11605    const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11606    const terminated = middlewareValue === TERMINATOR;
11607    // deprecated. The `null` return value should instead return TERMINATOR to
11608    // prevent confusion if a techs method actually returns null.
11609    const returnValue = terminated ? null : tech[method](middlewareValue);
11610    executeRight(middleware, method, returnValue, terminated);
11611    return returnValue;
11612  }
11613
11614  /**
11615   * Enumeration of allowed getters where the keys are method names.
11616   *
11617   * @type {Object}
11618   */
11619  const allowedGetters = {
11620    buffered: 1,
11621    currentTime: 1,
11622    duration: 1,
11623    muted: 1,
11624    played: 1,
11625    paused: 1,
11626    seekable: 1,
11627    volume: 1,
11628    ended: 1
11629  };
11630
11631  /**
11632   * Enumeration of allowed setters where the keys are method names.
11633   *
11634   * @type {Object}
11635   */
11636  const allowedSetters = {
11637    setCurrentTime: 1,
11638    setMuted: 1,
11639    setVolume: 1
11640  };
11641
11642  /**
11643   * Enumeration of allowed mediators where the keys are method names.
11644   *
11645   * @type {Object}
11646   */
11647  const allowedMediators = {
11648    play: 1,
11649    pause: 1
11650  };
11651  function middlewareIterator(method) {
11652    return (value, mw) => {
11653      // if the previous middleware terminated, pass along the termination
11654      if (value === TERMINATOR) {
11655        return TERMINATOR;
11656      }
11657      if (mw[method]) {
11658        return mw[method](value);
11659      }
11660      return value;
11661    };
11662  }
11663  function executeRight(mws, method, value, terminated) {
11664    for (let i = mws.length - 1; i >= 0; i--) {
11665      const mw = mws[i];
11666      if (mw[method]) {
11667        mw[method](terminated, value);
11668      }
11669    }
11670  }
11671
11672  /**
11673   * Clear the middleware cache for a player.
11674   *
11675   * @param  { import('../player').default } player
11676   *         A {@link Player} instance.
11677   */
11678  function clearCacheForPlayer(player) {
11679    middlewareInstances[player.id()] = null;
11680  }
11681
11682  /**
11683   * {
11684   *  [playerId]: [[mwFactory, mwInstance], ...]
11685   * }
11686   *
11687   * @private
11688   */
11689  function getOrCreateFactory(player, mwFactory) {
11690    const mws = middlewareInstances[player.id()];
11691    let mw = null;
11692    if (mws === undefined || mws === null) {
11693      mw = mwFactory(player);
11694      middlewareInstances[player.id()] = [[mwFactory, mw]];
11695      return mw;
11696    }
11697    for (let i = 0; i < mws.length; i++) {
11698      const [mwf, mwi] = mws[i];
11699      if (mwf !== mwFactory) {
11700        continue;
11701      }
11702      mw = mwi;
11703    }
11704    if (mw === null) {
11705      mw = mwFactory(player);
11706      mws.push([mwFactory, mw]);
11707    }
11708    return mw;
11709  }
11710  function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
11711    const [mwFactory, ...mwrest] = middleware;
11712
11713    // if mwFactory is a string, then we're at a fork in the road
11714    if (typeof mwFactory === 'string') {
11715      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
11716
11717      // if we have an mwFactory, call it with the player to get the mw,
11718      // then call the mw's setSource method
11719    } else if (mwFactory) {
11720      const mw = getOrCreateFactory(player, mwFactory);
11721
11722      // if setSource isn't present, implicitly select this middleware
11723      if (!mw.setSource) {
11724        acc.push(mw);
11725        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11726      }
11727      mw.setSource(Object.assign({}, src), function (err, _src) {
11728        // something happened, try the next middleware on the current level
11729        // make sure to use the old src
11730        if (err) {
11731          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11732        }
11733
11734        // we've succeeded, now we need to go deeper
11735        acc.push(mw);
11736
11737        // if it's the same type, continue down the current chain
11738        // otherwise, we want to go down the new chain
11739        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11740      });
11741    } else if (mwrest.length) {
11742      setSourceHelper(src, mwrest, next, player, acc, lastRun);
11743    } else if (lastRun) {
11744      next(src, acc);
11745    } else {
11746      setSourceHelper(src, middlewares['*'], next, player, acc, true);
11747    }
11748  }
11749
11750  /**
11751   * Mimetypes
11752   *
11753   * @see https://www.iana.org/assignments/media-types/media-types.xhtml
11754   * @typedef Mimetypes~Kind
11755   * @enum
11756   */
11757  const MimetypesKind = {
11758    opus: 'video/ogg',
11759    ogv: 'video/ogg',
11760    mp4: 'video/mp4',
11761    mov: 'video/mp4',
11762    m4v: 'video/mp4',
11763    mkv: 'video/x-matroska',
11764    m4a: 'audio/mp4',
11765    mp3: 'audio/mpeg',
11766    aac: 'audio/aac',
11767    caf: 'audio/x-caf',
11768    flac: 'audio/flac',
11769    oga: 'audio/ogg',
11770    wav: 'audio/wav',
11771    m3u8: 'application/x-mpegURL',
11772    mpd: 'application/dash+xml',
11773    jpg: 'image/jpeg',
11774    jpeg: 'image/jpeg',
11775    gif: 'image/gif',
11776    png: 'image/png',
11777    svg: 'image/svg+xml',
11778    webp: 'image/webp'
11779  };
11780
11781  /**
11782   * Get the mimetype of a given src url if possible
11783   *
11784   * @param {string} src
11785   *        The url to the src
11786   *
11787   * @return {string}
11788   *         return the mimetype if it was known or empty string otherwise
11789   */
11790  const getMimetype = function (src = '') {
11791    const ext = getFileExtension(src);
11792    const mimetype = MimetypesKind[ext.toLowerCase()];
11793    return mimetype || '';
11794  };
11795
11796  /**
11797   * Find the mime type of a given source string if possible. Uses the player
11798   * source cache.
11799   *
11800   * @param { import('../player').default } player
11801   *        The player object
11802   *
11803   * @param {string} src
11804   *        The source string
11805   *
11806   * @return {string}
11807   *         The type that was found
11808   */
11809  const findMimetype = (player, src) => {
11810    if (!src) {
11811      return '';
11812    }
11813
11814    // 1. check for the type in the `source` cache
11815    if (player.cache_.source.src === src && player.cache_.source.type) {
11816      return player.cache_.source.type;
11817    }
11818
11819    // 2. see if we have this source in our `currentSources` cache
11820    const matchingSources = player.cache_.sources.filter(s => s.src === src);
11821    if (matchingSources.length) {
11822      return matchingSources[0].type;
11823    }
11824
11825    // 3. look for the src url in source elements and use the type there
11826    const sources = player.$$('source');
11827    for (let i = 0; i < sources.length; i++) {
11828      const s = sources[i];
11829      if (s.type && s.src && s.src === src) {
11830        return s.type;
11831      }
11832    }
11833
11834    // 4. finally fallback to our list of mime types based on src url extension
11835    return getMimetype(src);
11836  };
11837
11838  /**
11839   * @module filter-source
11840   */
11841
11842  /**
11843   * Filter out single bad source objects or multiple source objects in an
11844   * array. Also flattens nested source object arrays into a 1 dimensional
11845   * array of source objects.
11846   *
11847   * @param {Tech~SourceObject|Tech~SourceObject[]} src
11848   *        The src object to filter
11849   *
11850   * @return {Tech~SourceObject[]}
11851   *         An array of sourceobjects containing only valid sources
11852   *
11853   * @private
11854   */
11855  const filterSource = function (src) {
11856    // traverse array
11857    if (Array.isArray(src)) {
11858      let newsrc = [];
11859      src.forEach(function (srcobj) {
11860        srcobj = filterSource(srcobj);
11861        if (Array.isArray(srcobj)) {
11862          newsrc = newsrc.concat(srcobj);
11863        } else if (isObject$1(srcobj)) {
11864          newsrc.push(srcobj);
11865        }
11866      });
11867      src = newsrc;
11868    } else if (typeof src === 'string' && src.trim()) {
11869      // convert string into object
11870      src = [fixSource({
11871        src
11872      })];
11873    } else if (isObject$1
vendor: 17,264 bytes, lines 11873-11901
11873(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
11874      // src is already valid
11875      src = [fixSource(src)];
11876    } else {
11877      // invalid source, turn it into an empty array
11878      src = [];
11879    }
11880    return src;
11881  };
11882
11883  /**
11884   * Checks src mimetype, adding it when possible
11885   *
11886   * @param {Tech~SourceObject} src
11887   *        The src object to check
11888   * @return {Tech~SourceObject}
11889   *        src Object with known type
11890   */
11891  function fixSource(src) {
11892    if (!src.type) {
11893      const mimetype = getMimetype(src.src);
11894      if (mimetype) {
11895        src.type = mimetype;
11896      }
11897    }
11898    return src;
11899  }
11900
11901  var icons = "<svg xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-play\">\n      <path d=\"M16 10v28l22-14z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-pause\">\n      <path d=\"M12 38h8V10h-8v28zm16-28v28h8V10h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-audio\">\n      <path d=\"M24 2C14.06 2 6 10.06 6 20v14c0 3.31 2.69 6 6 6h6V24h-8v-4c0-7.73 6.27-14 14-14s14 6.27 14 14v4h-8v16h6c3.31 0 6-2.69 6-6V20c0-9.94-8.06-18-18-18z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-captions\">\n      <path d=\"M38 8H10c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM22 22h-3v-1h-4v6h4v-1h3v2a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2zm14 0h-3v-1h-4v6h4v-1h3v2a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-subtitles\">\n      <path d=\"M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-fullscreen-enter\">\n      <path d=\"M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-fullscreen-exit\">\n      <path d=\"M10 32h6v6h4V28H10v4zm6-16h-6v4h10V10h-4v6zm12 22h4v-6h6v-4H28v10zm4-22v-6h-4v10h10v-4h-6z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-play-circle\">\n      <path d=\"M20 33l12-9-12-9v18zm4-29C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-mute\">\n      <path d=\"M33 24c0-3.53-2.04-6.58-5-8.05v4.42l4.91 4.91c.06-.42.09-.85.09-1.28zm5 0c0 1.88-.41 3.65-1.08 5.28l3.03 3.03C41.25 29.82 42 27 42 24c0-8.56-5.99-15.72-14-17.54v4.13c5.78 1.72 10 7.07 10 13.41zM8.55 6L6 8.55 15.45 18H6v12h8l10 10V26.55l8.51 8.51c-1.34 1.03-2.85 1.86-4.51 2.36v4.13a17.94 17.94 0 0 0 7.37-3.62L39.45 42 42 39.45l-18-18L8.55 6zM24 8l-4.18 4.18L24 16.36V8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-low\">\n      <path d=\"M14 18v12h8l10 10V8L22 18h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-medium\">\n      <path d=\"M37 24c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zm-27-6v12h8l10 10V8L18 18h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-high\">\n      <path d=\"M6 18v12h8l10 10V8L14 18H6zm27 6c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zM28 6.46v4.13c5.78 1.72 10 7.07 10 13.41s-4.22 11.69-10 13.41v4.13c8.01-1.82 14-8.97 14-17.54S36.01 8.28 28 6.46z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-spinner\">\n      <path d=\"M18.8 21l9.53-16.51C26.94 4.18 25.49 4 24 4c-4.8 0-9.19 1.69-12.64 4.51l7.33 12.69.11-.2zm24.28-3c-1.84-5.85-6.3-10.52-11.99-12.68L23.77 18h19.31zm.52 2H28.62l.58 1 9.53 16.5C41.99 33.94 44 29.21 44 24c0-1.37-.14-2.71-.4-4zm-26.53 4l-7.8-13.5C6.01 14.06 4 18.79 4 24c0 1.37.14 2.71.4 4h14.98l-2.31-4zM4.92 30c1.84 5.85 6.3 10.52 11.99 12.68L24.23 30H4.92zm22.54 0l-7.8 13.51c1.4.31 2.85.49 4.34.49 4.8 0 9.19-1.69 12.64-4.51L29.31 26.8 27.46 30z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 24 24\" id=\"vjs-icon-hd\">\n      <path d=\"M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm2-6h4c.55 0 1 .45 1 1v4c0 .55-.45 1-1 1h-4V9zm1.5 4.5h2v-3h-2v3z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-chapters\">\n      <path d=\"M6 26h4v-4H6v4zm0 8h4v-4H6v4zm0-16h4v-4H6v4zm8 8h28v-4H14v4zm0 8h28v-4H14v4zm0-20v4h28v-4H14z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 40 40\" id=\"vjs-icon-downloading\">\n      <path d=\"M18.208 36.875q-3.208-.292-5.979-1.729-2.771-1.438-4.812-3.729-2.042-2.292-3.188-5.229-1.146-2.938-1.146-6.23 0-6.583 4.334-11.416 4.333-4.834 10.833-5.5v3.166q-5.167.75-8.583 4.646Q6.25 14.75 6.25 19.958q0 5.209 3.396 9.104 3.396 3.896 8.562 4.646zM20 28.417L11.542 20l2.083-2.083 4.917 4.916v-11.25h2.916v11.25l4.875-4.916L28.417 20zm1.792 8.458v-3.167q1.833-.25 3.541-.958 1.709-.708 3.167-1.875l2.333 2.292q-1.958 1.583-4.25 2.541-2.291.959-4.791 1.167zm6.791-27.792q-1.541-1.125-3.25-1.854-1.708-.729-3.541-1.021V3.042q2.5.25 4.77 1.208 2.271.958 4.271 2.5zm4.584 21.584l-2.25-2.25q1.166-1.5 1.854-3.209.687-1.708.937-3.541h3.209q-.292 2.5-1.229 4.791-.938 2.292-2.521 4.209zm.541-12.417q-.291-1.833-.958-3.562-.667-1.73-1.833-3.188l2.375-2.208q1.541 1.916 2.458 4.208.917 2.292 1.167 4.75z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download\">\n      <path d=\"M10.8 40.55q-1.35 0-2.375-1T7.4 37.15v-7.7h3.4v7.7h26.35v-7.7h3.4v7.7q0 1.4-1 2.4t-2.4 1zM24 32.1L13.9 22.05l2.45-2.45 5.95 5.95V7.15h3.4v18.4l5.95-5.95 2.45 2.45z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download-done\">\n      <path d=\"M9.8 40.5v-3.45h28.4v3.45zm9.2-9.05L7.4 19.85l2.45-2.35L19 26.65l19.2-19.2 2.4 2.4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download-off\">\n      <path d=\"M4.9 4.75L43.25 43.1 41 45.3l-4.75-4.75q-.05.05-.075.025-.025-.025-.075-.025H10.8q-1.35 0-2.375-1T7.4 37.15v-7.7h3.4v7.7h22.05l-7-7-1.85 1.8L13.9 21.9l1.85-1.85L2.7 7zm26.75 14.7l2.45 2.45-3.75 3.8-2.45-2.5zM25.7 7.15V21.1l-3.4-3.45V7.15z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-share\">\n      <path d=\"M36 32.17c-1.52 0-2.89.59-3.93 1.54L17.82 25.4c.11-.45.18-.92.18-1.4s-.07-.95-.18-1.4l14.1-8.23c1.07 1 2.5 1.62 4.08 1.62 3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6c0 .48.07.95.18 1.4l-14.1 8.23c-1.07-1-2.5-1.62-4.08-1.62-3.31 0-6 2.69-6 6s2.69 6 6 6c1.58 0 3.01-.62 4.08-1.62l14.25 8.31c-.1.42-.16.86-.16 1.31A5.83 5.83 0 1 0 36 32.17z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cog\">\n      <path d=\"M38.86 25.95c.08-.64.14-1.29.14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3.49-.84.24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91.37-.99.84l-.75 5.3a14.8 14.8 0 0 0-3.38 1.97L9.9 10.1a1 1 0 0 0-1.22.43l-4 6.93c-.25.43-.14.97.24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31.14 1.95l-4.22 3.31c-.38.3-.49.84-.24 1.28l4 6.93c.25.43.77.61 1.22.43l4.98-2.01c1.03.79 2.16 1.46 3.38 1.97l.75 5.3c.08.47.49.84.99.84h8c.5 0 .91-.37.99-.84l.75-5.3a14.8 14.8 0 0 0 3.38-1.97l4.98 2.01a1 1 0 0 0 1.22-.43l4-6.93c.25-.43.14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-square\">\n      <path d=\"M36 8H12c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h24c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm0 28H12V12h24v24z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle\">\n      <circle cx=\"24\" cy=\"24\" r=\"20\"></circle>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle-outline\">\n      <path d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle-inner-circle\">\n      <path d=\"M24 4C12.97 4 4 12.97 4 24s8.97 20 20 20 20-8.97 20-20S35.03 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16zm6-16c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6 6 2.69 6 6z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cancel\">\n      <path d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-replay\">\n      <path d=\"M24 10V2L14 12l10 10v-8c6.63 0 12 5.37 12 12s-5.37 12-12 12-12-5.37-12-12H8c0 8.84 7.16 16 16 16s16-7.16 16-16-7.16-16-16-16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-repeat\">\n      <path d=\"M14 14h20v6l8-8-8-8v6H10v12h4v-8zm20 20H14v-6l-8 8 8 8v-6h24V26h-4v8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-5\">\n      <path d=\"M17.689 98l-8.697 8.696 8.697 8.697 2.486-2.485-4.32-4.319h1.302c4.93 0 9.071 1.722 12.424 5.165 3.352 3.443 5.029 7.638 5.029 12.584h3.55c0-2.958-.553-5.73-1.658-8.313-1.104-2.583-2.622-4.841-4.555-6.774-1.932-1.932-4.19-3.45-6.773-4.555-2.584-1.104-5.355-1.657-8.313-1.657H15.5l4.615-4.615zm-8.08 21.659v13.861h11.357v5.008H9.609V143h12.7c.834 0 1.55-.298 2.146-.894.596-.597.895-1.31.895-2.145v-7.781c0-.835-.299-1.55-.895-2.147a2.929 2.929 0 0 0-2.147-.894h-8.227v-5.096H25.35v-4.384z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-10\">\n      <path d=\"M42.315 125.63c0-4.997-1.694-9.235-5.08-12.713-3.388-3.479-7.571-5.218-12.552-5.218h-1.315l4.363 4.363-2.51 2.51-8.787-8.786L25.221 97l2.45 2.45-4.662 4.663h1.375c2.988 0 5.788.557 8.397 1.673 2.61 1.116 4.892 2.65 6.844 4.602 1.953 1.953 3.487 4.234 4.602 6.844 1.116 2.61 1.674 5.41 1.674 8.398zM8.183 142v-19.657H3.176V117.8h9.643V142zm13.63 0c-1.156 0-2.127-.393-2.912-1.178-.778-.778-1.168-1.746-1.168-2.902v-16.04c0-1.156.393-2.127 1.178-2.912.779-.779 1.746-1.168 2.902-1.168h7.696c1.156 0 2.126.392 2.911 1.177.779.78 1.168 1.747 1.168 2.903v16.04c0 1.156-.392 2.127-1.177 2.912-.779.779-1.746 1.168-2.902 1.168zm.556-4.636h6.583v-15.02H22.37z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-30\">\n      <path d=\"M26.047 97l-8.733 8.732 8.733 8.733 2.496-2.494-4.336-4.338h1.307c4.95 0 9.108 1.73 12.474 5.187 3.367 3.458 5.051 7.668 5.051 12.635h3.565c0-2.97-.556-5.751-1.665-8.346-1.109-2.594-2.633-4.862-4.574-6.802-1.94-1.941-4.208-3.466-6.803-4.575-2.594-1.109-5.375-1.664-8.345-1.664H23.85l4.634-4.634zM2.555 117.531v4.688h10.297v5.25H5.873v4.687h6.979v5.156H2.555V142H13.36c1.061 0 1.95-.395 2.668-1.186.718-.79 1.076-1.772 1.076-2.94v-16.218c0-1.168-.358-2.149-1.076-2.94-.717-.79-1.607-1.185-2.668-1.185zm22.482.14c-1.149 0-2.11.39-2.885 1.165-.78.78-1.172 1.744-1.172 2.893v15.943c0 1.149.388 2.11 1.163 2.885.78.78 1.745 1.172 2.894 1.172h7.649c1.148 0 2.11-.388 2.884-1.163.78-.78 1.17-1.745 1.17-2.894v-15.943c0-1.15-.386-2.111-1.16-2.885-.78-.78-1.746-1.172-2.894-1.172zm.553 4.518h6.545v14.93H25.59z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-5\">\n      <path d=\"M29.508 97l-2.431 2.43 4.625 4.625h-1.364c-2.965 0-5.742.554-8.332 1.66-2.589 1.107-4.851 2.629-6.788 4.566-1.937 1.937-3.458 4.2-4.565 6.788-1.107 2.59-1.66 5.367-1.66 8.331h3.557c0-4.957 1.68-9.16 5.04-12.611 3.36-3.45 7.51-5.177 12.451-5.177h1.304l-4.326 4.33 2.49 2.49 8.715-8.716zm-9.783 21.61v13.89h11.382v5.018H19.725V142h12.727a2.93 2.93 0 0 0 2.15-.896 2.93 2.93 0 0 0 .896-2.15v-7.798c0-.837-.299-1.554-.896-2.152a2.93 2.93 0 0 0-2.15-.896h-8.245V123h11.29v-4.392z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-10\">\n      <path d=\"M23.119 97l-2.386 2.383 4.538 4.538h-1.339c-2.908 0-5.633.543-8.173 1.63-2.54 1.085-4.76 2.577-6.66 4.478-1.9 1.9-3.392 4.12-4.478 6.66-1.085 2.54-1.629 5.264-1.629 8.172h3.49c0-4.863 1.648-8.986 4.944-12.372 3.297-3.385 7.368-5.078 12.216-5.078h1.279l-4.245 4.247 2.443 2.442 8.55-8.55zm-9.52 21.45v4.42h4.871V142h4.513v-23.55zm18.136 0c-1.125 0-2.066.377-2.824 1.135-.764.764-1.148 1.709-1.148 2.834v15.612c0 1.124.38 2.066 1.139 2.824.764.764 1.708 1.145 2.833 1.145h7.489c1.125 0 2.066-.378 2.824-1.136.764-.764 1.145-1.709 1.145-2.833v-15.612c0-1.125-.378-2.067-1.136-2.825-.764-.764-1.708-1.145-2.833-1.145zm.54 4.42h6.408v14.617h-6.407z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-30\">\n      <path d=\"M25.549 97l-2.437 2.434 4.634 4.635H26.38c-2.97 0-5.753.555-8.347 1.664-2.594 1.109-4.861 2.633-6.802 4.574-1.94 1.94-3.465 4.207-4.574 6.802-1.109 2.594-1.664 5.377-1.664 8.347h3.565c0-4.967 1.683-9.178 5.05-12.636 3.366-3.458 7.525-5.187 12.475-5.187h1.307l-4.335 4.338 2.495 2.494 8.732-8.732zm-11.553 20.53v4.689h10.297v5.249h-6.978v4.688h6.978v5.156H13.996V142h10.808c1.06 0 1.948-.395 2.666-1.186.718-.79 1.077-1.771 1.077-2.94v-16.217c0-1.169-.36-2.15-1.077-2.94-.718-.79-1.605-1.186-2.666-1.186zm21.174.168c-1.149 0-2.11.389-2.884 1.163-.78.78-1.172 1.745-1.172 2.894v15.942c0 1.15.388 2.11 1.162 2.885.78.78 1.745 1.17 2.894 1.17h7.649c1.149 0 2.11-.386 2.885-1.16.78-.78 1.17-1.746 1.17-2.895v-15.942c0-1.15-.387-2.11-1.161-2.885-.78-.78-1.745-1.172-2.894-1.172zm.552 4.516h6.542v14.931h-6.542z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 512 512\" id=\"vjs-icon-audio-description\">\n      <g fill-rule=\"evenodd\"><path d=\"M227.29 381.351V162.993c50.38-1.017 89.108-3.028 117.631 17.126 27.374 19.342 48.734 56.965 44.89 105.325-4.067 51.155-41.335 94.139-89.776 98.475-24.085 2.155-71.972 0-71.972 0s-.84-1.352-.773-2.568m48.755-54.804c31.43 1.26 53.208-16.633 56.495-45.386 4.403-38.51-21.188-63.552-58.041-60.796v103.612c-.036 1.466.575 2.22 1.546 2.57\"></path><path d=\"M383.78 381.328c13.336 3.71 17.387-11.06 23.215-21.408 12.722-22.571 22.294-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.51c-.587 3.874 2.226 7.315 3.865 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894\"></path><path d=\"M425.154 381.328c13.336 3.71 17.384-11.061 23.215-21.408 12.721-22.571 22.291-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.511c-.586 3.874 2.226 7.315 3.866 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894\"></path><path d=\"M466.26 381.328c13.337 3.71 17.385-11.061 23.216-21.408 12.722-22.571 22.292-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.51c-.587 3.874 2.225 7.315 3.865 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894M4.477 383.005H72.58l18.573-28.484 64.169-.135s.065 19.413.065 28.62h48.756V160.307h-58.816c-5.653 9.537-140.85 222.697-140.85 222.697zm152.667-145.282v71.158l-40.453-.27 40.453-70.888z\"></path></g>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-next-item\">\n      <path d=\"M12 36l17-12-17-12v24zm20-24v24h4V12h-4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-previous-item\">\n      <path d=\"M12 12h4v24h-4zm7 12l17 12V12z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-shuffle\">\n      <path d=\"M21.17 18.34L10.83 8 8 10.83l10.34 10.34 2.83-2.83zM29 8l4.09 4.09L8 37.17 10.83 40l25.09-25.09L40 19V8H29zm.66 18.83l-2.83 2.83 6.26 6.26L29 40h11V29l-4.09 4.09-6.25-6.26z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cast\">\n      <path d=\"M42 6H6c-2.21 0-4 1.79-4 4v6h4v-6h36v28H28v4h14c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4zM2 36v6h6c0-3.31-2.69-6-6-6zm0-8v4c5.52 0 10 4.48 10 10h4c0-7.73-6.27-14-14-14zm0-8v4c9.94 0 18 8.06 18 18h4c0-12.15-9.85-22-22-22z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-picture-in-picture-enter\">\n      <path d=\"M38 22H22v11.99h16V22zm8 16V9.96C46 7.76 44.2 6 42 6H6C3.8 6 2 7.76 2 9.96V38c0 2.2 1.8 4 4 4h36c2.2 0 4-1.8 4-4zm-4 .04H6V9.94h36v28.1z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 22 18\" id=\"vjs-icon-picture-in-picture-exit\">\n      <path d=\"M18 4H4v10h14V4zm4 12V1.98C22 .88 21.1 0 20 0H2C.9 0 0 .88 0 1.98V16c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zm-2 .02H2V1.97h18v14.05z\"></path>\n      <path fill=\"none\" d=\"M-1-3h24v24H-1z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-facebook\">\n      <path d=\"M1343 12v264h-157q-86 0-116 36t-30 108v189h293l-39 296h-254v759H734V905H479V609h255V391q0-186 104-288.5T1115 0q147 0 228 12z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-linkedin\">\n      <path d=\"M477 625v991H147V625h330zm21-306q1 73-50.5 122T312 490h-2q-82 0-132-49t-50-122q0-74 51.5-122.5T314 148t133 48.5T498 319zm1166 729v568h-329v-530q0-105-40.5-164.5T1168 862q-63 0-105.5 34.5T999 982q-11 30-11 81v553H659q2-399 2-647t-1-296l-1-48h329v144h-2q20-32 41-56t56.5-52 87-43.5T1285 602q171 0 275 113.5t104 332.5z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1
11901792 1792\" id=\"vjs-icon-twitter\">\n      <path d=\"M1684 408q-67 98-162 167 1 14 1 42 0 130-38 259.5T1369.5 1125 1185 1335.5t-258 146-323 54.5q-271 0-496-145 35 4 78 4 225 0 401-138-105-2-188-64.5T285 1033q33 5 61 5 43 0 85-11-112-23-185.5-111.5T172 710v-4q68 38 146 41-66-44-105-115t-39-154q0-88 44-163 121 149 294.5 238.5T884 653q-8-38-8-74 0-134 94.5-228.5T1199 256q140 0 236 102 109-21 205-78-37 115-142 178 93-10 186-50z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-tumblr\">\n      <path d=\"M1328 1329l80 237q-23 35-111 66t-177 32q-104 2-190.5-26T787 1564t-95-106-55.5-120-16.5-118V676H452V461q72-26 129-69.5t91-90 58-102 34-99T779 12q1-5 4.5-8.5T791 0h244v424h333v252h-334v518q0 30 6.5 56t22.5 52.5 49.5 41.5 81.5 14q78-2 134-29z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-pinterest\">\n      <path d=\"M1664 896q0 209-103 385.5T1281.5 1561 896 1664q-111 0-218-32 59-93 78-164 9-34 54-211 20 39 73 67.5t114 28.5q121 0 216-68.5t147-188.5 52-270q0-114-59.5-214T1180 449t-255-63q-105 0-196 29t-154.5 77-109 110.5-67 129.5T377 866q0 104 40 183t117 111q30 12 38-20 2-7 8-31t8-30q6-23-11-43-51-61-51-151 0-151 104.5-259.5T904 517q151 0 235.5 82t84.5 213q0 170-68.5 289T980 1220q-61 0-98-43.5T859 1072q8-35 26.5-93.5t30-103T927 800q0-50-27-83t-77-33q-62 0-105 57t-43 142q0 73 25 122l-99 418q-17 70-13 177-206-91-333-281T128 896q0-209 103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z\"></path>\n    </symbol>\n  </defs>\n</svg>";
11902
11903  /**
11904   * @file loader.js
11905   */
11906
11907  /**
11908   * The `MediaLoader` is the `Component` that decides which playback technology to load
11909   * when a player is initialized.
11910   *
11911   * @extends Component
11912   */
11913  class MediaLoader extends Component$1 {
11914    /**
11915     * Create an instance of this class.
11916     *
11917     * @param { import('../player').default } player
11918     *        The `Player` that this class should attach to.
11919     *
11920     * @param {Object} [options]
11921     *        The key/value store of player options.
11922     *
11923     * @param {Function} [ready]
11924     *        The function that is run when this component is ready.
11925     */
11926    constructor(player, options, ready) {
11927      // MediaLoader has no element
11928      const options_ = merge$2({
11929        createEl: false
11930      }, options);
11931      super(player, options_, ready);
11932
11933      // If there are no sources when the player is initialized,
11934      // load the first supported playback technology.
11935
11936      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
11937        for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
11938          const techName = toTitleCase$1(j[i]);
11939          let tech = Tech.getTech(techName);
11940
vendor: 5,851 bytes, lines 11941-12133
11941          // Support old behavior of techs being registered as components.
11942          // Remove once that deprecated behavior is removed.
11943          if (!techName) {
11944            tech = Component$1.getComponent(techName);
11945          }
11946
11947          // Check if the browser supports this technology
11948          if (tech && tech.isSupported()) {
11949            player.loadTech_(techName);
11950            break;
11951          }
11952        }
11953      } else {
11954        // Loop through playback technologies (e.g. HTML5) and check for support.
11955        // Then load the best source.
11956        // A few assumptions here:
11957        //   All playback technologies respect preload false.
11958        player.src(options.playerOptions.sources);
11959      }
11960    }
11961  }
11962  Component$1.registerComponent('MediaLoader', MediaLoader);
11963
11964  /**
11965   * @file clickable-component.js
11966   */
11967
11968  /**
11969   * Component which is clickable or keyboard actionable, but is not a
11970   * native HTML button.
11971   *
11972   * @extends Component
11973   */
11974  class ClickableComponent extends Component$1 {
11975    /**
11976     * Creates an instance of this class.
11977     *
11978     * @param  { import('./player').default } player
11979     *         The `Player` that this class should be attached to.
11980     *
11981     * @param  {Object} [options]
11982     *         The key/value store of component options.
11983     *
11984     * @param  {function} [options.clickHandler]
11985     *         The function to call when the button is clicked / activated
11986     *
11987     * @param  {string} [options.controlText]
11988     *         The text to set on the button
11989     *
11990     * @param  {string} [options.className]
11991     *         A class or space separated list of classes to add the component
11992     *
11993     */
11994    constructor(player, options) {
11995      super(player, options);
11996      if (this.options_.controlText) {
11997        this.controlText(this.options_.controlText);
11998      }
11999      this.handleMouseOver_ = e => this.handleMouseOver(e);
12000      this.handleMouseOut_ = e => this.handleMouseOut(e);
12001      this.handleClick_ = e => this.handleClick(e);
12002      this.handleKeyDown_ = e => this.handleKeyDown(e);
12003      this.emitTapEvents();
12004      this.enable();
12005    }
12006
12007    /**
12008     * Create the `ClickableComponent`s DOM element.
12009     *
12010     * @param {string} [tag=div]
12011     *        The element's node type.
12012     *
12013     * @param {Object} [props={}]
12014     *        An object of properties that should be set on the element.
12015     *
12016     * @param {Object} [attributes={}]
12017     *        An object of attributes that should be set on the element.
12018     *
12019     * @return {Element}
12020     *         The element that gets created.
12021     */
12022    createEl(tag = 'div', props = {}, attributes = {}) {
12023      props = Object.assign({
12024        className: this.buildCSSClass(),
12025        tabIndex: 0
12026      }, props);
12027      if (tag === 'button') {
12028        log$1.error(`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`);
12029      }
12030
12031      // Add ARIA attributes for clickable element which is not a native HTML button
12032      attributes = Object.assign({
12033        role: 'button'
12034      }, attributes);
12035      this.tabIndex_ = props.tabIndex;
12036      const el = createEl(tag, props, attributes);
12037      if (!this.player_.options_.experimentalSvgIcons) {
12038        el.appendChild(createEl('span', {
12039          className: 'vjs-icon-placeholder'
12040        }, {
12041          'aria-hidden': true
12042        }));
12043      }
12044      this.createControlTextEl(el);
12045      return el;
12046    }
12047    dispose() {
12048      // remove controlTextEl_ on dispose
12049      this.controlTextEl_ = null;
12050      super.dispose();
12051    }
12052
12053    /**
12054     * Create a control text element on this `ClickableComponent`
12055     *
12056     * @param {Element} [el]
12057     *        Parent element for the control text.
12058     *
12059     * @return {Element}
12060     *         The control text element that gets created.
12061     */
12062    createControlTextEl(el) {
12063      this.controlTextEl_ = createEl('span', {
12064        className: 'vjs-control-text'
12065      }, {
12066        // let the screen reader user know that the text of the element may change
12067        'aria-live': 'polite'
12068      });
12069      if (el) {
12070        el.appendChild(this.controlTextEl_);
12071      }
12072      this.controlText(this.controlText_, el);
12073      return this.controlTextEl_;
12074    }
12075
12076    /**
12077     * Get or set the localize text to use for the controls on the `ClickableComponent`.
12078     *
12079     * @param {string} [text]
12080     *        Control text for element.
12081     *
12082     * @param {Element} [el=this.el()]
12083     *        Element to set the title on.
12084     *
12085     * @return {string}
12086     *         - The control text when getting
12087     */
12088    controlText(text, el = this.el()) {
12089      if (text === undefined) {
12090        return this.controlText_ || 'Need Text';
12091      }
12092      const localizedText = this.localize(text);
12093
12094      /** @protected */
12095      this.controlText_ = text;
12096      textContent(this.controlTextEl_, localizedText);
12097      if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
12098        // Set title attribute if only an icon is shown
12099        el.setAttribute('title', localizedText);
12100      }
12101    }
12102
12103    /**
12104     * Builds the default DOM `className`.
12105     *
12106     * @return {string}
12107     *         The DOM `className` for this object.
12108     */
12109    buildCSSClass() {
12110      return `vjs-control vjs-button ${super.buildCSSClass()}`;
12111    }
12112
12113    /**
12114     * Enable this `ClickableComponent`
12115     */
12116    enable() {
12117      if (!this.enabled_) {
12118        this.enabled_ = true;
12119        this.removeClass('vjs-disabled');
12120        this.el_.setAttribute('aria-disabled', 'false');
12121        if (typeof this.tabIndex_ !== 'undefined') {
12122          this.el_.setAttribute('tabIndex', this.tabIndex_);
12123        }
12124        this.on(['tap', 'click'], this.handleClick_);
12125        this.on('keydown', this.handleKeyDown_);
12126      }
12127    }
12128
12129    /**
12130     * Disable this `ClickableComponent`
12131     */
12132    disable() {
12133      this.enabled_ = false;
vendor: 7,818 bytes, lines 12134-12385
12134      this.addClass('vjs-disabled');
12135      this.el_.setAttribute('aria-disabled', 'true');
12136      if (typeof this.tabIndex_ !== 'undefined') {
12137        this.el_.removeAttribute('tabIndex');
12138      }
12139      this.off('mouseover', this.handleMouseOver_);
12140      this.off('mouseout', this.handleMouseOut_);
12141      this.off(['tap', 'click'], this.handleClick_);
12142      this.off('keydown', this.handleKeyDown_);
12143    }
12144
12145    /**
12146     * Handles language change in ClickableComponent for the player in components
12147     *
12148     *
12149     */
12150    handleLanguagechange() {
12151      this.controlText(this.controlText_);
12152    }
12153
12154    /**
12155     * Event handler that is called when a `ClickableComponent` receives a
12156     * `click` or `tap` event.
12157     *
12158     * @param {Event} event
12159     *        The `tap` or `click` event that caused this function to be called.
12160     *
12161     * @listens tap
12162     * @listens click
12163     * @abstract
12164     */
12165    handleClick(event) {
12166      if (this.options_.clickHandler) {
12167        this.options_.clickHandler.call(this, arguments);
12168      }
12169    }
12170
12171    /**
12172     * Event handler that is called when a `ClickableComponent` receives a
12173     * `keydown` event.
12174     *
12175     * By default, if the key is Space or Enter, it will trigger a `click` event.
12176     *
12177     * @param {KeyboardEvent} event
12178     *        The `keydown` event that caused this function to be called.
12179     *
12180     * @listens keydown
12181     */
12182    handleKeyDown(event) {
12183      // Support Space or Enter key operation to fire a click event. Also,
12184      // prevent the event from propagating through the DOM and triggering
12185      // Player hotkeys.
12186      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12187        event.preventDefault();
12188        event.stopPropagation();
12189        this.trigger('click');
12190      } else {
12191        // Pass keypress handling up for unsupported keys
12192        super.handleKeyDown(event);
12193      }
12194    }
12195  }
12196  Component$1.registerComponent('ClickableComponent', ClickableComponent);
12197
12198  /**
12199   * @file poster-image.js
12200   */
12201
12202  /**
12203   * A `ClickableComponent` that handles showing the poster image for the player.
12204   *
12205   * @extends ClickableComponent
12206   */
12207  class PosterImage extends ClickableComponent {
12208    /**
12209     * Create an instance of this class.
12210     *
12211     * @param { import('./player').default } player
12212     *        The `Player` that this class should attach to.
12213     *
12214     * @param {Object} [options]
12215     *        The key/value store of player options.
12216     */
12217    constructor(player, options) {
12218      super(player, options);
12219      this.update();
12220      this.update_ = e => this.update(e);
12221      player.on('posterchange', this.update_);
12222    }
12223
12224    /**
12225     * Clean up and dispose of the `PosterImage`.
12226     */
12227    dispose() {
12228      this.player().off('posterchange', this.update_);
12229      super.dispose();
12230    }
12231
12232    /**
12233     * Create the `PosterImage`s DOM element.
12234     *
12235     * @return {Element}
12236     *         The element that gets created.
12237     */
12238    createEl() {
12239      // The el is an empty div to keep position in the DOM
12240      // A picture and img el will be inserted when a source is set
12241      return createEl('div', {
12242        className: 'vjs-poster'
12243      });
12244    }
12245
12246    /**
12247     * Get or set the `PosterImage`'s crossOrigin option.
12248     *
12249     * @param {string|null} [value]
12250     *        The value to set the crossOrigin to. If an argument is
12251     *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
12252     *
12253     * @return {string|null}
12254     *         - The current crossOrigin value of the `Player` when getting.
12255     *         - undefined when setting
12256     */
12257    crossOrigin(value) {
12258      // `null` can be set to unset a value
12259      if (typeof value === 'undefined') {
12260        if (this.$('img')) {
12261          // If the poster's element exists, give its value
12262          return this.$('img').crossOrigin;
12263        } else if (this.player_.tech_ && this.player_.tech_.isReady_) {
12264          // If not but the tech is ready, query the tech
12265          return this.player_.crossOrigin();
12266        }
12267        // Otherwise check options as the  poster is usually set before the state of crossorigin
12268        // can be retrieved by the getter
12269        return this.player_.options_.crossOrigin || this.player_.options_.crossorigin || null;
12270      }
12271      if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
12272        this.player_.log.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
12273        return;
12274      }
12275      if (this.$('img')) {
12276        this.$('img').crossOrigin = value;
12277      }
12278      return;
12279    }
12280
12281    /**
12282     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
12283     *
12284     * @listens Player#posterchange
12285     *
12286     * @param {Event} [event]
12287     *        The `Player#posterchange` event that triggered this function.
12288     */
12289    update(event) {
12290      const url = this.player().poster();
12291      this.setSrc(url);
12292
12293      // If there's no poster source we should display:none on this component
12294      // so it's not still clickable or right-clickable
12295      if (url) {
12296        this.show();
12297      } else {
12298        this.hide();
12299      }
12300    }
12301
12302    /**
12303     * Set the source of the `PosterImage` depending on the display method. (Re)creates
12304     * the inner picture and img elementss when needed.
12305     *
12306     * @param {string} [url]
12307     *        The URL to the source for the `PosterImage`. If not specified or falsy,
12308     *        any source and ant inner picture/img are removed.
12309     */
12310    setSrc(url) {
12311      if (!url) {
12312        this.el_.textContent = '';
12313        return;
12314      }
12315      if (!this.$('img')) {
12316        this.el_.appendChild(createEl('picture', {
12317          className: 'vjs-poster',
12318          // Don't want poster to be tabbable.
12319          tabIndex: -1
12320        }, {}, createEl('img', {
12321          loading: 'lazy',
12322          crossOrigin: this.crossOrigin()
12323        }, {
12324          alt: ''
12325        })));
12326      }
12327      this.$('img').src = url;
12328    }
12329
12330    /**
12331     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
12332     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
12333     *
12334     * @listens tap
12335     * @listens click
12336     * @listens keydown
12337     *
12338     * @param {Event} event
12339     +        The `click`, `tap` or `keydown` event that caused this function to be called.
12340     */
12341    handleClick(event) {
12342      // We don't want a click to trigger playback when controls are disabled
12343      if (!this.player_.controls()) {
12344        return;
12345      }
12346      if (this.player_.tech(true)) {
12347        this.player_.tech(true).focus();
12348      }
12349      if (this.player_.paused()) {
12350        silencePromise(this.player_.play());
12351      } else {
12352        this.player_.pause();
12353      }
12354    }
12355  }
12356
12357  /**
12358   * Get or set the `PosterImage`'s crossorigin option. For the HTML5 player, this
12359   * sets the `crossOrigin` property on the `<img>` tag to control the CORS
12360   * behavior.
12361   *
12362   * @param {string|null} [value]
12363   *        The value to set the `PosterImages`'s crossorigin to. If an argument is
12364   *        given, must be one of `anonymous` or `use-credentials`.
12365   *
12366   * @return {string|null|undefined}
12367   *         - The current crossorigin value of the `Player` when getting.
12368   *         - undefined when setting
12369   */
12370  PosterImage.prototype.crossorigin = PosterImage.prototype.crossOrigin;
12371  Component$1.registerComponent('PosterImage', PosterImage);
12372
12373  /**
12374   * @file text-track-display.js
12375   */
12376  const darkGray = '#222';
12377  const lightGray = '#ccc';
12378  const fontMap = {
12379    monospace: 'monospace',
12380    sansSerif: 'sans-serif',
12381    serif: 'serif',
12382    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
12383    monospaceSerif: '"Courier New", monospace',
12384    proportionalSansSerif: 'sans-serif',
12385    proportionalSerif: 'serif',
vendor: 20,012 bytes, lines 12386-12968
12386    casual: '"Comic Sans MS", Impact, fantasy',
12387    script: '"Monotype Corsiva", cursive',
12388    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
12389  };
12390
12391  /**
12392   * Construct an rgba color from a given hex color code.
12393   *
12394   * @param {number} color
12395   *        Hex number for color, like #f0e or #f604e2.
12396   *
12397   * @param {number} opacity
12398   *        Value for opacity, 0.0 - 1.0.
12399   *
12400   * @return {string}
12401   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
12402   */
12403  function constructColor(color, opacity) {
12404    let hex;
12405    if (color.length === 4) {
12406      // color looks like "#f0e"
12407      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
12408    } else if (color.length === 7) {
12409      // color looks like "#f604e2"
12410      hex = color.slice(1);
12411    } else {
12412      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
12413    }
12414    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
12415  }
12416
12417  /**
12418   * Try to update the style of a DOM element. Some style changes will throw an error,
12419   * particularly in IE8. Those should be noops.
12420   *
12421   * @param {Element} el
12422   *        The DOM element to be styled.
12423   *
12424   * @param {string} style
12425   *        The CSS property on the element that should be styled.
12426   *
12427   * @param {string} rule
12428   *        The style rule that should be applied to the property.
12429   *
12430   * @private
12431   */
12432  function tryUpdateStyle(el, style, rule) {
12433    try {
12434      el.style[style] = rule;
12435    } catch (e) {
12436      // Satisfies linter.
12437      return;
12438    }
12439  }
12440
12441  /**
12442   * Converts the CSS top/right/bottom/left property numeric value to string in pixels.
12443   *
12444   * @param {number} position
12445   *        The CSS top/right/bottom/left property value.
12446   *
12447   * @return {string}
12448   *          The CSS property value that was created, like '10px'.
12449   *
12450   * @private
12451   */
12452  function getCSSPositionValue(position) {
12453    return position ? `${position}px` : '';
12454  }
12455
12456  /**
12457   * The component for displaying text track cues.
12458   *
12459   * @extends Component
12460   */
12461  class TextTrackDisplay extends Component$1 {
12462    /**
12463     * Creates an instance of this class.
12464     *
12465     * @param { import('../player').default } player
12466     *        The `Player` that this class should be attached to.
12467     *
12468     * @param {Object} [options]
12469     *        The key/value store of player options.
12470     *
12471     * @param {Function} [ready]
12472     *        The function to call when `TextTrackDisplay` is ready.
12473     */
12474    constructor(player, options, ready) {
12475      super(player, options, ready);
12476      const updateDisplayTextHandler = e => this.updateDisplay(e);
12477      const updateDisplayHandler = e => {
12478        this.updateDisplayOverlay();
12479        this.updateDisplay(e);
12480      };
12481      player.on('loadstart', e => this.toggleDisplay(e));
12482      player.on('texttrackchange', updateDisplayTextHandler);
12483      player.on('loadedmetadata', e => {
12484        this.updateDisplayOverlay();
12485        this.preselectTrack(e);
12486      });
12487
12488      // This used to be called during player init, but was causing an error
12489      // if a track should show by default and the display hadn't loaded yet.
12490      // Should probably be moved to an external track loader when we support
12491      // tracks that don't need a display.
12492      player.ready(bind_(this, function () {
12493        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12494          this.hide();
12495          return;
12496        }
12497        player.on('fullscreenchange', updateDisplayHandler);
12498        player.on('playerresize', updateDisplayHandler);
12499        const screenOrientation = window.screen.orientation || window;
12500        const changeOrientationEvent = window.screen.orientation ? 'change' : 'orientationchange';
12501        screenOrientation.addEventListener(changeOrientationEvent, updateDisplayHandler);
12502        player.on('dispose', () => screenOrientation.removeEventListener(changeOrientationEvent, updateDisplayHandler));
12503        const tracks = this.options_.playerOptions.tracks || [];
12504        for (let i = 0; i < tracks.length; i++) {
12505          this.player_.addRemoteTextTrack(tracks[i], true);
12506        }
12507        this.preselectTrack();
12508      }));
12509    }
12510
12511    /**
12512    * Preselect a track following this precedence:
12513    * - matches the previously selected {@link TextTrack}'s language and kind
12514    * - matches the previously selected {@link TextTrack}'s language only
12515    * - is the first default captions track
12516    * - is the first default descriptions track
12517    *
12518    * @listens Player#loadstart
12519    */
12520    preselectTrack() {
12521      const modes = {
12522        captions: 1,
12523        subtitles: 1
12524      };
12525      const trackList = this.player_.textTracks();
12526      const userPref = this.player_.cache_.selectedLanguage;
12527      let firstDesc;
12528      let firstCaptions;
12529      let preferredTrack;
12530      for (let i = 0; i < trackList.length; i++) {
12531        const track = trackList[i];
12532        if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
12533          // Always choose the track that matches both language and kind
12534          if (track.kind === userPref.kind) {
12535            preferredTrack = track;
12536            // or choose the first track that matches language
12537          } else if (!preferredTrack) {
12538            preferredTrack = track;
12539          }
12540
12541          // clear everything if offTextTrackMenuItem was clicked
12542        } else if (userPref && !userPref.enabled) {
12543          preferredTrack = null;
12544          firstDesc = null;
12545          firstCaptions = null;
12546        } else if (track.default) {
12547          if (track.kind === 'descriptions' && !firstDesc) {
12548            firstDesc = track;
12549          } else if (track.kind in modes && !firstCaptions) {
12550            firstCaptions = track;
12551          }
12552        }
12553      }
12554
12555      // The preferredTrack matches the user preference and takes
12556      // precedence over all the other tracks.
12557      // So, display the preferredTrack before the first default track
12558      // and the subtitles/captions track before the descriptions track
12559      if (preferredTrack) {
12560        preferredTrack.mode = 'showing';
12561      } else if (firstCaptions) {
12562        firstCaptions.mode = 'showing';
12563      } else if (firstDesc) {
12564        firstDesc.mode = 'showing';
12565      }
12566    }
12567
12568    /**
12569     * Turn display of {@link TextTrack}'s from the current state into the other state.
12570     * There are only two states:
12571     * - 'shown'
12572     * - 'hidden'
12573     *
12574     * @listens Player#loadstart
12575     */
12576    toggleDisplay() {
12577      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12578        this.hide();
12579      } else {
12580        this.show();
12581      }
12582    }
12583
12584    /**
12585     * Create the {@link Component}'s DOM element.
12586     *
12587     * @return {Element}
12588     *         The element that was created.
12589     */
12590    createEl() {
12591      return super.createEl('div', {
12592        className: 'vjs-text-track-display'
12593      }, {
12594        'translate': 'yes',
12595        'aria-live': 'off',
12596        'aria-atomic': 'true'
12597      });
12598    }
12599
12600    /**
12601     * Clear all displayed {@link TextTrack}s.
12602     */
12603    clearDisplay() {
12604      if (typeof window.WebVTT === 'function') {
12605        window.WebVTT.processCues(window, [], this.el_);
12606      }
12607    }
12608
12609    /**
12610     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12611     * a {@link Player#fullscreenchange} is fired.
12612     *
12613     * @listens Player#texttrackchange
12614     * @listens Player#fullscreenchange
12615     */
12616    updateDisplay() {
12617      const tracks = this.player_.textTracks();
12618      const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
12619      this.clearDisplay();
12620      if (allowMultipleShowingTracks) {
12621        const showingTracks = [];
12622        for (let i = 0; i < tracks.length; ++i) {
12623          const track = tracks[i];
12624          if (track.mode !== 'showing') {
12625            continue;
12626          }
12627          showingTracks.push(track);
12628        }
12629        this.updateForTrack(showingTracks);
12630        return;
12631      }
12632
12633      //  Track display prioritization model: if multiple tracks are 'showing',
12634      //  display the first 'subtitles' or 'captions' track which is 'showing',
12635      //  otherwise display the first 'descriptions' track which is 'showing'
12636
12637      let descriptionsTrack = null;
12638      let captionsSubtitlesTrack = null;
12639      let i = tracks.length;
12640      while (i--) {
12641        const track = tracks[i];
12642        if (track.mode === 'showing') {
12643          if (track.kind === 'descriptions') {
12644            descriptionsTrack = track;
12645          } else {
12646            captionsSubtitlesTrack = track;
12647          }
12648        }
12649      }
12650      if (captionsSubtitlesTrack) {
12651        if (this.getAttribute('aria-live') !== 'off') {
12652          this.setAttribute('aria-live', 'off');
12653        }
12654        this.updateForTrack(captionsSubtitlesTrack);
12655      } else if (descriptionsTrack) {
12656        if (this.getAttribute('aria-live') !== 'assertive') {
12657          this.setAttribute('aria-live', 'assertive');
12658        }
12659        this.updateForTrack(descriptionsTrack);
12660      }
12661    }
12662
12663    /**
12664     * Updates the displayed TextTrack to be sure it overlays the video when a either
12665     * a {@link Player#texttrackchange} or a {@link Player#fullscreenchange} is fired.
12666     */
12667    updateDisplayOverlay() {
12668      // inset-inline and inset-block are not supprted on old chrome, but these are
12669      // only likely to be used on TV devices
12670      if (!this.player_.videoHeight() || !window.CSS.supports('inset-inline: 10px')) {
12671        return;
12672      }
12673      const playerWidth = this.player_.currentWidth();
12674      const playerHeight = this.player_.currentHeight();
12675      const playerAspectRatio = playerWidth / playerHeight;
12676      const videoAspectRatio = this.player_.videoWidth() / this.player_.videoHeight();
12677      let insetInlineMatch = 0;
12678      let insetBlockMatch = 0;
12679      if (Math.abs(playerAspectRatio - videoAspectRatio) > 0.1) {
12680        if (playerAspectRatio > videoAspectRatio) {
12681          insetInlineMatch = Math.round((playerWidth - playerHeight * videoAspectRatio) / 2);
12682        } else {
12683          insetBlockMatch = Math.round((playerHeight - playerWidth / videoAspectRatio) / 2);
12684        }
12685      }
12686      tryUpdateStyle(this.el_, 'insetInline', getCSSPositionValue(insetInlineMatch));
12687      tryUpdateStyle(this.el_, 'insetBlock', getCSSPositionValue(insetBlockMatch));
12688    }
12689
12690    /**
12691     * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
12692     *
12693     * @param {TextTrack} track
12694     *        Text track object containing active cues to style.
12695     */
12696    updateDisplayState(track) {
12697      const overrides = this.player_.textTrackSettings.getValues();
12698      const cues = track.activeCues;
12699      let i = cues.length;
12700      while (i--) {
12701        const cue = cues[i];
12702        if (!cue) {
12703          continue;
12704        }
12705        const cueDiv = cue.displayState;
12706        if (overrides.color) {
12707          cueDiv.firstChild.style.color = overrides.color;
12708        }
12709        if (overrides.textOpacity) {
12710          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12711        }
12712        if (overrides.backgroundColor) {
12713          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12714        }
12715        if (overrides.backgroundOpacity) {
12716          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12717        }
12718        if (overrides.windowColor) {
12719          if (overrides.windowOpacity) {
12720            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12721          } else {
12722            cueDiv.style.backgroundColor = overrides.windowColor;
12723          }
12724        }
12725        if (overrides.edgeStyle) {
12726          if (overrides.edgeStyle === 'dropshadow') {
12727            cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}`;
12728          } else if (overrides.edgeStyle === 'raised') {
12729            cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}`;
12730          } else if (overrides.edgeStyle === 'depressed') {
12731            cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
12732          } else if (overrides.edgeStyle === 'uniform') {
12733            cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
12734          }
12735        }
12736        if (overrides.fontPercent && overrides.fontPercent !== 1) {
12737          const fontSize = window.parseFloat(cueDiv.style.fontSize);
12738          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12739          cueDiv.style.height = 'auto';
12740          cueDiv.style.top = 'auto';
12741        }
12742        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12743          if (overrides.fontFamily === 'small-caps') {
12744            cueDiv.firstChild.style.fontVariant = 'small-caps';
12745          } else {
12746            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12747          }
12748        }
12749      }
12750    }
12751
12752    /**
12753     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12754     *
12755     * @param {TextTrack|TextTrack[]} tracks
12756     *        Text track object or text track array to be added to the list.
12757     */
12758    updateForTrack(tracks) {
12759      if (!Array.isArray(tracks)) {
12760        tracks = [tracks];
12761      }
12762      if (typeof window.WebVTT !== 'function' || tracks.every(track => {
12763        return !track.activeCues;
12764      })) {
12765        return;
12766      }
12767      const cues = [];
12768
12769      // push all active track cues
12770      for (let i = 0; i < tracks.length; ++i) {
12771        const track = tracks[i];
12772        for (let j = 0; j < track.activeCues.length; ++j) {
12773          cues.push(track.activeCues[j]);
12774        }
12775      }
12776
12777      // removes all cues before it processes new ones
12778      window.WebVTT.processCues(window, cues, this.el_);
12779
12780      // add unique class to each language text track & add settings styling if necessary
12781      for (let i = 0; i < tracks.length; ++i) {
12782        const track = tracks[i];
12783        for (let j = 0; j < track.activeCues.length; ++j) {
12784          const cueEl = track.activeCues[j].displayState;
12785          addClass(cueEl, 'vjs-text-track-cue', 'vjs-text-track-cue-' + (track.language ? track.language : i));
12786          if (track.language) {
12787            setAttribute(cueEl, 'lang', track.language);
12788          }
12789        }
12790        if (this.player_.textTrackSettings) {
12791          this.updateDisplayState(track);
12792        }
12793      }
12794    }
12795  }
12796  Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
12797
12798  /**
12799   * @file loading-spinner.js
12800   */
12801
12802  /**
12803   * A loading spinner for use during waiting/loading events.
12804   *
12805   * @extends Component
12806   */
12807  class LoadingSpinner extends Component$1 {
12808    /**
12809     * Create the `LoadingSpinner`s DOM element.
12810     *
12811     * @return {Element}
12812     *         The dom element that gets created.
12813     */
12814    createEl() {
12815      const isAudio = this.player_.isAudio();
12816      const playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12817      const controlText = createEl('span', {
12818        className: 'vjs-control-text',
12819        textContent: this.localize('{1} is loading.', [playerType])
12820      });
12821      const el = super.createEl('div', {
12822        className: 'vjs-loading-spinner',
12823        dir: 'ltr'
12824      });
12825      el.appendChild(controlText);
12826      return el;
12827    }
12828
12829    /**
12830     * Update control text on languagechange
12831     */
12832    handleLanguagechange() {
12833      this.$('.vjs-control-text').textContent = this.localize('{1} is loading.', [this.player_.isAudio() ? 'Audio Player' : 'Video Player']);
12834    }
12835  }
12836  Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
12837
12838  /**
12839   * @file button.js
12840   */
12841
12842  /**
12843   * Base class for all buttons.
12844   *
12845   * @extends ClickableComponent
12846   */
12847  class Button extends ClickableComponent {
12848    /**
12849     * Create the `Button`s DOM element.
12850     *
12851     * @param {string} [tag="button"]
12852     *        The element's node type. This argument is IGNORED: no matter what
12853     *        is passed, it will always create a `button` element.
12854     *
12855     * @param {Object} [props={}]
12856     *        An object of properties that should be set on the element.
12857     *
12858     * @param {Object} [attributes={}]
12859     *        An object of attributes that should be set on the element.
12860     *
12861     * @return {Element}
12862     *         The element that gets created.
12863     */
12864    createEl(tag, props = {}, attributes = {}) {
12865      tag = 'button';
12866      props = Object.assign({
12867        className: this.buildCSSClass()
12868      }, props);
12869
12870      // Add attributes for button element
12871      attributes = Object.assign({
12872        // Necessary since the default button type is "submit"
12873        type: 'button'
12874      }, attributes);
12875      const el = createEl(tag, props, attributes);
12876      if (!this.player_.options_.experimentalSvgIcons) {
12877        el.appendChild(createEl('span', {
12878          className: 'vjs-icon-placeholder'
12879        }, {
12880          'aria-hidden': true
12881        }));
12882      }
12883      this.createControlTextEl(el);
12884      return el;
12885    }
12886
12887    /**
12888     * Add a child `Component` inside of this `Button`.
12889     *
12890     * @param {string|Component} child
12891     *        The name or instance of a child to add.
12892     *
12893     * @param {Object} [options={}]
12894     *        The key/value store of options that will get passed to children of
12895     *        the child.
12896     *
12897     * @return {Component}
12898     *         The `Component` that gets added as a child. When using a string the
12899     *         `Component` will get created by this process.
12900     *
12901     * @deprecated since version 5
12902     */
12903    addChild(child, options = {}) {
12904      const className = this.constructor.name;
12905      log$1.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`);
12906
12907      // Avoid the error message generated by ClickableComponent's addChild method
12908      return Component$1.prototype.addChild.call(this, child, options);
12909    }
12910
12911    /**
12912     * Enable the `Button` element so that it can be activated or clicked. Use this with
12913     * {@link Button#disable}.
12914     */
12915    enable() {
12916      super.enable();
12917      this.el_.removeAttribute('disabled');
12918    }
12919
12920    /**
12921     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12922     * {@link Button#enable}.
12923     */
12924    disable() {
12925      super.disable();
12926      this.el_.setAttribute('disabled', 'disabled');
12927    }
12928
12929    /**
12930     * This gets called when a `Button` has focus and `keydown` is triggered via a key
12931     * press.
12932     *
12933     * @param {KeyboardEvent} event
12934     *        The event that caused this function to get called.
12935     *
12936     * @listens keydown
12937     */
12938    handleKeyDown(event) {
12939      // Ignore Space or Enter key operation, which is handled by the browser for
12940      // a button - though not for its super class, ClickableComponent. Also,
12941      // prevent the event from propagating through the DOM and triggering Player
12942      // hotkeys. We do not preventDefault here because we _want_ the browser to
12943      // handle it.
12944      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12945        event.stopPropagation();
12946        return;
12947      }
12948
12949      // Pass keypress handling up for unsupported keys
12950      super.handleKeyDown(event);
12951    }
12952  }
12953  Component$1.registerComponent('Button', Button);
12954
12955  /**
12956   * @file big-play-button.js
12957   */
12958
12959  /**
12960   * The initial play button that shows before the video has played. The hiding of the
12961   * `BigPlayButton` get done via CSS and `Player` states.
12962   *
12963   * @extends Button
12964   */
12965  class BigPlayButton extends Button {
12966    constructor(player, options) {
12967      super(player, options);
12968      this.mouseused_ = false;
vendor: 4,353 bytes, lines 12969-13126
12969      this.setIcon('play');
12970      this.on('mousedown', e => this.handleMouseDown(e));
12971    }
12972
12973    /**
12974     * Builds the default DOM `className`.
12975     *
12976     * @return {string}
12977     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12978     */
12979    buildCSSClass() {
12980      return 'vjs-big-play-button';
12981    }
12982
12983    /**
12984     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12985     * for more detailed information on what a click can be.
12986     *
12987     * @param {KeyboardEvent|MouseEvent|TouchEvent} event
12988     *        The `keydown`, `tap`, or `click` event that caused this function to be
12989     *        called.
12990     *
12991     * @listens tap
12992     * @listens click
12993     */
12994    handleClick(event) {
12995      const playPromise = this.player_.play();
12996
12997      // exit early if clicked via the mouse
12998      if (this.mouseused_ && 'clientX' in event && 'clientY' in event) {
12999        silencePromise(playPromise);
13000        if (this.player_.tech(true)) {
13001          this.player_.tech(true).focus();
13002        }
13003        return;
13004      }
13005      const cb = this.player_.getChild('controlBar');
13006      const playToggle = cb && cb.getChild('playToggle');
13007      if (!playToggle) {
13008        this.player_.tech(true).focus();
13009        return;
13010      }
13011      const playFocus = () => playToggle.focus();
13012      if (isPromise(playPromise)) {
13013        playPromise.then(playFocus, () => {});
13014      } else {
13015        this.setTimeout(playFocus, 1);
13016      }
13017    }
13018
13019    /**
13020     * Event handler that is called when a `BigPlayButton` receives a
13021     * `keydown` event.
13022     *
13023     * @param {KeyboardEvent} event
13024     *        The `keydown` event that caused this function to be called.
13025     *
13026     * @listens keydown
13027     */
13028    handleKeyDown(event) {
13029      this.mouseused_ = false;
13030      super.handleKeyDown(event);
13031    }
13032
13033    /**
13034     * Handle `mousedown` events on the `BigPlayButton`.
13035     *
13036     * @param {MouseEvent} event
13037     *        `mousedown` or `touchstart` event that triggered this function
13038     *
13039     * @listens mousedown
13040     */
13041    handleMouseDown(event) {
13042      this.mouseused_ = true;
13043    }
13044  }
13045
13046  /**
13047   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
13048   *
13049   * @type {string}
13050   * @protected
13051   */
13052  BigPlayButton.prototype.controlText_ = 'Play Video';
13053  Component$1.registerComponent('BigPlayButton', BigPlayButton);
13054
13055  /**
13056   * @file close-button.js
13057   */
13058
13059  /**
13060   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
13061   * it gets clicked.
13062   *
13063   * @extends Button
13064   */
13065  class CloseButton extends Button {
13066    /**
13067    * Creates an instance of the this class.
13068    *
13069    * @param  { import('./player').default } player
13070    *         The `Player` that this class should be attached to.
13071    *
13072    * @param  {Object} [options]
13073    *         The key/value store of player options.
13074    */
13075    constructor(player, options) {
13076      super(player, options);
13077      this.setIcon('cancel');
13078      this.controlText(options && options.controlText || this.localize('Close'));
13079    }
13080
13081    /**
13082    * Builds the default DOM `className`.
13083    *
13084    * @return {string}
13085    *         The DOM `className` for this object.
13086    */
13087    buildCSSClass() {
13088      return `vjs-close-button ${super.buildCSSClass()}`;
13089    }
13090
13091    /**
13092     * This gets called when a `CloseButton` gets clicked. See
13093     * {@link ClickableComponent#handleClick} for more information on when
13094     * this will be triggered
13095     *
13096     * @param {Event} event
13097     *        The `keydown`, `tap`, or `click` event that caused this function to be
13098     *        called.
13099     *
13100     * @listens tap
13101     * @listens click
13102     * @fires CloseButton#close
13103     */
13104    handleClick(event) {
13105      /**
13106       * Triggered when the a `CloseButton` is clicked.
13107       *
13108       * @event CloseButton#close
13109       * @type {Event}
13110       *
13111       * @property {boolean} [bubbles=false]
13112       *           set to false so that the close event does not
13113       *           bubble up to parents if there is no listener
13114       */
13115      this.trigger({
13116        type: 'close',
13117        bubbles: false
13118      });
13119    }
13120    /**
13121     * Event handler that is called when a `CloseButton` receives a
13122     * `keydown` event.
13123     *
13124     * By default, if the key is Esc, it will trigger a `click` event.
13125     *
13126     * @param {KeyboardEvent} event
vendor: 20,330 bytes, lines 13127-13851
13127     *        The `keydown` event that caused this function to be called.
13128     *
13129     * @listens keydown
13130     */
13131    handleKeyDown(event) {
13132      // Esc button will trigger `click` event
13133      if (keycode.isEventKey(event, 'Esc')) {
13134        event.preventDefault();
13135        event.stopPropagation();
13136        this.trigger('click');
13137      } else {
13138        // Pass keypress handling up for unsupported keys
13139        super.handleKeyDown(event);
13140      }
13141    }
13142  }
13143  Component$1.registerComponent('CloseButton', CloseButton);
13144
13145  /**
13146   * @file play-toggle.js
13147   */
13148
13149  /**
13150   * Button to toggle between play and pause.
13151   *
13152   * @extends Button
13153   */
13154  class PlayToggle extends Button {
13155    /**
13156     * Creates an instance of this class.
13157     *
13158     * @param { import('./player').default } player
13159     *        The `Player` that this class should be attached to.
13160     *
13161     * @param {Object} [options={}]
13162     *        The key/value store of player options.
13163     */
13164    constructor(player, options = {}) {
13165      super(player, options);
13166
13167      // show or hide replay icon
13168      options.replay = options.replay === undefined || options.replay;
13169      this.setIcon('play');
13170      this.on(player, 'play', e => this.handlePlay(e));
13171      this.on(player, 'pause', e => this.handlePause(e));
13172      if (options.replay) {
13173        this.on(player, 'ended', e => this.handleEnded(e));
13174      }
13175    }
13176
13177    /**
13178     * Builds the default DOM `className`.
13179     *
13180     * @return {string}
13181     *         The DOM `className` for this object.
13182     */
13183    buildCSSClass() {
13184      return `vjs-play-control ${super.buildCSSClass()}`;
13185    }
13186
13187    /**
13188     * This gets called when an `PlayToggle` is "clicked". See
13189     * {@link ClickableComponent} for more detailed information on what a click can be.
13190     *
13191     * @param {Event} [event]
13192     *        The `keydown`, `tap`, or `click` event that caused this function to be
13193     *        called.
13194     *
13195     * @listens tap
13196     * @listens click
13197     */
13198    handleClick(event) {
13199      if (this.player_.paused()) {
13200        silencePromise(this.player_.play());
13201      } else {
13202        this.player_.pause();
13203      }
13204    }
13205
13206    /**
13207     * This gets called once after the video has ended and the user seeks so that
13208     * we can change the replay button back to a play button.
13209     *
13210     * @param {Event} [event]
13211     *        The event that caused this function to run.
13212     *
13213     * @listens Player#seeked
13214     */
13215    handleSeeked(event) {
13216      this.removeClass('vjs-ended');
13217      if (this.player_.paused()) {
13218        this.handlePause(event);
13219      } else {
13220        this.handlePlay(event);
13221      }
13222    }
13223
13224    /**
13225     * Add the vjs-playing class to the element so it can change appearance.
13226     *
13227     * @param {Event} [event]
13228     *        The event that caused this function to run.
13229     *
13230     * @listens Player#play
13231     */
13232    handlePlay(event) {
13233      this.removeClass('vjs-ended', 'vjs-paused');
13234      this.addClass('vjs-playing');
13235      // change the button text to "Pause"
13236      this.setIcon('pause');
13237      this.controlText('Pause');
13238    }
13239
13240    /**
13241     * Add the vjs-paused class to the element so it can change appearance.
13242     *
13243     * @param {Event} [event]
13244     *        The event that caused this function to run.
13245     *
13246     * @listens Player#pause
13247     */
13248    handlePause(event) {
13249      this.removeClass('vjs-playing');
13250      this.addClass('vjs-paused');
13251      // change the button text to "Play"
13252      this.setIcon('play');
13253      this.controlText('Play');
13254    }
13255
13256    /**
13257     * Add the vjs-ended class to the element so it can change appearance
13258     *
13259     * @param {Event} [event]
13260     *        The event that caused this function to run.
13261     *
13262     * @listens Player#ended
13263     */
13264    handleEnded(event) {
13265      this.removeClass('vjs-playing');
13266      this.addClass('vjs-ended');
13267      // change the button text to "Replay"
13268      this.setIcon('replay');
13269      this.controlText('Replay');
13270
13271      // on the next seek remove the replay button
13272      this.one(this.player_, 'seeked', e => this.handleSeeked(e));
13273    }
13274  }
13275
13276  /**
13277   * The text that should display over the `PlayToggle`s controls. Added for localization.
13278   *
13279   * @type {string}
13280   * @protected
13281   */
13282  PlayToggle.prototype.controlText_ = 'Play';
13283  Component$1.registerComponent('PlayToggle', PlayToggle);
13284
13285  /**
13286   * @file time-display.js
13287   */
13288
13289  /**
13290   * Displays time information about the video
13291   *
13292   * @extends Component
13293   */
13294  class TimeDisplay extends Component$1 {
13295    /**
13296     * Creates an instance of this class.
13297     *
13298     * @param { import('../../player').default } player
13299     *        The `Player` that this class should be attached to.
13300     *
13301     * @param {Object} [options]
13302     *        The key/value store of player options.
13303     */
13304    constructor(player, options) {
13305      super(player, options);
13306      this.on(player, ['timeupdate', 'ended', 'seeking'], e => this.update(e));
13307      this.updateTextNode_();
13308    }
13309
13310    /**
13311     * Create the `Component`'s DOM element
13312     *
13313     * @return {Element}
13314     *         The element that was created.
13315     */
13316    createEl() {
13317      const className = this.buildCSSClass();
13318      const el = super.createEl('div', {
13319        className: `${className} vjs-time-control vjs-control`
13320      });
13321      const span = createEl('span', {
13322        className: 'vjs-control-text',
13323        textContent: `${this.localize(this.labelText_)}\u00a0`
13324      }, {
13325        role: 'presentation'
13326      });
13327      el.appendChild(span);
13328      this.contentEl_ = createEl('span', {
13329        className: `${className}-display`
13330      }, {
13331        // span elements have no implicit role, but some screen readers (notably VoiceOver)
13332        // treat them as a break between items in the DOM when using arrow keys
13333        // (or left-to-right swipes on iOS) to read contents of a page. Using
13334        // role='presentation' causes VoiceOver to NOT treat this span as a break.
13335        role: 'presentation'
13336      });
13337      el.appendChild(this.contentEl_);
13338      return el;
13339    }
13340    dispose() {
13341      this.contentEl_ = null;
13342      this.textNode_ = null;
13343      super.dispose();
13344    }
13345
13346    /**
13347     * Updates the displayed time according to the `updateContent` function which is defined in the child class.
13348     *
13349     * @param {Event} [event]
13350     *          The `timeupdate`, `ended` or `seeking` (if enableSmoothSeeking is true) event that caused this function to be called.
13351     */
13352    update(event) {
13353      if (!this.player_.options_.enableSmoothSeeking && event.type === 'seeking') {
13354        return;
13355      }
13356      this.updateContent(event);
13357    }
13358
13359    /**
13360     * Updates the time display text node with a new time
13361     *
13362     * @param {number} [time=0] the time to update to
13363     *
13364     * @private
13365     */
13366    updateTextNode_(time = 0) {
13367      time = formatTime(time);
13368      if (this.formattedTime_ === time) {
13369        return;
13370      }
13371      this.formattedTime_ = time;
13372      this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', () => {
13373        if (!this.contentEl_) {
13374          return;
13375        }
13376        let oldNode = this.textNode_;
13377        if (oldNode && this.contentEl_.firstChild !== oldNode) {
13378          oldNode = null;
13379          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.');
13380        }
13381        this.textNode_ = document.createTextNode(this.formattedTime_);
13382        if (!this.textNode_) {
13383          return;
13384        }
13385        if (oldNode) {
13386          this.contentEl_.replaceChild(this.textNode_, oldNode);
13387        } else {
13388          this.contentEl_.appendChild(this.textNode_);
13389        }
13390      });
13391    }
13392
13393    /**
13394     * To be filled out in the child class, should update the displayed time
13395     * in accordance with the fact that the current time has changed.
13396     *
13397     * @param {Event} [event]
13398     *        The `timeupdate`  event that caused this to run.
13399     *
13400     * @listens Player#timeupdate
13401     */
13402    updateContent(event) {}
13403  }
13404
13405  /**
13406   * The text that is added to the `TimeDisplay` for screen reader users.
13407   *
13408   * @type {string}
13409   * @private
13410   */
13411  TimeDisplay.prototype.labelText_ = 'Time';
13412
13413  /**
13414   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13415   *
13416   * @type {string}
13417   * @protected
13418   *
13419   * @deprecated in v7; controlText_ is not used in non-active display Components
13420   */
13421  TimeDisplay.prototype.controlText_ = 'Time';
13422  Component$1.registerComponent('TimeDisplay', TimeDisplay);
13423
13424  /**
13425   * @file current-time-display.js
13426   */
13427
13428  /**
13429   * Displays the current time
13430   *
13431   * @extends Component
13432   */
13433  class CurrentTimeDisplay extends TimeDisplay {
13434    /**
13435     * Builds the default DOM `className`.
13436     *
13437     * @return {string}
13438     *         The DOM `className` for this object.
13439     */
13440    buildCSSClass() {
13441      return 'vjs-current-time';
13442    }
13443
13444    /**
13445     * Update current time display
13446     *
13447     * @param {Event} [event]
13448     *        The `timeupdate` event that caused this function to run.
13449     *
13450     * @listens Player#timeupdate
13451     */
13452    updateContent(event) {
13453      // Allows for smooth scrubbing, when player can't keep up.
13454      let time;
13455      if (this.player_.ended()) {
13456        time = this.player_.duration();
13457      } else {
13458        time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13459      }
13460      this.updateTextNode_(time);
13461    }
13462  }
13463
13464  /**
13465   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13466   *
13467   * @type {string}
13468   * @private
13469   */
13470  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13471
13472  /**
13473   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13474   *
13475   * @type {string}
13476   * @protected
13477   *
13478   * @deprecated in v7; controlText_ is not used in non-active display Components
13479   */
13480  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13481  Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13482
13483  /**
13484   * @file duration-display.js
13485   */
13486
13487  /**
13488   * Displays the duration
13489   *
13490   * @extends Component
13491   */
13492  class DurationDisplay extends TimeDisplay {
13493    /**
13494     * Creates an instance of this class.
13495     *
13496     * @param { import('../../player').default } player
13497     *        The `Player` that this class should be attached to.
13498     *
13499     * @param {Object} [options]
13500     *        The key/value store of player options.
13501     */
13502    constructor(player, options) {
13503      super(player, options);
13504      const updateContent = e => this.updateContent(e);
13505
13506      // we do not want to/need to throttle duration changes,
13507      // as they should always display the changed duration as
13508      // it has changed
13509      this.on(player, 'durationchange', updateContent);
13510
13511      // Listen to loadstart because the player duration is reset when a new media element is loaded,
13512      // but the durationchange on the user agent will not fire.
13513      // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
13514      this.on(player, 'loadstart', updateContent);
13515
13516      // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13517      // listeners could have broken dependent applications/libraries. These
13518      // can likely be removed for 7.0.
13519      this.on(player, 'loadedmetadata', updateContent);
13520    }
13521
13522    /**
13523     * Builds the default DOM `className`.
13524     *
13525     * @return {string}
13526     *         The DOM `className` for this object.
13527     */
13528    buildCSSClass() {
13529      return 'vjs-duration';
13530    }
13531
13532    /**
13533     * Update duration time display.
13534     *
13535     * @param {Event} [event]
13536     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13537     *        this function to be called.
13538     *
13539     * @listens Player#durationchange
13540     * @listens Player#timeupdate
13541     * @listens Player#loadedmetadata
13542     */
13543    updateContent(event) {
13544      const duration = this.player_.duration();
13545      this.updateTextNode_(duration);
13546    }
13547  }
13548
13549  /**
13550   * The text that is added to the `DurationDisplay` for screen reader users.
13551   *
13552   * @type {string}
13553   * @private
13554   */
13555  DurationDisplay.prototype.labelText_ = 'Duration';
13556
13557  /**
13558   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13559   *
13560   * @type {string}
13561   * @protected
13562   *
13563   * @deprecated in v7; controlText_ is not used in non-active display Components
13564   */
13565  DurationDisplay.prototype.controlText_ = 'Duration';
13566  Component$1.registerComponent('DurationDisplay', DurationDisplay);
13567
13568  /**
13569   * @file time-divider.js
13570   */
13571
13572  /**
13573   * The separator between the current time and duration.
13574   * Can be hidden if it's not needed in the design.
13575   *
13576   * @extends Component
13577   */
13578  class TimeDivider extends Component$1 {
13579    /**
13580     * Create the component's DOM element
13581     *
13582     * @return {Element}
13583     *         The element that was created.
13584     */
13585    createEl() {
13586      const el = super.createEl('div', {
13587        className: 'vjs-time-control vjs-time-divider'
13588      }, {
13589        // this element and its contents can be hidden from assistive techs since
13590        // it is made extraneous by the announcement of the control text
13591        // for the current time and duration displays
13592        'aria-hidden': true
13593      });
13594      const div = super.createEl('div');
13595      const span = super.createEl('span', {
13596        textContent: '/'
13597      });
13598      div.appendChild(span);
13599      el.appendChild(div);
13600      return el;
13601    }
13602  }
13603  Component$1.registerComponent('TimeDivider', TimeDivider);
13604
13605  /**
13606   * @file remaining-time-display.js
13607   */
13608
13609  /**
13610   * Displays the time left in the video
13611   *
13612   * @extends Component
13613   */
13614  class RemainingTimeDisplay extends TimeDisplay {
13615    /**
13616     * Creates an instance of this class.
13617     *
13618     * @param { import('../../player').default } player
13619     *        The `Player` that this class should be attached to.
13620     *
13621     * @param {Object} [options]
13622     *        The key/value store of player options.
13623     */
13624    constructor(player, options) {
13625      super(player, options);
13626      this.on(player, 'durationchange', e => this.updateContent(e));
13627    }
13628
13629    /**
13630     * Builds the default DOM `className`.
13631     *
13632     * @return {string}
13633     *         The DOM `className` for this object.
13634     */
13635    buildCSSClass() {
13636      return 'vjs-remaining-time';
13637    }
13638
13639    /**
13640     * Create the `Component`'s DOM element with the "minus" character prepend to the time
13641     *
13642     * @return {Element}
13643     *         The element that was created.
13644     */
13645    createEl() {
13646      const el = super.createEl();
13647      if (this.options_.displayNegative !== false) {
13648        el.insertBefore(createEl('span', {}, {
13649          'aria-hidden': true
13650        }, '-'), this.contentEl_);
13651      }
13652      return el;
13653    }
13654
13655    /**
13656     * Update remaining time display.
13657     *
13658     * @param {Event} [event]
13659     *        The `timeupdate` or `durationchange` event that caused this to run.
13660     *
13661     * @listens Player#timeupdate
13662     * @listens Player#durationchange
13663     */
13664    updateContent(event) {
13665      if (typeof this.player_.duration() !== 'number') {
13666        return;
13667      }
13668      let time;
13669
13670      // @deprecated We should only use remainingTimeDisplay
13671      // as of video.js 7
13672      if (this.player_.ended()) {
13673        time = 0;
13674      } else if (this.player_.remainingTimeDisplay) {
13675        time = this.player_.remainingTimeDisplay();
13676      } else {
13677        time = this.player_.remainingTime();
13678      }
13679      this.updateTextNode_(time);
13680    }
13681  }
13682
13683  /**
13684   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
13685   *
13686   * @type {string}
13687   * @private
13688   */
13689  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
13690
13691  /**
13692   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
13693   *
13694   * @type {string}
13695   * @protected
13696   *
13697   * @deprecated in v7; controlText_ is not used in non-active display Components
13698   */
13699  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
13700  Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
13701
13702  /**
13703   * @file live-display.js
13704   */
13705
13706  // TODO - Future make it click to snap to live
13707
13708  /**
13709   * Displays the live indicator when duration is Infinity.
13710   *
13711   * @extends Component
13712   */
13713  class LiveDisplay extends Component$1 {
13714    /**
13715     * Creates an instance of this class.
13716     *
13717     * @param { import('./player').default } player
13718     *        The `Player` that this class should be attached to.
13719     *
13720     * @param {Object} [options]
13721     *        The key/value store of player options.
13722     */
13723    constructor(player, options) {
13724      super(player, options);
13725      this.updateShowing();
13726      this.on(this.player(), 'durationchange', e => this.updateShowing(e));
13727    }
13728
13729    /**
13730     * Create the `Component`'s DOM element
13731     *
13732     * @return {Element}
13733     *         The element that was created.
13734     */
13735    createEl() {
13736      const el = super.createEl('div', {
13737        className: 'vjs-live-control vjs-control'
13738      });
13739      this.contentEl_ = createEl('div', {
13740        className: 'vjs-live-display'
13741      }, {
13742        'aria-live': 'off'
13743      });
13744      this.contentEl_.appendChild(createEl('span', {
13745        className: 'vjs-control-text',
13746        textContent: `${this.localize('Stream Type')}\u00a0`
13747      }));
13748      this.contentEl_.appendChild(document.createTextNode(this.localize('LIVE')));
13749      el.appendChild(this.contentEl_);
13750      return el;
13751    }
13752    dispose() {
13753      this.contentEl_ = null;
13754      super.dispose();
13755    }
13756
13757    /**
13758     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
13759     * it accordingly
13760     *
13761     * @param {Event} [event]
13762     *        The {@link Player#durationchange} event that caused this function to run.
13763     *
13764     * @listens Player#durationchange
13765     */
13766    updateShowing(event) {
13767      if (this.player().duration() === Infinity) {
13768        this.show();
13769      } else {
13770        this.hide();
13771      }
13772    }
13773  }
13774  Component$1.registerComponent('LiveDisplay', LiveDisplay);
13775
13776  /**
13777   * @file seek-to-live.js
13778   */
13779
13780  /**
13781   * Displays the live indicator when duration is Infinity.
13782   *
13783   * @extends Component
13784   */
13785  class SeekToLive extends Button {
13786    /**
13787     * Creates an instance of this class.
13788     *
13789     * @param { import('./player').default } player
13790     *        The `Player` that this class should be attached to.
13791     *
13792     * @param {Object} [options]
13793     *        The key/value store of player options.
13794     */
13795    constructor(player, options) {
13796      super(player, options);
13797      this.updateLiveEdgeStatus();
13798      if (this.player_.liveTracker) {
13799        this.updateLiveEdgeStatusHandler_ = e => this.updateLiveEdgeStatus(e);
13800        this.on(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13801      }
13802    }
13803
13804    /**
13805     * Create the `Component`'s DOM element
13806     *
13807     * @return {Element}
13808     *         The element that was created.
13809     */
13810    createEl() {
13811      const el = super.createEl('button', {
13812        className: 'vjs-seek-to-live-control vjs-control'
13813      });
13814      this.setIcon('circle', el);
13815      this.textEl_ = createEl('span', {
13816        className: 'vjs-seek-to-live-text',
13817        textContent: this.localize('LIVE')
13818      }, {
13819        'aria-hidden': 'true'
13820      });
13821      el.appendChild(this.textEl_);
13822      return el;
13823    }
13824
13825    /**
13826     * Update the state of this button if we are at the live edge
13827     * or not
13828     */
13829    updateLiveEdgeStatus() {
13830      // default to live edge
13831      if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
13832        this.setAttribute('aria-disabled', true);
13833        this.addClass('vjs-at-live-edge');
13834        this.controlText('Seek to live, currently playing live');
13835      } else {
13836        this.setAttribute('aria-disabled', false);
13837        this.removeClass('vjs-at-live-edge');
13838        this.controlText('Seek to live, currently behind live');
13839      }
13840    }
13841
13842    /**
13843     * On click bring us as near to the live point as possible.
13844     * This requires that we wait for the next `live-seekable-change`
13845     * event which will happen every segment length seconds.
13846     */
13847    handleClick() {
13848      this.player_.liveTracker.seekToLiveEdge();
13849    }
13850
13851    /**
13852     * Dispose of the element and stop tracking
13853     */
13854    dispose() {
13855      if (this.player_.liveTracker) {
13856        this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13857      }
13858      this.textEl_ = null;
13859      super.dispose();
13860    }
13861  }
13862  /**
13863   * The text that should display over the `SeekToLive`s control. Added for localization.
13864   *
13865   * @type {string}
13866   * @protected
13867   */
13868  SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
13869  Component$1.registerComponent('SeekToLive', SeekToLive);
13870
13871  /**
13872   * @file num.js
13873   * @module num
13874   */
13875
13876  /**
13877   * Keep a number between a min and a max value
13878   *
13879   * @param {number} number
13880   *        The number to clamp
13881   *
13882   * @param {number} min
13883   *        The minimum value
13884   * @param {number} max
13885   *        The maximum value
13886   *
13887   * @return {number}
13888   *         the clamped number
13889   */
13890  function clamp(number, min, max) {
13891    number = Number(number);
13892    return Math.min(max, Math.max(min, isNaN(number) ? min : number));
13893  }
13894
13895  var Num = /*#__PURE__*/Object.freeze({
13896    __proto__: null,
13897    clamp: clamp
13898  });
13899
13900  /**
13901   * @file slider.js
13902   */
13903
13904  /**
13905   * The base functionality for a slider. Can be vertical or horizontal.
13906   * For instance the volume bar or the seek bar on a video is a slider.
13907   *
13908   * @extends Component
13909   */
13910  class Slider extends Component$1 {
13911    /**
13912    * Create an instance of this class
13913    *
13914    * @param { import('../player').default 
vendor: 8,858 bytes, lines 13914-14180
13914} player
13915    *        The `Player` that this class should be attached to.
13916    *
13917    * @param {Object} [options]
13918    *        The key/value store of player options.
13919    */
13920    constructor(player, options) {
13921      super(player, options);
13922      this.handleMouseDown_ = e => this.handleMouseDown(e);
13923      this.handleMouseUp_ = e => this.handleMouseUp(e);
13924      this.handleKeyDown_ = e => this.handleKeyDown(e);
13925      this.handleClick_ = e => this.handleClick(e);
13926      this.handleMouseMove_ = e => this.handleMouseMove(e);
13927      this.update_ = e => this.update(e);
13928
13929      // Set property names to bar to match with the child Slider class is looking for
13930      this.bar = this.getChild(this.options_.barName);
13931
13932      // Set a horizontal or vertical class on the slider depending on the slider type
13933      this.vertical(!!this.options_.vertical);
13934      this.enable();
13935    }
13936
13937    /**
13938     * Are controls are currently enabled for this slider or not.
13939     *
13940     * @return {boolean}
13941     *         true if controls are enabled, false otherwise
13942     */
13943    enabled() {
13944      return this.enabled_;
13945    }
13946
13947    /**
13948     * Enable controls for this slider if they are disabled
13949     */
13950    enable() {
13951      if (this.enabled()) {
13952        return;
13953      }
13954      this.on('mousedown', this.handleMouseDown_);
13955      this.on('touchstart', this.handleMouseDown_);
13956      this.on('keydown', this.handleKeyDown_);
13957      this.on('click', this.handleClick_);
13958
13959      // TODO: deprecated, controlsvisible does not seem to be fired
13960      this.on(this.player_, 'controlsvisible', this.update);
13961      if (this.playerEvent) {
13962        this.on(this.player_, this.playerEvent, this.update);
13963      }
13964      this.removeClass('disabled');
13965      this.setAttribute('tabindex', 0);
13966      this.enabled_ = true;
13967    }
13968
13969    /**
13970     * Disable controls for this slider if they are enabled
13971     */
13972    disable() {
13973      if (!this.enabled()) {
13974        return;
13975      }
13976      const doc = this.bar.el_.ownerDocument;
13977      this.off('mousedown', this.handleMouseDown_);
13978      this.off('touchstart', this.handleMouseDown_);
13979      this.off('keydown', this.handleKeyDown_);
13980      this.off('click', this.handleClick_);
13981      this.off(this.player_, 'controlsvisible', this.update_);
13982      this.off(doc, 'mousemove', this.handleMouseMove_);
13983      this.off(doc, 'mouseup', this.handleMouseUp_);
13984      this.off(doc, 'touchmove', this.handleMouseMove_);
13985      this.off(doc, 'touchend', this.handleMouseUp_);
13986      this.removeAttribute('tabindex');
13987      this.addClass('disabled');
13988      if (this.playerEvent) {
13989        this.off(this.player_, this.playerEvent, this.update);
13990      }
13991      this.enabled_ = false;
13992    }
13993
13994    /**
13995     * Create the `Slider`s DOM element.
13996     *
13997     * @param {string} type
13998     *        Type of element to create.
13999     *
14000     * @param {Object} [props={}]
14001     *        List of properties in Object form.
14002     *
14003     * @param {Object} [attributes={}]
14004     *        list of attributes in Object form.
14005     *
14006     * @return {Element}
14007     *         The element that gets created.
14008     */
14009    createEl(type, props = {}, attributes = {}) {
14010      // Add the slider element class to all sub classes
14011      props.className = props.className + ' vjs-slider';
14012      props = Object.assign({
14013        tabIndex: 0
14014      }, props);
14015      attributes = Object.assign({
14016        'role': 'slider',
14017        'aria-valuenow': 0,
14018        'aria-valuemin': 0,
14019        'aria-valuemax': 100
14020      }, attributes);
14021      return super.createEl(type, props, attributes);
14022    }
14023
14024    /**
14025     * Handle `mousedown` or `touchstart` events on the `Slider`.
14026     *
14027     * @param {MouseEvent} event
14028     *        `mousedown` or `touchstart` event that triggered this function
14029     *
14030     * @listens mousedown
14031     * @listens touchstart
14032     * @fires Slider#slideractive
14033     */
14034    handleMouseDown(event) {
14035      const doc = this.bar.el_.ownerDocument;
14036      if (event.type === 'mousedown') {
14037        event.preventDefault();
14038      }
14039      // Do not call preventDefault() on touchstart in Chrome
14040      // to avoid console warnings. Use a 'touch-action: none' style
14041      // instead to prevent unintended scrolling.
14042      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
14043      if (event.type === 'touchstart' && !IS_CHROME) {
14044        event.preventDefault();
14045      }
14046      blockTextSelection();
14047      this.addClass('vjs-sliding');
14048      /**
14049       * Triggered when the slider is in an active state
14050       *
14051       * @event Slider#slideractive
14052       * @type {MouseEvent}
14053       */
14054      this.trigger('slideractive');
14055      this.on(doc, 'mousemove', this.handleMouseMove_);
14056      this.on(doc, 'mouseup', this.handleMouseUp_);
14057      this.on(doc, 'touchmove', this.handleMouseMove_);
14058      this.on(doc, 'touchend', this.handleMouseUp_);
14059      this.handleMouseMove(event, true);
14060    }
14061
14062    /**
14063     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
14064     * The `mousemove` and `touchmove` events will only only trigger this function during
14065     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
14066     * {@link Slider#handleMouseUp}.
14067     *
14068     * @param {MouseEvent} event
14069     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
14070     *        this function
14071     * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false.
14072     *
14073     * @listens mousemove
14074     * @listens touchmove
14075     */
14076    handleMouseMove(event) {}
14077
14078    /**
14079     * Handle `mouseup` or `touchend` events on the `Slider`.
14080     *
14081     * @param {MouseEvent} event
14082     *        `mouseup` or `touchend` event that triggered this function.
14083     *
14084     * @listens touchend
14085     * @listens mouseup
14086     * @fires Slider#sliderinactive
14087     */
14088    handleMouseUp(event) {
14089      const doc = this.bar.el_.ownerDocument;
14090      unblockTextSelection();
14091      this.removeClass('vjs-sliding');
14092      /**
14093       * Triggered when the slider is no longer in an active state.
14094       *
14095       * @event Slider#sliderinactive
14096       * @type {Event}
14097       */
14098      this.trigger('sliderinactive');
14099      this.off(doc, 'mousemove', this.handleMouseMove_);
14100      this.off(doc, 'mouseup', this.handleMouseUp_);
14101      this.off(doc, 'touchmove', this.handleMouseMove_);
14102      this.off(doc, 'touchend', this.handleMouseUp_);
14103      this.update();
14104    }
14105
14106    /**
14107     * Update the progress bar of the `Slider`.
14108     *
14109     * @return {number}
14110     *          The percentage of progress the progress bar represents as a
14111     *          number from 0 to 1.
14112     */
14113    update() {
14114      // In VolumeBar init we have a setTimeout for update that pops and update
14115      // to the end of the execution stack. The player is destroyed before then
14116      // update will cause an error
14117      // If there's no bar...
14118      if (!this.el_ || !this.bar) {
14119        return;
14120      }
14121
14122      // clamp progress between 0 and 1
14123      // and only round to four decimal places, as we round to two below
14124      const progress = this.getProgress();
14125      if (progress === this.progress_) {
14126        return progress;
14127      }
14128      this.progress_ = progress;
14129      this.requestNamedAnimationFrame('Slider#update', () => {
14130        // Set the new bar width or height
14131        const sizeKey = this.vertical() ? 'height' : 'width';
14132
14133        // Convert to a percentage for css value
14134        this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
14135      });
14136      return progress;
14137    }
14138
14139    /**
14140     * Get the percentage of the bar that should be filled
14141     * but clamped and rounded.
14142     *
14143     * @return {number}
14144     *         percentage filled that the slider is
14145     */
14146    getProgress() {
14147      return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
14148    }
14149
14150    /**
14151     * Calculate distance for slider
14152     *
14153     * @param {Event} event
14154     *        The event that caused this function to run.
14155     *
14156     * @return {number}
14157     *         The current position of the Slider.
14158     *         - position.x for vertical `Slider`s
14159     *         - position.y for horizontal `Slider`s
14160     */
14161    calculateDistance(event) {
14162      const position = getPointerPosition(this.el_, event);
14163      if (this.vertical()) {
14164        return position.y;
14165      }
14166      return position.x;
14167    }
14168
14169    /**
14170     * Handle a `keydown` event on the `Slider`. Watches for left, right, up, and down
14171     * arrow keys. This function will only be called when the slider has focus. See
14172     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
14173     *
14174     * @param {KeyboardEvent} event
14175     *        the `keydown` event that caused this function to run.
14176     *
14177     * @listens keydown
14178     */
14179    handleKeyDown(event) {
14180      
vendor: 88,768 bytes, lines 14180-17042
14180// Left and Down Arrows
14181      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
14182        event.preventDefault();
14183        event.stopPropagation();
14184        this.stepBack();
14185
14186        // Up and Right Arrows
14187      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
14188        event.preventDefault();
14189        event.stopPropagation();
14190        this.stepForward();
14191      } else {
14192        // Pass keydown handling up for unsupported keys
14193        super.handleKeyDown(event);
14194      }
14195    }
14196
14197    /**
14198     * Listener for click events on slider, used to prevent clicks
14199     *   from bubbling up to parent elements like button menus.
14200     *
14201     * @param {Object} event
14202     *        Event that caused this object to run
14203     */
14204    handleClick(event) {
14205      event.stopPropagation();
14206      event.preventDefault();
14207    }
14208
14209    /**
14210     * Get/set if slider is horizontal for vertical
14211     *
14212     * @param {boolean} [bool]
14213     *        - true if slider is vertical,
14214     *        - false is horizontal
14215     *
14216     * @return {boolean}
14217     *         - true if slider is vertical, and getting
14218     *         - false if the slider is horizontal, and getting
14219     */
14220    vertical(bool) {
14221      if (bool === undefined) {
14222        return this.vertical_ || false;
14223      }
14224      this.vertical_ = !!bool;
14225      if (this.vertical_) {
14226        this.addClass('vjs-slider-vertical');
14227      } else {
14228        this.addClass('vjs-slider-horizontal');
14229      }
14230    }
14231  }
14232  Component$1.registerComponent('Slider', Slider);
14233
14234  /**
14235   * @file load-progress-bar.js
14236   */
14237
14238  // get the percent width of a time compared to the total end
14239  const percentify = (time, end) => clamp(time / end * 100, 0, 100).toFixed(2) + '%';
14240
14241  /**
14242   * Shows loading progress
14243   *
14244   * @extends Component
14245   */
14246  class LoadProgressBar extends Component$1 {
14247    /**
14248     * Creates an instance of this class.
14249     *
14250     * @param { import('../../player').default } player
14251     *        The `Player` that this class should be attached to.
14252     *
14253     * @param {Object} [options]
14254     *        The key/value store of player options.
14255     */
14256    constructor(player, options) {
14257      super(player, options);
14258      this.partEls_ = [];
14259      this.on(player, 'progress', e => this.update(e));
14260    }
14261
14262    /**
14263     * Create the `Component`'s DOM element
14264     *
14265     * @return {Element}
14266     *         The element that was created.
14267     */
14268    createEl() {
14269      const el = super.createEl('div', {
14270        className: 'vjs-load-progress'
14271      });
14272      const wrapper = createEl('span', {
14273        className: 'vjs-control-text'
14274      });
14275      const loadedText = createEl('span', {
14276        textContent: this.localize('Loaded')
14277      });
14278      const separator = document.createTextNode(': ');
14279      this.percentageEl_ = createEl('span', {
14280        className: 'vjs-control-text-loaded-percentage',
14281        textContent: '0%'
14282      });
14283      el.appendChild(wrapper);
14284      wrapper.appendChild(loadedText);
14285      wrapper.appendChild(separator);
14286      wrapper.appendChild(this.percentageEl_);
14287      return el;
14288    }
14289    dispose() {
14290      this.partEls_ = null;
14291      this.percentageEl_ = null;
14292      super.dispose();
14293    }
14294
14295    /**
14296     * Update progress bar
14297     *
14298     * @param {Event} [event]
14299     *        The `progress` event that caused this function to run.
14300     *
14301     * @listens Player#progress
14302     */
14303    update(event) {
14304      this.requestNamedAnimationFrame('LoadProgressBar#update', () => {
14305        const liveTracker = this.player_.liveTracker;
14306        const buffered = this.player_.buffered();
14307        const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
14308        const bufferedEnd = this.player_.bufferedEnd();
14309        const children = this.partEls_;
14310        const percent = percentify(bufferedEnd, duration);
14311        if (this.percent_ !== percent) {
14312          // update the width of the progress bar
14313          this.el_.style.width = percent;
14314          // update the control-text
14315          textContent(this.percentageEl_, percent);
14316          this.percent_ = percent;
14317        }
14318
14319        // add child elements to represent the individual buffered time ranges
14320        for (let i = 0; i < buffered.length; i++) {
14321          const start = buffered.start(i);
14322          const end = buffered.end(i);
14323          let part = children[i];
14324          if (!part) {
14325            part = this.el_.appendChild(createEl());
14326            children[i] = part;
14327          }
14328
14329          //  only update if changed
14330          if (part.dataset.start === start && part.dataset.end === end) {
14331            continue;
14332          }
14333          part.dataset.start = start;
14334          part.dataset.end = end;
14335
14336          // set the percent based on the width of the progress bar (bufferedEnd)
14337          part.style.left = percentify(start, bufferedEnd);
14338          part.style.width = percentify(end - start, bufferedEnd);
14339        }
14340
14341        // remove unused buffered range elements
14342        for (let i = children.length; i > buffered.length; i--) {
14343          this.el_.removeChild(children[i - 1]);
14344        }
14345        children.length = buffered.length;
14346      });
14347    }
14348  }
14349  Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
14350
14351  /**
14352   * @file time-tooltip.js
14353   */
14354
14355  /**
14356   * Time tooltips display a time above the progress bar.
14357   *
14358   * @extends Component
14359   */
14360  class TimeTooltip extends Component$1 {
14361    /**
14362     * Creates an instance of this class.
14363     *
14364     * @param { import('../../player').default } player
14365     *        The {@link Player} that this class should be attached to.
14366     *
14367     * @param {Object} [options]
14368     *        The key/value store of player options.
14369     */
14370    constructor(player, options) {
14371      super(player, options);
14372      this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14373    }
14374
14375    /**
14376     * Create the time tooltip DOM element
14377     *
14378     * @return {Element}
14379     *         The element that was created.
14380     */
14381    createEl() {
14382      return super.createEl('div', {
14383        className: 'vjs-time-tooltip'
14384      }, {
14385        'aria-hidden': 'true'
14386      });
14387    }
14388
14389    /**
14390     * Updates the position of the time tooltip relative to the `SeekBar`.
14391     *
14392     * @param {Object} seekBarRect
14393     *        The `ClientRect` for the {@link SeekBar} element.
14394     *
14395     * @param {number} seekBarPoint
14396     *        A number from 0 to 1, representing a horizontal reference point
14397     *        from the left edge of the {@link SeekBar}
14398     */
14399    update(seekBarRect, seekBarPoint, content) {
14400      const tooltipRect = findPosition(this.el_);
14401      const playerRect = getBoundingClientRect(this.player_.el());
14402      const seekBarPointPx = seekBarRect.width * seekBarPoint;
14403
14404      // do nothing if either rect isn't available
14405      // for example, if the player isn't in the DOM for testing
14406      if (!playerRect || !tooltipRect) {
14407        return;
14408      }
14409
14410      // This is the space left of the `seekBarPoint` available within the bounds
14411      // of the player. We calculate any gap between the left edge of the player
14412      // and the left edge of the `SeekBar` and add the number of pixels in the
14413      // `SeekBar` before hitting the `seekBarPoint`
14414      const spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
14415
14416      // This is the space right of the `seekBarPoint` available within the bounds
14417      // of the player. We calculate the number of pixels from the `seekBarPoint`
14418      // to the right edge of the `SeekBar` and add to that any gap between the
14419      // right edge of the `SeekBar` and the player.
14420      const spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
14421
14422      // This is the number of pixels by which the tooltip will need to be pulled
14423      // further to the right to center it over the `seekBarPoint`.
14424      let pullTooltipBy = tooltipRect.width / 2;
14425
14426      // Adjust the `pullTooltipBy` distance to the left or right depending on
14427      // the results of the space calculations above.
14428      if (spaceLeftOfPoint < pullTooltipBy) {
14429        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14430      } else if (spaceRightOfPoint < pullTooltipBy) {
14431        pullTooltipBy = spaceRightOfPoint;
14432      }
14433
14434      // Due to the imprecision of decimal/ratio based calculations and varying
14435      // rounding behaviors, there are cases where the spacing adjustment is off
14436      // by a pixel or two. This adds insurance to these calculations.
14437      if (pullTooltipBy < 0) {
14438        pullTooltipBy = 0;
14439      } else if (pullTooltipBy > tooltipRect.width) {
14440        pullTooltipBy = tooltipRect.width;
14441      }
14442
14443      // prevent small width fluctuations within 0.4px from
14444      // changing the value below.
14445      // This really helps for live to prevent the play
14446      // progress time tooltip from jittering
14447      pullTooltipBy = Math.round(pullTooltipBy);
14448      this.el_.style.right = `-${pullTooltipBy}px`;
14449      this.write(content);
14450    }
14451
14452    /**
14453     * Write the time to the tooltip DOM element.
14454     *
14455     * @param {string} content
14456     *        The formatted time for the tooltip.
14457     */
14458    write(content) {
14459      textContent(this.el_, content);
14460    }
14461
14462    /**
14463     * Updates the position of the time tooltip relative to the `SeekBar`.
14464     *
14465     * @param {Object} seekBarRect
14466     *        The `ClientRect` for the {@link SeekBar} element.
14467     *
14468     * @param {number} seekBarPoint
14469     *        A number from 0 to 1, representing a horizontal reference point
14470     *        from the left edge of the {@link SeekBar}
14471     *
14472     * @param {number} time
14473     *        The time to update the tooltip to, not used during live playback
14474     *
14475     * @param {Function} cb
14476     *        A function that will be called during the request animation frame
14477     *        for tooltips that need to do additional animations from the default
14478     */
14479    updateTime(seekBarRect, seekBarPoint, time, cb) {
14480      this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
14481        let content;
14482        const duration = this.player_.duration();
14483        if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
14484          const liveWindow = this.player_.liveTracker.liveWindow();
14485          const secondsBehind = liveWindow - seekBarPoint * liveWindow;
14486          content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
14487        } else {
14488          content = formatTime(time, duration);
14489        }
14490        this.update(seekBarRect, seekBarPoint, content);
14491        if (cb) {
14492          cb();
14493        }
14494      });
14495    }
14496  }
14497  Component$1.registerComponent('TimeTooltip', TimeTooltip);
14498
14499  /**
14500   * @file play-progress-bar.js
14501   */
14502
14503  /**
14504   * Used by {@link SeekBar} to display media playback progress as part of the
14505   * {@link ProgressControl}.
14506   *
14507   * @extends Component
14508   */
14509  class PlayProgressBar extends Component$1 {
14510    /**
14511     * Creates an instance of this class.
14512     *
14513     * @param { import('../../player').default } player
14514     *        The {@link Player} that this class should be attached to.
14515     *
14516     * @param {Object} [options]
14517     *        The key/value store of player options.
14518     */
14519    constructor(player, options) {
14520      super(player, options);
14521      this.setIcon('circle');
14522      this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14523    }
14524
14525    /**
14526     * Create the the DOM element for this class.
14527     *
14528     * @return {Element}
14529     *         The element that was created.
14530     */
14531    createEl() {
14532      return super.createEl('div', {
14533        className: 'vjs-play-progress vjs-slider-bar'
14534      }, {
14535        'aria-hidden': 'true'
14536      });
14537    }
14538
14539    /**
14540     * Enqueues updates to its own DOM as well as the DOM of its
14541     * {@link TimeTooltip} child.
14542     *
14543     * @param {Object} seekBarRect
14544     *        The `ClientRect` for the {@link SeekBar} element.
14545     *
14546     * @param {number} seekBarPoint
14547     *        A number from 0 to 1, representing a horizontal reference point
14548     *        from the left edge of the {@link SeekBar}
14549     */
14550    update(seekBarRect, seekBarPoint) {
14551      const timeTooltip = this.getChild('timeTooltip');
14552      if (!timeTooltip) {
14553        return;
14554      }
14555      const time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14556      timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
14557    }
14558  }
14559
14560  /**
14561   * Default options for {@link PlayProgressBar}.
14562   *
14563   * @type {Object}
14564   * @private
14565   */
14566  PlayProgressBar.prototype.options_ = {
14567    children: []
14568  };
14569
14570  // Time tooltips should not be added to a player on mobile devices
14571  if (!IS_IOS && !IS_ANDROID) {
14572    PlayProgressBar.prototype.options_.children.push('timeTooltip');
14573  }
14574  Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
14575
14576  /**
14577   * @file mouse-time-display.js
14578   */
14579
14580  /**
14581   * The {@link MouseTimeDisplay} component tracks mouse movement over the
14582   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14583   * indicating the time which is represented by a given point in the
14584   * {@link ProgressControl}.
14585   *
14586   * @extends Component
14587   */
14588  class MouseTimeDisplay extends Component$1 {
14589    /**
14590     * Creates an instance of this class.
14591     *
14592     * @param { import('../../player').default } player
14593     *        The {@link Player} that this class should be attached to.
14594     *
14595     * @param {Object} [options]
14596     *        The key/value store of player options.
14597     */
14598    constructor(player, options) {
14599      super(player, options);
14600      this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14601    }
14602
14603    /**
14604     * Create the DOM element for this class.
14605     *
14606     * @return {Element}
14607     *         The element that was created.
14608     */
14609    createEl() {
14610      return super.createEl('div', {
14611        className: 'vjs-mouse-display'
14612      });
14613    }
14614
14615    /**
14616     * Enqueues updates to its own DOM as well as the DOM of its
14617     * {@link TimeTooltip} child.
14618     *
14619     * @param {Object} seekBarRect
14620     *        The `ClientRect` for the {@link SeekBar} element.
14621     *
14622     * @param {number} seekBarPoint
14623     *        A number from 0 to 1, representing a horizontal reference point
14624     *        from the left edge of the {@link SeekBar}
14625     */
14626    update(seekBarRect, seekBarPoint) {
14627      const time = seekBarPoint * this.player_.duration();
14628      this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, () => {
14629        this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
14630      });
14631    }
14632  }
14633
14634  /**
14635   * Default options for `MouseTimeDisplay`
14636   *
14637   * @type {Object}
14638   * @private
14639   */
14640  MouseTimeDisplay.prototype.options_ = {
14641    children: ['timeTooltip']
14642  };
14643  Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
14644
14645  /**
14646   * @file seek-bar.js
14647   */
14648
14649  // The number of seconds the `step*` functions move the timeline.
14650  const STEP_SECONDS = 5;
14651
14652  // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
14653  const PAGE_KEY_MULTIPLIER = 12;
14654
14655  /**
14656   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
14657   * as its `bar`.
14658   *
14659   * @extends Slider
14660   */
14661  class SeekBar extends Slider {
14662    /**
14663     * Creates an instance of this class.
14664     *
14665     * @param { import('../../player').default } player
14666     *        The `Player` that this class should be attached to.
14667     *
14668     * @param {Object} [options]
14669     *        The key/value store of player options.
14670     */
14671    constructor(player, options) {
14672      super(player, options);
14673      this.setEventHandlers_();
14674    }
14675
14676    /**
14677     * Sets the event handlers
14678     *
14679     * @private
14680     */
14681    setEventHandlers_() {
14682      this.update_ = bind_(this, this.update);
14683      this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
14684      this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
14685      if (this.player_.liveTracker) {
14686        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
14687      }
14688
14689      // when playing, let's ensure we smoothly update the play progress bar
14690      // via an interval
14691      this.updateInterval = null;
14692      this.enableIntervalHandler_ = e => this.enableInterval_(e);
14693      this.disableIntervalHandler_ = e => this.disableInterval_(e);
14694      this.on(this.player_, ['playing'], this.enableIntervalHandler_);
14695      this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
14696
14697      // we don't need to update the play progress if the document is hidden,
14698      // also, this causes the CPU to spike and eventually crash the page on IE11.
14699      if ('hidden' in document && 'visibilityState' in document) {
14700        this.on(document, 'visibilitychange', this.toggleVisibility_);
14701      }
14702    }
14703    toggleVisibility_(e) {
14704      if (document.visibilityState === 'hidden') {
14705        this.cancelNamedAnimationFrame('SeekBar#update');
14706        this.cancelNamedAnimationFrame('Slider#update');
14707        this.disableInterval_(e);
14708      } else {
14709        if (!this.player_.ended() && !this.player_.paused()) {
14710          this.enableInterval_();
14711        }
14712
14713        // we just switched back to the page and someone may be looking, so, update ASAP
14714        this.update();
14715      }
14716    }
14717    enableInterval_() {
14718      if (this.updateInterval) {
14719        return;
14720      }
14721      this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
14722    }
14723    disableInterval_(e) {
14724      if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
14725        return;
14726      }
14727      if (!this.updateInterval) {
14728        return;
14729      }
14730      this.clearInterval(this.updateInterval);
14731      this.updateInterval = null;
14732    }
14733
14734    /**
14735     * Create the `Component`'s DOM element
14736     *
14737     * @return {Element}
14738     *         The element that was created.
14739     */
14740    createEl() {
14741      return super.createEl('div', {
14742        className: 'vjs-progress-holder'
14743      }, {
14744        'aria-label': this.localize('Progress Bar')
14745      });
14746    }
14747
14748    /**
14749     * This function updates the play progress bar and accessibility
14750     * attributes to whatever is passed in.
14751     *
14752     * @param {Event} [event]
14753     *        The `timeupdate` or `ended` event that caused this to run.
14754     *
14755     * @listens Player#timeupdate
14756     *
14757     * @return {number}
14758     *          The current percent at a number from 0-1
14759     */
14760    update(event) {
14761      // ignore updates while the tab is hidden
14762      if (document.visibilityState === 'hidden') {
14763        return;
14764      }
14765      const percent = super.update();
14766      this.requestNamedAnimationFrame('SeekBar#update', () => {
14767        const currentTime = this.player_.ended() ? this.player_.duration() : this.getCurrentTime_();
14768        const liveTracker = this.player_.liveTracker;
14769        let duration = this.player_.duration();
14770        if (liveTracker && liveTracker.isLive()) {
14771          duration = this.player_.liveTracker.liveCurrentTime();
14772        }
14773        if (this.percent_ !== percent) {
14774          // machine readable value of progress bar (percentage complete)
14775          this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14776          this.percent_ = percent;
14777        }
14778        if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
14779          // human readable value of progress bar (time complete)
14780          this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
14781          this.currentTime_ = currentTime;
14782          this.duration_ = duration;
14783        }
14784
14785        // update the progress bar time tooltip with the current time
14786        if (this.bar) {
14787          this.bar.update(getBoundingClientRect(this.el()), this.getProgress());
14788        }
14789      });
14790      return percent;
14791    }
14792
14793    /**
14794     * Prevent liveThreshold from causing seeks to seem like they
14795     * are not happening from a user perspective.
14796     *
14797     * @param {number} ct
14798     *        current time to seek to
14799     */
14800    userSeek_(ct) {
14801      if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
14802        this.player_.liveTracker.nextSeekedFromUser();
14803      }
14804      this.player_.currentTime(ct);
14805    }
14806
14807    /**
14808     * Get the value of current time but allows for smooth scrubbing,
14809     * when player can't keep up.
14810     *
14811     * @return {number}
14812     *         The current time value to display
14813     *
14814     * @private
14815     */
14816    getCurrentTime_() {
14817      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14818    }
14819
14820    /**
14821     * Get the percentage of media played so far.
14822     *
14823     * @return {number}
14824     *         The percentage of media played so far (0 to 1).
14825     */
14826    getPercent() {
14827      const currentTime = this.getCurrentTime_();
14828      let percent;
14829      const liveTracker = this.player_.liveTracker;
14830      if (liveTracker && liveTracker.isLive()) {
14831        percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
14832
14833        // prevent the percent from changing at the live edge
14834        if (liveTracker.atLiveEdge()) {
14835          percent = 1;
14836        }
14837      } else {
14838        percent = currentTime / this.player_.duration();
14839      }
14840      return percent;
14841    }
14842
14843    /**
14844     * Handle mouse down on seek bar
14845     *
14846     * @param {MouseEvent} event
14847     *        The `mousedown` event that caused this to run.
14848     *
14849     * @listens mousedown
14850     */
14851    handleMouseDown(event) {
14852      if (!isSingleLeftClick(event)) {
14853        return;
14854      }
14855
14856      // Stop event propagation to prevent double fire in progress-control.js
14857      event.stopPropagation();
14858      this.videoWasPlaying = !this.player_.paused();
14859      this.player_.pause();
14860      super.handleMouseDown(event);
14861    }
14862
14863    /**
14864     * Handle mouse move on seek bar
14865     *
14866     * @param {MouseEvent} event
14867     *        The `mousemove` event that caused this to run.
14868     * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false
14869     *
14870     * @listens mousemove
14871     */
14872    handleMouseMove(event, mouseDown = false) {
14873      if (!isSingleLeftClick(event) || isNaN(this.player_.duration())) {
14874        return;
14875      }
14876      if (!mouseDown && !this.player_.scrubbing()) {
14877        this.player_.scrubbing(true);
14878      }
14879      let newTime;
14880      const distance = this.calculateDistance(event);
14881      const liveTracker = this.player_.liveTracker;
14882      if (!liveTracker || !liveTracker.isLive()) {
14883        newTime = distance * this.player_.duration();
14884
14885        // Don't let video end while scrubbing.
14886        if (newTime === this.player_.duration()) {
14887          newTime = newTime - 0.1;
14888        }
14889      } else {
14890        if (distance >= 0.99) {
14891          liveTracker.seekToLiveEdge();
14892          return;
14893        }
14894        const seekableStart = liveTracker.seekableStart();
14895        const seekableEnd = liveTracker.liveCurrentTime();
14896        newTime = seekableStart + distance * liveTracker.liveWindow();
14897
14898        // Don't let video end while scrubbing.
14899        if (newTime >= seekableEnd) {
14900          newTime = seekableEnd;
14901        }
14902
14903        // Compensate for precision differences so that currentTime is not less
14904        // than seekable start
14905        if (newTime <= seekableStart) {
14906          newTime = seekableStart + 0.1;
14907        }
14908
14909        // On android seekableEnd can be Infinity sometimes,
14910        // this will cause newTime to be Infinity, which is
14911        // not a valid currentTime.
14912        if (newTime === Infinity) {
14913          return;
14914        }
14915      }
14916
14917      // Set new time (tell player to seek to new time)
14918      this.userSeek_(newTime);
14919      if (this.player_.options_.enableSmoothSeeking) {
14920        this.update();
14921      }
14922    }
14923    enable() {
14924      super.enable();
14925      const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14926      if (!mouseTimeDisplay) {
14927        return;
14928      }
14929      mouseTimeDisplay.show();
14930    }
14931    disable() {
14932      super.disable();
14933      const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14934      if (!mouseTimeDisplay) {
14935        return;
14936      }
14937      mouseTimeDisplay.hide();
14938    }
14939
14940    /**
14941     * Handle mouse up on seek bar
14942     *
14943     * @param {MouseEvent} event
14944     *        The `mouseup` event that caused this to run.
14945     *
14946     * @listens mouseup
14947     */
14948    handleMouseUp(event) {
14949      super.handleMouseUp(event);
14950
14951      // Stop event propagation to prevent double fire in progress-control.js
14952      if (event) {
14953        event.stopPropagation();
14954      }
14955      this.player_.scrubbing(false);
14956
14957      /**
14958       * Trigger timeupdate because we're done seeking and the time has changed.
14959       * This is particularly useful for if the player is paused to time the time displays.
14960       *
14961       * @event Tech#timeupdate
14962       * @type {Event}
14963       */
14964      this.player_.trigger({
14965        type: 'timeupdate',
14966        target: this,
14967        manuallyTriggered: true
14968      });
14969      if (this.videoWasPlaying) {
14970        silencePromise(this.player_.play());
14971      } else {
14972        // We're done seeking and the time has changed.
14973        // If the player is paused, make sure we display the correct time on the seek bar.
14974        this.update_();
14975      }
14976    }
14977
14978    /**
14979     * Move more quickly fast forward for keyboard-only users
14980     */
14981    stepForward() {
14982      this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
14983    }
14984
14985    /**
14986     * Move more quickly rewind for keyboard-only users
14987     */
14988    stepBack() {
14989      this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
14990    }
14991
14992    /**
14993     * Toggles the playback state of the player
14994     * This gets called when enter or space is used on the seekbar
14995     *
14996     * @param {KeyboardEvent} event
14997     *        The `keydown` event that caused this function to be called
14998     *
14999     */
15000    handleAction(event) {
15001      if (this.player_.paused()) {
15002        this.player_.play();
15003      } else {
15004        this.player_.pause();
15005      }
15006    }
15007
15008    /**
15009     * Called when this SeekBar has focus and a key gets pressed down.
15010     * Supports the following keys:
15011     *
15012     *   Space or Enter key fire a click event
15013     *   Home key moves to start of the timeline
15014     *   End key moves to end of the timeline
15015     *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
15016     *   PageDown key moves back a larger step than ArrowDown
15017     *   PageUp key moves forward a large step
15018     *
15019     * @param {KeyboardEvent} event
15020     *        The `keydown` event that caused this function to be called.
15021     *
15022     * @listens keydown
15023     */
15024    handleKeyDown(event) {
15025      const liveTracker = this.player_.liveTracker;
15026      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
15027        event.preventDefault();
15028        event.stopPropagation();
15029        this.handleAction(event);
15030      } else if (keycode.isEventKey(event, 'Home')) {
15031        event.preventDefault();
15032        event.stopPropagation();
15033        this.userSeek_(0);
15034      } else if (keycode.isEventKey(event, 'End')) {
15035        event.preventDefault();
15036        event.stopPropagation();
15037        if (liveTracker && liveTracker.isLive()) {
15038          this.userSeek_(liveTracker.liveCurrentTime());
15039        } else {
15040          this.userSeek_(this.player_.duration());
15041        }
15042      } else if (/^[0-9]$/.test(keycode(event))) {
15043        event.preventDefault();
15044        event.stopPropagation();
15045        const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
15046        if (liveTracker && liveTracker.isLive()) {
15047          this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
15048        } else {
15049          this.userSeek_(this.player_.duration() * gotoFraction);
15050        }
15051      } else if (keycode.isEventKey(event, 'PgDn')) {
15052        event.preventDefault();
15053        event.stopPropagation();
15054        this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15055      } else if (keycode.isEventKey(event, 'PgUp')) {
15056        event.preventDefault();
15057        event.stopPropagation();
15058        this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15059      } else {
15060        // Pass keydown handling up for unsupported keys
15061        super.handleKeyDown(event);
15062      }
15063    }
15064    dispose() {
15065      this.disableInterval_();
15066      this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
15067      if (this.player_.liveTracker) {
15068        this.off(this.player_.liveTracker, 'liveedgechange', this.update);
15069      }
15070      this.off(this.player_, ['playing'], this.enableIntervalHandler_);
15071      this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
15072
15073      // we don't need to update the play progress if the document is hidden,
15074      // also, this causes the CPU to spike and eventually crash the page on IE11.
15075      if ('hidden' in document && 'visibilityState' in document) {
15076        this.off(document, 'visibilitychange', this.toggleVisibility_);
15077      }
15078      super.dispose();
15079    }
15080  }
15081
15082  /**
15083   * Default options for the `SeekBar`
15084   *
15085   * @type {Object}
15086   * @private
15087   */
15088  SeekBar.prototype.options_ = {
15089    children: ['loadProgressBar', 'playProgressBar'],
15090    barName: 'playProgressBar'
15091  };
15092
15093  // MouseTimeDisplay tooltips should not be added to a player on mobile devices
15094  if (!IS_IOS && !IS_ANDROID) {
15095    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
15096  }
15097  Component$1.registerComponent('SeekBar', SeekBar);
15098
15099  /**
15100   * @file progress-control.js
15101   */
15102
15103  /**
15104   * The Progress Control component contains the seek bar, load progress,
15105   * and play progress.
15106   *
15107   * @extends Component
15108   */
15109  class ProgressControl extends Component$1 {
15110    /**
15111     * Creates an instance of this class.
15112     *
15113     * @param { import('../../player').default } player
15114     *        The `Player` that this class should be attached to.
15115     *
15116     * @param {Object} [options]
15117     *        The key/value store of player options.
15118     */
15119    constructor(player, options) {
15120      super(player, options);
15121      this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
15122      this.throttledHandleMouseSeek = throttle(bind_(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
15123      this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
15124      this.handleMouseDownHandler_ = e => this.handleMouseDown(e);
15125      this.enable();
15126    }
15127
15128    /**
15129     * Create the `Component`'s DOM element
15130     *
15131     * @return {Element}
15132     *         The element that was created.
15133     */
15134    createEl() {
15135      return super.createEl('div', {
15136        className: 'vjs-progress-control vjs-control'
15137      });
15138    }
15139
15140    /**
15141     * When the mouse moves over the `ProgressControl`, the pointer position
15142     * gets passed down to the `MouseTimeDisplay` component.
15143     *
15144     * @param {Event} event
15145     *        The `mousemove` event that caused this function to run.
15146     *
15147     * @listen mousemove
15148     */
15149    handleMouseMove(event) {
15150      const seekBar = this.getChild('seekBar');
15151      if (!seekBar) {
15152        return;
15153      }
15154      const playProgressBar = seekBar.getChild('playProgressBar');
15155      const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
15156      if (!playProgressBar && !mouseTimeDisplay) {
15157        return;
15158      }
15159      const seekBarEl = seekBar.el();
15160      const seekBarRect = findPosition(seekBarEl);
15161      let seekBarPoint = getPointerPosition(seekBarEl, event).x;
15162
15163      // The default skin has a gap on either side of the `SeekBar`. This means
15164      // that it's possible to trigger this behavior outside the boundaries of
15165      // the `SeekBar`. This ensures we stay within it at all times.
15166      seekBarPoint = clamp(seekBarPoint, 0, 1);
15167      if (mouseTimeDisplay) {
15168        mouseTimeDisplay.update(seekBarRect, seekBarPoint);
15169      }
15170      if (playProgressBar) {
15171        playProgressBar.update(seekBarRect, seekBar.getProgress());
15172      }
15173    }
15174
15175    /**
15176     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
15177     *
15178     * @method ProgressControl#throttledHandleMouseSeek
15179     * @param {Event} event
15180     *        The `mousemove` event that caused this function to run.
15181     *
15182     * @listen mousemove
15183     * @listen touchmove
15184     */
15185
15186    /**
15187     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
15188     *
15189     * @param {Event} event
15190     *        `mousedown` or `touchstart` event that triggered this function
15191     *
15192     * @listens mousemove
15193     * @listens touchmove
15194     */
15195    handleMouseSeek(event) {
15196      const seekBar = this.getChild('seekBar');
15197      if (seekBar) {
15198        seekBar.handleMouseMove(event);
15199      }
15200    }
15201
15202    /**
15203     * Are controls are currently enabled for this progress control.
15204     *
15205     * @return {boolean}
15206     *         true if controls are enabled, false otherwise
15207     */
15208    enabled() {
15209      return this.enabled_;
15210    }
15211
15212    /**
15213     * Disable all controls on the progress control and its children
15214     */
15215    disable() {
15216      this.children().forEach(child => child.disable && child.disable());
15217      if (!this.enabled()) {
15218        return;
15219      }
15220      this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
15221      this.off(this.el_, 'mousemove', this.handleMouseMove);
15222      this.removeListenersAddedOnMousedownAndTouchstart();
15223      this.addClass('disabled');
15224      this.enabled_ = false;
15225
15226      // Restore normal playback state if controls are disabled while scrubbing
15227      if (this.player_.scrubbing()) {
15228        const seekBar = this.getChild('seekBar');
15229        this.player_.scrubbing(false);
15230        if (seekBar.videoWasPlaying) {
15231          silencePromise(this.player_.play());
15232        }
15233      }
15234    }
15235
15236    /**
15237     * Enable all controls on the progress control and its children
15238     */
15239    enable() {
15240      this.children().forEach(child => child.enable && child.enable());
15241      if (this.enabled()) {
15242        return;
15243      }
15244      this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
15245      this.on(this.el_, 'mousemove', this.handleMouseMove);
15246      this.removeClass('disabled');
15247      this.enabled_ = true;
15248    }
15249
15250    /**
15251     * Cleanup listeners after the user finishes interacting with the progress controls
15252     */
15253    removeListenersAddedOnMousedownAndTouchstart() {
15254      const doc = this.el_.ownerDocument;
15255      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
15256      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
15257      this.off(doc, 'mouseup', this.handleMouseUpHandler_);
15258      this.off(doc, 'touchend', this.handleMouseUpHandler_);
15259    }
15260
15261    /**
15262     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
15263     *
15264     * @param {Event} event
15265     *        `mousedown` or `touchstart` event that triggered this function
15266     *
15267     * @listens mousedown
15268     * @listens touchstart
15269     */
15270    handleMouseDown(event) {
15271      const doc = this.el_.ownerDocument;
15272      const seekBar = this.getChild('seekBar');
15273      if (seekBar) {
15274        seekBar.handleMouseDown(event);
15275      }
15276      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
15277      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
15278      this.on(doc, 'mouseup', this.handleMouseUpHandler_);
15279      this.on(doc, 'touchend', this.handleMouseUpHandler_);
15280    }
15281
15282    /**
15283     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
15284     *
15285     * @param {Event} event
15286     *        `mouseup` or `touchend` event that triggered this function.
15287     *
15288     * @listens touchend
15289     * @listens mouseup
15290     */
15291    handleMouseUp(event) {
15292      const seekBar = this.getChild('seekBar');
15293      if (seekBar) {
15294        seekBar.handleMouseUp(event);
15295      }
15296      this.removeListenersAddedOnMousedownAndTouchstart();
15297    }
15298  }
15299
15300  /**
15301   * Default options for `ProgressControl`
15302   *
15303   * @type {Object}
15304   * @private
15305   */
15306  ProgressControl.prototype.options_ = {
15307    children: ['seekBar']
15308  };
15309  Component$1.registerComponent('ProgressControl', ProgressControl);
15310
15311  /**
15312   * @file picture-in-picture-toggle.js
15313   */
15314
15315  /**
15316   * Toggle Picture-in-Picture mode
15317   *
15318   * @extends Button
15319   */
15320  class PictureInPictureToggle extends Button {
15321    /**
15322     * Creates an instance of this class.
15323     *
15324     * @param { import('./player').default } player
15325     *        The `Player` that this class should be attached to.
15326     *
15327     * @param {Object} [options]
15328     *        The key/value store of player options.
15329     *
15330     * @listens Player#enterpictureinpicture
15331     * @listens Player#leavepictureinpicture
15332     */
15333    constructor(player, options) {
15334      super(player, options);
15335      this.setIcon('picture-in-picture-enter');
15336      this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], e => this.handlePictureInPictureChange(e));
15337      this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], e => this.handlePictureInPictureEnabledChange(e));
15338      this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => this.handlePictureInPictureAudioModeChange());
15339
15340      // TODO: Deactivate button on player emptied event.
15341      this.disable();
15342    }
15343
15344    /**
15345     * Builds the default DOM `className`.
15346     *
15347     * @return {string}
15348     *         The DOM `className` for this object.
15349     */
15350    buildCSSClass() {
15351      return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
15352    }
15353
15354    /**
15355     * Displays or hides the button depending on the audio mode detection.
15356     * Exits picture-in-picture if it is enabled when switching to audio mode.
15357     */
15358    handlePictureInPictureAudioModeChange() {
15359      // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
15360      const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
15361      const isAudioMode = isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();
15362      if (!isAudioMode) {
15363        this.show();
15364        return;
15365      }
15366      if (this.player_.isInPictureInPicture()) {
15367        this.player_.exitPictureInPicture();
15368      }
15369      this.hide();
15370    }
15371
15372    /**
15373     * Enables or disables button based on availability of a Picture-In-Picture mode.
15374     *
15375     * Enabled if
15376     * - `player.options().enableDocumentPictureInPicture` is true and
15377     *   window.documentPictureInPicture is available; or
15378     * - `player.disablePictureInPicture()` is false and
15379     *   element.requestPictureInPicture is available
15380     */
15381    handlePictureInPictureEnabledChange() {
15382      if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false || this.player_.options_.enableDocumentPictureInPicture && 'documentPictureInPicture' in window) {
15383        this.enable();
15384      } else {
15385        this.disable();
15386      }
15387    }
15388
15389    /**
15390     * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
15391     *
15392     * @param {Event} [event]
15393     *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
15394     *        called.
15395     *
15396     * @listens Player#enterpictureinpicture
15397     * @listens Player#leavepictureinpicture
15398     */
15399    handlePictureInPictureChange(event) {
15400      if (this.player_.isInPictureInPicture()) {
15401        this.setIcon('picture-in-picture-exit');
15402        this.controlText('Exit Picture-in-Picture');
15403      } else {
15404        this.setIcon('picture-in-picture-enter');
15405        this.controlText('Picture-in-Picture');
15406      }
15407      this.handlePictureInPictureEnabledChange();
15408    }
15409
15410    /**
15411     * This gets called when an `PictureInPictureToggle` is "clicked". See
15412     * {@link ClickableComponent} for more detailed information on what a click can be.
15413     *
15414     * @param {Event} [event]
15415     *        The `keydown`, `tap`, or `click` event that caused this function to be
15416     *        called.
15417     *
15418     * @listens tap
15419     * @listens click
15420     */
15421    handleClick(event) {
15422      if (!this.player_.isInPictureInPicture()) {
15423        this.player_.requestPictureInPicture();
15424      } else {
15425        this.player_.exitPictureInPicture();
15426      }
15427    }
15428
15429    /**
15430     * Show the `Component`s element if it is hidden by removing the
15431     * 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
15432     */
15433    show() {
15434      // Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
15435      if (typeof document.exitPictureInPicture !== 'function') {
15436        return;
15437      }
15438      super.show();
15439    }
15440  }
15441
15442  /**
15443   * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
15444   *
15445   * @type {string}
15446   * @protected
15447   */
15448  PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
15449  Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
15450
15451  /**
15452   * @file fullscreen-toggle.js
15453   */
15454
15455  /**
15456   * Toggle fullscreen video
15457   *
15458   * @extends Button
15459   */
15460  class FullscreenToggle extends Button {
15461    /**
15462     * Creates an instance of this class.
15463     *
15464     * @param { import('./player').default } player
15465     *        The `Player` that this class should be attached to.
15466     *
15467     * @param {Object} [options]
15468     *        The key/value store of player options.
15469     */
15470    constructor(player, options) {
15471      super(player, options);
15472      this.setIcon('fullscreen-enter');
15473      this.on(player, 'fullscreenchange', e => this.handleFullscreenChange(e));
15474      if (document[player.fsApi_.fullscreenEnabled] === false) {
15475        this.disable();
15476      }
15477    }
15478
15479    /**
15480     * Builds the default DOM `className`.
15481     *
15482     * @return {string}
15483     *         The DOM `className` for this object.
15484     */
15485    buildCSSClass() {
15486      return `vjs-fullscreen-control ${super.buildCSSClass()}`;
15487    }
15488
15489    /**
15490     * Handles fullscreenchange on the player and change control text accordingly.
15491     *
15492     * @param {Event} [event]
15493     *        The {@link Player#fullscreenchange} event that caused this function to be
15494     *        called.
15495     *
15496     * @listens Player#fullscreenchange
15497     */
15498    handleFullscreenChange(event) {
15499      if (this.player_.isFullscreen()) {
15500        this.controlText('Exit Fullscreen');
15501        this.setIcon('fullscreen-exit');
15502      } else {
15503        this.controlText('Fullscreen');
15504        this.setIcon('fullscreen-enter');
15505      }
15506    }
15507
15508    /**
15509     * This gets called when an `FullscreenToggle` is "clicked". See
15510     * {@link ClickableComponent} for more detailed information on what a click can be.
15511     *
15512     * @param {Event} [event]
15513     *        The `keydown`, `tap`, or `click` event that caused this function to be
15514     *        called.
15515     *
15516     * @listens tap
15517     * @listens click
15518     */
15519    handleClick(event) {
15520      if (!this.player_.isFullscreen()) {
15521        this.player_.requestFullscreen();
15522      } else {
15523        this.player_.exitFullscreen();
15524      }
15525    }
15526  }
15527
15528  /**
15529   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
15530   *
15531   * @type {string}
15532   * @protected
15533   */
15534  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
15535  Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
15536
15537  /**
15538   * Check if volume control is supported and if it isn't hide the
15539   * `Component` that was passed  using the `vjs-hidden` class.
15540   *
15541   * @param { import('../../component').default } self
15542   *        The component that should be hidden if volume is unsupported
15543   *
15544   * @param { import('../../player').default } player
15545   *        A reference to the player
15546   *
15547   * @private
15548   */
15549  const checkVolumeSupport = function (self, player) {
15550    // hide volume controls when they're not supported by the current tech
15551    if (player.tech_ && !player.tech_.featuresVolumeControl) {
15552      self.addClass('vjs-hidden');
15553    }
15554    self.on(player, 'loadstart', function () {
15555      if (!player.tech_.featuresVolumeControl) {
15556        self.addClass('vjs-hidden');
15557      } else {
15558        self.removeClass('vjs-hidden');
15559      }
15560    });
15561  };
15562
15563  /**
15564   * @file volume-level.js
15565   */
15566
15567  /**
15568   * Shows volume level
15569   *
15570   * @extends Component
15571   */
15572  class VolumeLevel extends Component$1 {
15573    /**
15574     * Create the `Component`'s DOM element
15575     *
15576     * @return {Element}
15577     *         The element that was created.
15578     */
15579    createEl() {
15580      const el = super.createEl('div', {
15581        className: 'vjs-volume-level'
15582      });
15583      this.setIcon('circle', el);
15584      el.appendChild(super.createEl('span', {
15585        className: 'vjs-control-text'
15586      }));
15587      return el;
15588    }
15589  }
15590  Component$1.registerComponent('VolumeLevel', VolumeLevel);
15591
15592  /**
15593   * @file volume-level-tooltip.js
15594   */
15595
15596  /**
15597   * Volume level tooltips display a volume above or side by side the volume bar.
15598   *
15599   * @extends Component
15600   */
15601  class VolumeLevelTooltip extends Component$1 {
15602    /**
15603     * Creates an instance of this class.
15604     *
15605     * @param { import('../../player').default } player
15606     *        The {@link Player} that this class should be attached to.
15607     *
15608     * @param {Object} [options]
15609     *        The key/value store of player options.
15610     */
15611    constructor(player, options) {
15612      super(player, options);
15613      this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
15614    }
15615
15616    /**
15617     * Create the volume tooltip DOM element
15618     *
15619     * @return {Element}
15620     *         The element that was created.
15621     */
15622    createEl() {
15623      return super.createEl('div', {
15624        className: 'vjs-volume-tooltip'
15625      }, {
15626        'aria-hidden': 'true'
15627      });
15628    }
15629
15630    /**
15631     * Updates the position of the tooltip relative to the `VolumeBar` and
15632     * its content text.
15633     *
15634     * @param {Object} rangeBarRect
15635     *        The `ClientRect` for the {@link VolumeBar} element.
15636     *
15637     * @param {number} rangeBarPoint
15638     *        A number from 0 to 1, representing a horizontal/vertical reference point
15639     *        from the left edge of the {@link VolumeBar}
15640     *
15641     * @param {boolean} vertical
15642     *        Referees to the Volume control position
15643     *        in the control bar{@link VolumeControl}
15644     *
15645     */
15646    update(rangeBarRect, rangeBarPoint, vertical, content) {
15647      if (!vertical) {
15648        const tooltipRect = getBoundingClientRect(this.el_);
15649        const playerRect = getBoundingClientRect(this.player_.el());
15650        const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
15651        if (!playerRect || !tooltipRect) {
15652          return;
15653        }
15654        const spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
15655        const spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
15656        let pullTooltipBy = tooltipRect.width / 2;
15657        if (spaceLeftOfPoint < pullTooltipBy) {
15658          pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15659        } else if (spaceRightOfPoint < pullTooltipBy) {
15660          pullTooltipBy = spaceRightOfPoint;
15661        }
15662        if (pullTooltipBy < 0) {
15663          pullTooltipBy = 0;
15664        } else if (pullTooltipBy > tooltipRect.width) {
15665          pullTooltipBy = tooltipRect.width;
15666        }
15667        this.el_.style.right = `-${pullTooltipBy}px`;
15668      }
15669      this.write(`${content}%`);
15670    }
15671
15672    /**
15673     * Write the volume to the tooltip DOM element.
15674     *
15675     * @param {string} content
15676     *        The formatted volume for the tooltip.
15677     */
15678    write(content) {
15679      textContent(this.el_, content);
15680    }
15681
15682    /**
15683     * Updates the position of the volume tooltip relative to the `VolumeBar`.
15684     *
15685     * @param {Object} rangeBarRect
15686     *        The `ClientRect` for the {@link VolumeBar} element.
15687     *
15688     * @param {number} rangeBarPoint
15689     *        A number from 0 to 1, representing a horizontal/vertical reference point
15690     *        from the left edge of the {@link VolumeBar}
15691     *
15692     * @param {boolean} vertical
15693     *        Referees to the Volume control position
15694     *        in the control bar{@link VolumeControl}
15695     *
15696     * @param {number} volume
15697     *        The volume level to update the tooltip to
15698     *
15699     * @param {Function} cb
15700     *        A function that will be called during the request animation frame
15701     *        for tooltips that need to do additional animations from the default
15702     */
15703    updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
15704      this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
15705        this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
15706        if (cb) {
15707          cb();
15708        }
15709      });
15710    }
15711  }
15712  Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
15713
15714  /**
15715   * @file mouse-volume-level-display.js
15716   */
15717
15718  /**
15719   * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
15720   * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
15721   * indicating the volume level which is represented by a given point in the
15722   * {@link VolumeBar}.
15723   *
15724   * @extends Component
15725   */
15726  class MouseVolumeLevelDisplay extends Component$1 {
15727    /**
15728     * Creates an instance of this class.
15729     *
15730     * @param { import('../../player').default } player
15731     *        The {@link Player} that this class should be attached to.
15732     *
15733     * @param {Object} [options]
15734     *        The key/value store of player options.
15735     */
15736    constructor(player, options) {
15737      super(player, options);
15738      this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
15739    }
15740
15741    /**
15742     * Create the DOM element for this class.
15743     *
15744     * @return {Element}
15745     *         The element that was created.
15746     */
15747    createEl() {
15748      return super.createEl('div', {
15749        className: 'vjs-mouse-display'
15750      });
15751    }
15752
15753    /**
15754     * Enquires updates to its own DOM as well as the DOM of its
15755     * {@link VolumeLevelTooltip} child.
15756     *
15757     * @param {Object} rangeBarRect
15758     *        The `ClientRect` for the {@link VolumeBar} element.
15759     *
15760     * @param {number} rangeBarPoint
15761     *        A number from 0 to 1, representing a horizontal/vertical reference point
15762     *        from the left edge of the {@link VolumeBar}
15763     *
15764     * @param {boolean} vertical
15765     *        Referees to the Volume control position
15766     *        in the control bar{@link VolumeControl}
15767     *
15768     */
15769    update(rangeBarRect, rangeBarPoint, vertical) {
15770      const volume = 100 * rangeBarPoint;
15771      this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, () => {
15772        if (vertical) {
15773          this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
15774        } else {
15775          this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
15776        }
15777      });
15778    }
15779  }
15780
15781  /**
15782   * Default options for `MouseVolumeLevelDisplay`
15783   *
15784   * @type {Object}
15785   * @private
15786   */
15787  MouseVolumeLevelDisplay.prototype.options_ = {
15788    children: ['volumeLevelTooltip']
15789  };
15790  Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
15791
15792  /**
15793   * @file volume-bar.js
15794   */
15795
15796  /**
15797   * The bar that contains the volume level and can be clicked on to adjust the level
15798   *
15799   * @extends Slider
15800   */
15801  class VolumeBar extends Slider {
15802    /**
15803     * Creates an instance of this class.
15804     *
15805     * @param { import('../../player').default } player
15806     *        The `Player` that this class should be attached to.
15807     *
15808     * @param {Object} [options]
15809     *        The key/value store of player options.
15810     */
15811    constructor(player, options) {
15812      super(player, options);
15813      this.on('slideractive', e => this.updateLastVolume_(e));
15814      this.on(player, 'volumechange', e => this.updateARIAAttributes(e));
15815      player.ready(() => this.updateARIAAttributes());
15816    }
15817
15818    /**
15819     * Create the `Component`'s DOM element
15820     *
15821     * @return {Element}
15822     *         The element that was created.
15823     */
15824    createEl() {
15825      return super.createEl('div', {
15826        className: 'vjs-volume-bar vjs-slider-bar'
15827      }, {
15828        'aria-label': this.localize('Volume Level'),
15829        'aria-live': 'polite'
15830      });
15831    }
15832
15833    /**
15834     * Handle mouse down on volume bar
15835     *
15836     * @param {Event} event
15837     *        The `mousedown` event that caused this to run.
15838     *
15839     * @listens mousedown
15840     */
15841    handleMouseDown(event) {
15842      if (!isSingleLeftClick(event)) {
15843        return;
15844      }
15845      super.handleMouseDown(event);
15846    }
15847
15848    /**
15849     * Handle movement events on the {@link VolumeMenuButton}.
15850     *
15851     * @param {Event} event
15852     *        The event that caused this function to run.
15853     *
15854     * @listens mousemove
15855     */
15856    handleMouseMove(event) {
15857      const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
15858      if (mouseVolumeLevelDisplay) {
15859        const volumeBarEl = this.el();
15860        const volumeBarRect = getBoundingClientRect(volumeBarEl);
15861        const vertical = this.vertical();
15862        let volumeBarPoint = getPointerPosition(volumeBarEl, event);
15863        volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
15864        // The default skin has a gap on either side of the `VolumeBar`. This means
15865        // that it's possible to trigger this behavior outside the boundaries of
15866        // the `VolumeBar`. This ensures we stay within it at all times.
15867        volumeBarPoint = clamp(volumeBarPoint, 0, 1);
15868        mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
15869      }
15870      if (!isSingleLeftClick(event)) {
15871        return;
15872      }
15873      this.checkMuted();
15874      this.player_.volume(this.calculateDistance(event));
15875    }
15876
15877    /**
15878     * If the player is muted unmute it.
15879     */
15880    checkMuted() {
15881      if (this.player_.muted()) {
15882        this.player_.muted(false);
15883      }
15884    }
15885
15886    /**
15887     * Get percent of volume level
15888     *
15889     * @return {number}
15890     *         Volume level percent as a decimal number.
15891     */
15892    getPercent() {
15893      if (this.player_.muted()) {
15894        return 0;
15895      }
15896      return this.player_.volume();
15897    }
15898
15899    /**
15900     * Increase volume level for keyboard users
15901     */
15902    stepForward() {
15903      this.checkMuted();
15904      this.player_.volume(this.player_.volume() + 0.1);
15905    }
15906
15907    /**
15908     * Decrease volume level for keyboard users
15909     */
15910    stepBack() {
15911      this.checkMuted();
15912      this.player_.volume(this.player_.volume() - 0.1);
15913    }
15914
15915    /**
15916     * Update ARIA accessibility attributes
15917     *
15918     * @param {Event} [event]
15919     *        The `volumechange` event that caused this function to run.
15920     *
15921     * @listens Player#volumechange
15922     */
15923    updateARIAAttributes(event) {
15924      const ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15925      this.el_.setAttribute('aria-valuenow', ariaValue);
15926      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15927    }
15928
15929    /**
15930     * Returns the current value of the player volume as a percentage
15931     *
15932     * @private
15933     */
15934    volumeAsPercentage_() {
15935      return Math.round(this.player_.volume() * 100);
15936    }
15937
15938    /**
15939     * When user starts dragging the VolumeBar, store the volume and listen for
15940     * the end of the drag. When the drag ends, if the volume was set to zero,
15941     * set lastVolume to the stored volume.
15942     *
15943     * @listens slideractive
15944     * @private
15945     */
15946    updateLastVolume_() {
15947      const volumeBeforeDrag = this.player_.volume();
15948      this.one('sliderinactive', () => {
15949        if (this.player_.volume() === 0) {
15950          this.player_.lastVolume_(volumeBeforeDrag);
15951        }
15952      });
15953    }
15954  }
15955
15956  /**
15957   * Default options for the `VolumeBar`
15958   *
15959   * @type {Object}
15960   * @private
15961   */
15962  VolumeBar.prototype.options_ = {
15963    children: ['volumeLevel'],
15964    barName: 'volumeLevel'
15965  };
15966
15967  // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
15968  if (!IS_IOS && !IS_ANDROID) {
15969    VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
15970  }
15971
15972  /**
15973   * Call the update event for this Slider when this event happens on the player.
15974   *
15975   * @type {string}
15976   */
15977  VolumeBar.prototype.playerEvent = 'volumechange';
15978  Component$1.registerComponent('VolumeBar', VolumeBar);
15979
15980  /**
15981   * @file volume-control.js
15982   */
15983
15984  /**
15985   * The component for controlling the volume level
15986   *
15987   * @extends Component
15988   */
15989  class VolumeControl extends Component$1 {
15990    /**
15991     * Creates an instance of this class.
15992     *
15993     * @param { import('../../player').default } player
15994     *        The `Player` that this class should be attached to.
15995     *
15996     * @param {Object} [options={}]
15997     *        The key/value store of player options.
15998     */
15999    constructor(player, options = {}) {
16000      options.vertical = options.vertical || false;
16001
16002      // Pass the vertical option down to the VolumeBar if
16003      // the VolumeBar is turned on.
16004      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
16005        options.volumeBar = options.volumeBar || {};
16006        options.volumeBar.vertical = options.vertical;
16007      }
16008      super(player, options);
16009
16010      // hide this control if volume support is missing
16011      checkVolumeSupport(this, player);
16012      this.throttledHandleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
16013      this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
16014      this.on('mousedown', e => this.handleMouseDown(e));
16015      this.on('touchstart', e => this.handleMouseDown(e));
16016      this.on('mousemove', e => this.handleMouseMove(e));
16017
16018      // while the slider is active (the mouse has been pressed down and
16019      // is dragging) or in focus we do not want to hide the VolumeBar
16020      this.on(this.volumeBar, ['focus', 'slideractive'], () => {
16021        this.volumeBar.addClass('vjs-slider-active');
16022        this.addClass('vjs-slider-active');
16023        this.trigger('slideractive');
16024      });
16025      this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
16026        this.volumeBar.removeClass('vjs-slider-active');
16027        this.removeClass('vjs-slider-active');
16028        this.trigger('sliderinactive');
16029      });
16030    }
16031
16032    /**
16033     * Create the `Component`'s DOM element
16034     *
16035     * @return {Element}
16036     *         The element that was created.
16037     */
16038    createEl() {
16039      let orientationClass = 'vjs-volume-horizontal';
16040      if (this.options_.vertical) {
16041        orientationClass = 'vjs-volume-vertical';
16042      }
16043      return super.createEl('div', {
16044        className: `vjs-volume-control vjs-control ${orientationClass}`
16045      });
16046    }
16047
16048    /**
16049     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16050     *
16051     * @param {Event} event
16052     *        `mousedown` or `touchstart` event that triggered this function
16053     *
16054     * @listens mousedown
16055     * @listens touchstart
16056     */
16057    handleMouseDown(event) {
16058      const doc = this.el_.ownerDocument;
16059      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
16060      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
16061      this.on(doc, 'mouseup', this.handleMouseUpHandler_);
16062      this.on(doc, 'touchend', this.handleMouseUpHandler_);
16063    }
16064
16065    /**
16066     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
16067     *
16068     * @param {Event} event
16069     *        `mouseup` or `touchend` event that triggered this function.
16070     *
16071     * @listens touchend
16072     * @listens mouseup
16073     */
16074    handleMouseUp(event) {
16075      const doc = this.el_.ownerDocument;
16076      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
16077      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
16078      this.off(doc, 'mouseup', this.handleMouseUpHandler_);
16079      this.off(doc, 'touchend', this.handleMouseUpHandler_);
16080    }
16081
16082    /**
16083     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16084     *
16085     * @param {Event} event
16086     *        `mousedown` or `touchstart` event that triggered this function
16087     *
16088     * @listens mousedown
16089     * @listens touchstart
16090     */
16091    handleMouseMove(event) {
16092      this.volumeBar.handleMouseMove(event);
16093    }
16094  }
16095
16096  /**
16097   * Default options for the `VolumeControl`
16098   *
16099   * @type {Object}
16100   * @private
16101   */
16102  VolumeControl.prototype.options_ = {
16103    children: ['volumeBar']
16104  };
16105  Component$1.registerComponent('VolumeControl', VolumeControl);
16106
16107  /**
16108   * Check if muting volume is supported and if it isn't hide the mute toggle
16109   * button.
16110   *
16111   * @param { import('../../component').default } self
16112   *        A reference to the mute toggle button
16113   *
16114   * @param { import('../../player').default } player
16115   *        A reference to the player
16116   *
16117   * @private
16118   */
16119  const checkMuteSupport = function (self, player) {
16120    // hide mute toggle button if it's not supported by the current tech
16121    if (player.tech_ && !player.tech_.featuresMuteControl) {
16122      self.addClass('vjs-hidden');
16123    }
16124    self.on(player, 'loadstart', function () {
16125      if (!player.tech_.featuresMuteControl) {
16126        self.addClass('vjs-hidden');
16127      } else {
16128        self.removeClass('vjs-hidden');
16129      }
16130    });
16131  };
16132
16133  /**
16134   * @file mute-toggle.js
16135   */
16136
16137  /**
16138   * A button component for muting the audio.
16139   *
16140   * @extends Button
16141   */
16142  class MuteToggle extends Button {
16143    /**
16144     * Creates an instance of this class.
16145     *
16146     * @param { import('./player').default } player
16147     *        The `Player` that this class should be attached to.
16148     *
16149     * @param {Object} [options]
16150     *        The key/value store of player options.
16151     */
16152    constructor(player, options) {
16153      super(player, options);
16154
16155      // hide this control if volume support is missing
16156      checkMuteSupport(this, player);
16157      this.on(player, ['loadstart', 'volumechange'], e => this.update(e));
16158    }
16159
16160    /**
16161     * Builds the default DOM `className`.
16162     *
16163     * @return {string}
16164     *         The DOM `className` for this object.
16165     */
16166    buildCSSClass() {
16167      return `vjs-mute-control ${super.buildCSSClass()}`;
16168    }
16169
16170    /**
16171     * This gets called when an `MuteToggle` is "clicked". See
16172     * {@link ClickableComponent} for more detailed information on what a click can be.
16173     *
16174     * @param {Event} [event]
16175     *        The `keydown`, `tap`, or `click` event that caused this function to be
16176     *        called.
16177     *
16178     * @listens tap
16179     * @listens click
16180     */
16181    handleClick(event) {
16182      const vol = this.player_.volume();
16183      const lastVolume = this.player_.lastVolume_();
16184      if (vol === 0) {
16185        const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
16186        this.player_.volume(volumeToSet);
16187        this.player_.muted(false);
16188      } else {
16189        this.player_.muted(this.player_.muted() ? false : true);
16190      }
16191    }
16192
16193    /**
16194     * Update the `MuteToggle` button based on the state of `volume` and `muted`
16195     * on the player.
16196     *
16197     * @param {Event} [event]
16198     *        The {@link Player#loadstart} event if this function was called
16199     *        through an event.
16200     *
16201     * @listens Player#loadstart
16202     * @listens Player#volumechange
16203     */
16204    update(event) {
16205      this.updateIcon_();
16206      this.updateControlText_();
16207    }
16208
16209    /**
16210     * Update the appearance of the `MuteToggle` icon.
16211     *
16212     * Possible states (given `level` variable below):
16213     * - 0: crossed out
16214     * - 1: zero bars of volume
16215     * - 2: one bar of volume
16216     * - 3: two bars of volume
16217     *
16218     * @private
16219     */
16220    updateIcon_() {
16221      const vol = this.player_.volume();
16222      let level = 3;
16223      this.setIcon('volume-high');
16224
16225      // in iOS when a player is loaded with muted attribute
16226      // and volume is changed with a native mute button
16227      // we want to make sure muted state is updated
16228      if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
16229        this.player_.muted(this.player_.tech_.el_.muted);
16230      }
16231      if (vol === 0 || this.player_.muted()) {
16232        this.setIcon('volume-mute');
16233        level = 0;
16234      } else if (vol < 0.33) {
16235        this.setIcon('volume-low');
16236        level = 1;
16237      } else if (vol < 0.67) {
16238        this.setIcon('volume-medium');
16239        level = 2;
16240      }
16241      removeClass(this.el_, [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, ''));
16242      addClass(this.el_, `vjs-vol-${level}`);
16243    }
16244
16245    /**
16246     * If `muted` has changed on the player, update the control text
16247     * (`title` attribute on `vjs-mute-control` element and content of
16248     * `vjs-control-text` element).
16249     *
16250     * @private
16251     */
16252    updateControlText_() {
16253      const soundOff = this.player_.muted() || this.player_.volume() === 0;
16254      const text = soundOff ? 'Unmute' : 'Mute';
16255      if (this.controlText() !== text) {
16256        this.controlText(text);
16257      }
16258    }
16259  }
16260
16261  /**
16262   * The text that should display over the `MuteToggle`s controls. Added for localization.
16263   *
16264   * @type {string}
16265   * @protected
16266   */
16267  MuteToggle.prototype.controlText_ = 'Mute';
16268  Component$1.registerComponent('MuteToggle', MuteToggle);
16269
16270  /**
16271   * @file volume-control.js
16272   */
16273
16274  /**
16275   * A Component to contain the MuteToggle and VolumeControl so that
16276   * they can work together.
16277   *
16278   * @extends Component
16279   */
16280  class VolumePanel extends Component$1 {
16281    /**
16282     * Creates an instance of this class.
16283     *
16284     * @param { import('./player').default } player
16285     *        The `Player` that this class should be attached to.
16286     *
16287     * @param {Object} [options={}]
16288     *        The key/value store of player options.
16289     */
16290    constructor(player, options = {}) {
16291      if (typeof options.inline !== 'undefined') {
16292        options.inline = options.inline;
16293      } else {
16294        options.inline = true;
16295      }
16296
16297      // pass the inline option down to the VolumeControl as vertical if
16298      // the VolumeControl is on.
16299      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
16300        options.volumeControl = options.volumeControl || {};
16301        options.volumeControl.vertical = !options.inline;
16302      }
16303      super(player, options);
16304
16305      // this handler is used by mouse handler methods below
16306      this.handleKeyPressHandler_ = e => this.handleKeyPress(e);
16307      this.on(player, ['loadstart'], e => this.volumePanelState_(e));
16308      this.on(this.muteToggle, 'keyup', e => this.handleKeyPress(e));
16309      this.on(this.volumeControl, 'keyup', e => this.handleVolumeControlKeyUp(e));
16310      this.on('keydown', e => this.handleKeyPress(e));
16311      this.on('mouseover', e => this.handleMouseOver(e));
16312      this.on('mouseout', e => this.handleMouseOut(e));
16313
16314      // while the slider is active (the mouse has been pressed down and
16315      // is dragging) we do not want to hide the VolumeBar
16316      this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
16317      this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
16318    }
16319
16320    /**
16321     * Add vjs-slider-active class to the VolumePanel
16322     *
16323     * @listens VolumeControl#slideractive
16324     * @private
16325     */
16326    sliderActive_() {
16327      this.addClass('vjs-slider-active');
16328    }
16329
16330    /**
16331     * Removes vjs-slider-active class to the VolumePanel
16332     *
16333     * @listens VolumeControl#sliderinactive
16334     * @private
16335     */
16336    sliderInactive_() {
16337      this.removeClass('vjs-slider-active');
16338    }
16339
16340    /**
16341     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
16342     * depending on MuteToggle and VolumeControl state
16343     *
16344     * @listens Player#loadstart
16345     * @private
16346     */
16347    volumePanelState_() {
16348      // hide volume panel if neither volume control or mute toggle
16349      // are displayed
16350      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
16351        this.addClass('vjs-hidden');
16352      }
16353
16354      // if only mute toggle is visible we don't want
16355      // volume panel expanding when hovered or active
16356      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
16357        this.addClass('vjs-mute-toggle-only');
16358      }
16359    }
16360
16361    /**
16362     * Create the `Component`'s DOM element
16363     *
16364     * @return {Element}
16365     *         The element that was created.
16366     */
16367    createEl() {
16368      let orientationClass = 'vjs-volume-panel-horizontal';
16369      if (!this.options_.inline) {
16370        orientationClass = 'vjs-volume-panel-vertical';
16371      }
16372      return super.createEl('div', {
16373        className: `vjs-volume-panel vjs-control ${orientationClass}`
16374      });
16375    }
16376
16377    /**
16378     * Dispose of the `volume-panel` and all child components.
16379     */
16380    dispose() {
16381      this.handleMouseOut();
16382      super.dispose();
16383    }
16384
16385    /**
16386     * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
16387     * the volume panel and sets focus on `MuteToggle`.
16388     *
16389     * @param {Event} event
16390     *        The `keyup` event that caused this function to be called.
16391     *
16392     * @listens keyup
16393     */
16394    handleVolumeControlKeyUp(event) {
16395      if (keycode.isEventKey(event, 'Esc')) {
16396        this.muteToggle.focus();
16397      }
16398    }
16399
16400    /**
16401     * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
16402     * Turns on listening for `mouseover` event. When they happen it
16403     * calls `this.handleMouseOver`.
16404     *
16405     * @param {Event} event
16406     *        The `mouseover` event that caused this function to be called.
16407     *
16408     * @listens mouseover
16409     */
16410    handleMouseOver(event) {
16411      this.addClass('vjs-hover');
16412      on(document, 'keyup', this.handleKeyPressHandler_);
16413    }
16414
16415    /**
16416     * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
16417     * Turns on listening for `mouseout` event. When they happen it
16418     * calls `this.handleMouseOut`.
16419     *
16420     * @param {Event} event
16421     *        The `mouseout` event that caused this function to be called.
16422     *
16423     * @listens mouseout
16424     */
16425    handleMouseOut(event) {
16426      this.removeClass('vjs-hover');
16427      off(document, 'keyup', this.handleKeyPressHandler_);
16428    }
16429
16430    /**
16431     * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
16432     * looking for ESC, which hides the `VolumeControl`.
16433     *
16434     * @param {Event} event
16435     *        The keypress that triggered this event.
16436     *
16437     * @listens keydown | keyup
16438     */
16439    handleKeyPress(event) {
16440      if (keycode.isEventKey(event, 'Esc')) {
16441        this.handleMouseOut();
16442      }
16443    }
16444  }
16445
16446  /**
16447   * Default options for the `VolumeControl`
16448   *
16449   * @type {Object}
16450   * @private
16451   */
16452  VolumePanel.prototype.options_ = {
16453    children: ['muteToggle', 'volumeControl']
16454  };
16455  Component$1.registerComponent('VolumePanel', VolumePanel);
16456
16457  /**
16458   * Button to skip forward a configurable amount of time
16459   * through a video. Renders in the control bar.
16460   *
16461   * e.g. options: {controlBar: {skipButtons: forward: 5}}
16462   *
16463   * @extends Button
16464   */
16465  class SkipForward extends Button {
16466    constructor(player, options) {
16467      super(player, options);
16468      this.validOptions = [5, 10, 30];
16469      this.skipTime = this.getSkipForwardTime();
16470      if (this.skipTime && this.validOptions.includes(this.skipTime)) {
16471        this.setIcon(`forward-${this.skipTime}`);
16472        this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
16473        this.show();
16474      } else {
16475        this.hide();
16476      }
16477    }
16478    getSkipForwardTime() {
16479      const playerOptions = this.options_.playerOptions;
16480      return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.forward;
16481    }
16482    buildCSSClass() {
16483      return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
16484    }
16485
16486    /**
16487     * On click, skips forward in the duration/seekable range by a configurable amount of seconds.
16488     * If the time left in the duration/seekable range is less than the configured 'skip forward' time,
16489     * skips to end of duration/seekable range.
16490     *
16491     * Handle a click on a `SkipForward` button
16492     *
16493     * @param {EventTarget~Event} event
16494     *        The `click` event that caused this function
16495     *        to be called
16496     */
16497    handleClick(event) {
16498      if (isNaN(this.player_.duration())) {
16499        return;
16500      }
16501      const currentVideoTime = this.player_.currentTime();
16502      const liveTracker = this.player_.liveTracker;
16503      const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
16504      let newTime;
16505      if (currentVideoTime + this.skipTime <= duration) {
16506        newTime = currentVideoTime + this.skipTime;
16507      } else {
16508        newTime = duration;
16509      }
16510      this.player_.currentTime(newTime);
16511    }
16512
16513    /**
16514     * Update control text on languagechange
16515     */
16516    handleLanguagechange() {
16517      this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
16518    }
16519  }
16520  SkipForward.prototype.controlText_ = 'Skip Forward';
16521  Component$1.registerComponent('SkipForward', SkipForward);
16522
16523  /**
16524   * Button to skip backward a configurable amount of time
16525   * through a video. Renders in the control bar.
16526   *
16527   *  * e.g. options: {controlBar: {skipButtons: backward: 5}}
16528   *
16529   * @extends Button
16530   */
16531  class SkipBackward extends Button {
16532    constructor(player, options) {
16533      super(player, options);
16534      this.validOptions = [5, 10, 30];
16535      this.skipTime = this.getSkipBackwardTime();
16536      if (this.skipTime && this.validOptions.includes(this.skipTime)) {
16537        this.setIcon(`replay-${this.skipTime}`);
16538        this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
16539        this.show();
16540      } else {
16541        this.hide();
16542      }
16543    }
16544    getSkipBackwardTime() {
16545      const playerOptions = this.options_.playerOptions;
16546      return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.backward;
16547    }
16548    buildCSSClass() {
16549      return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
16550    }
16551
16552    /**
16553     * On click, skips backward in the video by a configurable amount of seconds.
16554     * If the current time in the video is less than the configured 'skip backward' time,
16555     * skips to beginning of video or seekable range.
16556     *
16557     * Handle a click on a `SkipBackward` button
16558     *
16559     * @param {EventTarget~Event} event
16560     *        The `click` event that caused this function
16561     *        to be called
16562     */
16563    handleClick(event) {
16564      const currentVideoTime = this.player_.currentTime();
16565      const liveTracker = this.player_.liveTracker;
16566      const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
16567      let newTime;
16568      if (seekableStart && currentVideoTime - this.skipTime <= seekableStart) {
16569        newTime = seekableStart;
16570      } else if (currentVideoTime >= this.skipTime) {
16571        newTime = currentVideoTime - this.skipTime;
16572      } else {
16573        newTime = 0;
16574      }
16575      this.player_.currentTime(newTime);
16576    }
16577
16578    /**
16579     * Update control text on languagechange
16580     */
16581    handleLanguagechange() {
16582      this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
16583    }
16584  }
16585  SkipBackward.prototype.controlText_ = 'Skip Backward';
16586  Component$1.registerComponent('SkipBackward', SkipBackward);
16587
16588  /**
16589   * @file menu.js
16590   */
16591
16592  /**
16593   * The Menu component is used to build popup menus, including subtitle and
16594   * captions selection menus.
16595   *
16596   * @extends Component
16597   */
16598  class Menu extends Component$1 {
16599    /**
16600     * Create an instance of this class.
16601     *
16602     * @param { import('../player').default } player
16603     *        the player that this component should attach to
16604     *
16605     * @param {Object} [options]
16606     *        Object of option names and values
16607     *
16608     */
16609    constructor(player, options) {
16610      super(player, options);
16611      if (options) {
16612        this.menuButton_ = options.menuButton;
16613      }
16614      this.focusedChild_ = -1;
16615      this.on('keydown', e => this.handleKeyDown(e));
16616
16617      // All the menu item instances share the same blur handler provided by the menu container.
16618      this.boundHandleBlur_ = e => this.handleBlur(e);
16619      this.boundHandleTapClick_ = e => this.handleTapClick(e);
16620    }
16621
16622    /**
16623     * Add event listeners to the {@link MenuItem}.
16624     *
16625     * @param {Object} component
16626     *        The instance of the `MenuItem` to add listeners to.
16627     *
16628     */
16629    addEventListenerForItem(component) {
16630      if (!(component instanceof Component$1)) {
16631        return;
16632      }
16633      this.on(component, 'blur', this.boundHandleBlur_);
16634      this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
16635    }
16636
16637    /**
16638     * Remove event listeners from the {@link MenuItem}.
16639     *
16640     * @param {Object} component
16641     *        The instance of the `MenuItem` to remove listeners.
16642     *
16643     */
16644    removeEventListenerForItem(component) {
16645      if (!(component instanceof Component$1)) {
16646        return;
16647      }
16648      this.off(component, 'blur', this.boundHandleBlur_);
16649      this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
16650    }
16651
16652    /**
16653     * This method will be called indirectly when the component has been added
16654     * before the component adds to the new menu instance by `addItem`.
16655     * In this case, the original menu instance will remove the component
16656     * by calling `removeChild`.
16657     *
16658     * @param {Object} component
16659     *        The instance of the `MenuItem`
16660     */
16661    removeChild(component) {
16662      if (typeof component === 'string') {
16663        component = this.getChild(component);
16664      }
16665      this.removeEventListenerForItem(component);
16666      super.removeChild(component);
16667    }
16668
16669    /**
16670     * Add a {@link MenuItem} to the menu.
16671     *
16672     * @param {Object|string} component
16673     *        The name or instance of the `MenuItem` to add.
16674     *
16675     */
16676    addItem(component) {
16677      const childComponent = this.addChild(component);
16678      if (childComponent) {
16679        this.addEventListenerForItem(childComponent);
16680      }
16681    }
16682
16683    /**
16684     * Create the `Menu`s DOM element.
16685     *
16686     * @return {Element}
16687     *         the element that was created
16688     */
16689    createEl() {
16690      const contentElType = this.options_.contentElType || 'ul';
16691      this.contentEl_ = createEl(contentElType, {
16692        className: 'vjs-menu-content'
16693      });
16694      this.contentEl_.setAttribute('role', 'menu');
16695      const el = super.createEl('div', {
16696        append: this.contentEl_,
16697        className: 'vjs-menu'
16698      });
16699      el.appendChild(this.contentEl_);
16700
16701      // Prevent clicks from bubbling up. Needed for Menu Buttons,
16702      // where a click on the parent is significant
16703      on(el, 'click', function (event) {
16704        event.preventDefault();
16705        event.stopImmediatePropagation();
16706      });
16707      return el;
16708    }
16709    dispose() {
16710      this.contentEl_ = null;
16711      this.boundHandleBlur_ = null;
16712      this.boundHandleTapClick_ = null;
16713      super.dispose();
16714    }
16715
16716    /**
16717     * Called when a `MenuItem` loses focus.
16718     *
16719     * @param {Event} event
16720     *        The `blur` event that caused this function to be called.
16721     *
16722     * @listens blur
16723     */
16724    handleBlur(event) {
16725      const relatedTarget = event.relatedTarget || document.activeElement;
16726
16727      // Close menu popup when a user clicks outside the menu
16728      if (!this.children().some(element => {
16729        return element.el() === relatedTarget;
16730      })) {
16731        const btn = this.menuButton_;
16732        if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
16733          btn.unpressButton();
16734        }
16735      }
16736    }
16737
16738    /**
16739     * Called when a `MenuItem` gets clicked or tapped.
16740     *
16741     * @param {Event} event
16742     *        The `click` or `tap` event that caused this function to be called.
16743     *
16744     * @listens click,tap
16745     */
16746    handleTapClick(event) {
16747      // Unpress the associated MenuButton, and move focus back to it
16748      if (this.menuButton_) {
16749        this.menuButton_.unpressButton();
16750        const childComponents = this.children();
16751        if (!Array.isArray(childComponents)) {
16752          return;
16753        }
16754        const foundComponent = childComponents.filter(component => component.el() === event.target)[0];
16755        if (!foundComponent) {
16756          return;
16757        }
16758
16759        // don't focus menu button if item is a caption settings item
16760        // because focus will move elsewhere
16761        if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
16762          this.menuButton_.focus();
16763        }
16764      }
16765    }
16766
16767    /**
16768     * Handle a `keydown` event on this menu. This listener is added in the constructor.
16769     *
16770     * @param {KeyboardEvent} event
16771     *        A `keydown` event that happened on the menu.
16772     *
16773     * @listens keydown
16774     */
16775    handleKeyDown(event) {
16776      // Left and Down Arrows
16777      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
16778        event.preventDefault();
16779        event.stopPropagation();
16780        this.stepForward();
16781
16782        // Up and Right Arrows
16783      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
16784        event.preventDefault();
16785        event.stopPropagation();
16786        this.stepBack();
16787      }
16788    }
16789
16790    /**
16791     * Move to next (lower) menu item for keyboard users.
16792     */
16793    stepForward() {
16794      let stepChild = 0;
16795      if (this.focusedChild_ !== undefined) {
16796        stepChild = this.focusedChild_ + 1;
16797      }
16798      this.focus(stepChild);
16799    }
16800
16801    /**
16802     * Move to previous (higher) menu item for keyboard users.
16803     */
16804    stepBack() {
16805      let stepChild = 0;
16806      if (this.focusedChild_ !== undefined) {
16807        stepChild = this.focusedChild_ - 1;
16808      }
16809      this.focus(stepChild);
16810    }
16811
16812    /**
16813     * Set focus on a {@link MenuItem} in the `Menu`.
16814     *
16815     * @param {Object|string} [item=0]
16816     *        Index of child item set focus on.
16817     */
16818    focus(item = 0) {
16819      const children = this.children().slice();
16820      const haveTitle = children.length && children[0].hasClass('vjs-menu-title');
16821      if (haveTitle) {
16822        children.shift();
16823      }
16824      if (children.length > 0) {
16825        if (item < 0) {
16826          item = 0;
16827        } else if (item >= children.length) {
16828          item = children.length - 1;
16829        }
16830        this.focusedChild_ = item;
16831        children[item].el_.focus();
16832      }
16833    }
16834  }
16835  Component$1.registerComponent('Menu', Menu);
16836
16837  /**
16838   * @file menu-button.js
16839   */
16840
16841  /**
16842   * A `MenuButton` class for any popup {@link Menu}.
16843   *
16844   * @extends Component
16845   */
16846  class MenuButton extends Component$1 {
16847    /**
16848     * Creates an instance of this class.
16849     *
16850     * @param { import('../player').default } player
16851     *        The `Player` that this class should be attached to.
16852     *
16853     * @param {Object} [options={}]
16854     *        The key/value store of player options.
16855     */
16856    constructor(player, options = {}) {
16857      super(player, options);
16858      this.menuButton_ = new Button(player, options);
16859      this.menuButton_.controlText(this.controlText_);
16860      this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
16861
16862      // Add buildCSSClass values to the button, not the wrapper
16863      const buttonClass = Button.prototype.buildCSSClass();
16864      this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
16865      this.menuButton_.removeClass('vjs-control');
16866      this.addChild(this.menuButton_);
16867      this.update();
16868      this.enabled_ = true;
16869      const handleClick = e => this.handleClick(e);
16870      this.handleMenuKeyUp_ = e => this.handleMenuKeyUp(e);
16871      this.on(this.menuButton_, 'tap', handleClick);
16872      this.on(this.menuButton_, 'click', handleClick);
16873      this.on(this.menuButton_, 'keydown', e => this.handleKeyDown(e));
16874      this.on(this.menuButton_, 'mouseenter', () => {
16875        this.addClass('vjs-hover');
16876        this.menu.show();
16877        on(document, 'keyup', this.handleMenuKeyUp_);
16878      });
16879      this.on('mouseleave', e => this.handleMouseLeave(e));
16880      this.on('keydown', e => this.handleSubmenuKeyDown(e));
16881    }
16882
16883    /**
16884     * Update the menu based on the current state of its items.
16885     */
16886    update() {
16887      const menu = this.createMenu();
16888      if (this.menu) {
16889        this.menu.dispose();
16890        this.removeChild(this.menu);
16891      }
16892      this.menu = menu;
16893      this.addChild(menu);
16894
16895      /**
16896       * Track the state of the menu button
16897       *
16898       * @type {Boolean}
16899       * @private
16900       */
16901      this.buttonPressed_ = false;
16902      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16903      if (this.items && this.items.length <= this.hideThreshold_) {
16904        this.hide();
16905        this.menu.contentEl_.removeAttribute('role');
16906      } else {
16907        this.show();
16908        this.menu.contentEl_.setAttribute('role', 'menu');
16909      }
16910    }
16911
16912    /**
16913     * Create the menu and add all items to it.
16914     *
16915     * @return {Menu}
16916     *         The constructed menu
16917     */
16918    createMenu() {
16919      const menu = new Menu(this.player_, {
16920        menuButton: this
16921      });
16922
16923      /**
16924       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16925       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16926       * it here because every time we run `createMenu` we need to reset the value.
16927       *
16928       * @protected
16929       * @type {Number}
16930       */
16931      this.hideThreshold_ = 0;
16932
16933      // Add a title list item to the top
16934      if (this.options_.title) {
16935        const titleEl = createEl('li', {
16936          className: 'vjs-menu-title',
16937          textContent: toTitleCase$1(this.options_.title),
16938          tabIndex: -1
16939        });
16940        const titleComponent = new Component$1(this.player_, {
16941          el: titleEl
16942        });
16943        menu.addItem(titleComponent);
16944      }
16945      this.items = this.createItems();
16946      if (this.items) {
16947        // Add menu items to the menu
16948        for (let i = 0; i < this.items.length; i++) {
16949          menu.addItem(this.items[i]);
16950        }
16951      }
16952      return menu;
16953    }
16954
16955    /**
16956     * Create the list of menu items. Specific to each subclass.
16957     *
16958     * @abstract
16959     */
16960    createItems() {}
16961
16962    /**
16963     * Create the `MenuButtons`s DOM element.
16964     *
16965     * @return {Element}
16966     *         The element that gets created.
16967     */
16968    createEl() {
16969      return super.createEl('div', {
16970        className: this.buildWrapperCSSClass()
16971      }, {});
16972    }
16973
16974    /**
16975     * Overwrites the `setIcon` method from `Component`.
16976     * In this case, we want the icon to be appended to the menuButton.
16977     *
16978     * @param {string} name
16979     *         The icon name to be added.
16980     */
16981    setIcon(name) {
16982      super.setIcon(name, this.menuButton_.el_);
16983    }
16984
16985    /**
16986     * Allow sub components to stack CSS class names for the wrapper element
16987     *
16988     * @return {string}
16989     *         The constructed wrapper DOM `className`
16990     */
16991    buildWrapperCSSClass() {
16992      let menuButtonClass = 'vjs-menu-button';
16993
16994      // If the inline option is passed, we want to use different styles altogether.
16995      if (this.options_.inline === true) {
16996        menuButtonClass += '-inline';
16997      } else {
16998        menuButtonClass += '-popup';
16999      }
17000
17001      // TODO: Fix the CSS so that this isn't necessary
17002      const buttonClass = Button.prototype.buildCSSClass();
17003      return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
17004    }
17005
17006    /**
17007     * Builds the default DOM `className`.
17008     *
17009     * @return {string}
17010     *         The DOM `className` for this object.
17011     */
17012    buildCSSClass() {
17013      let menuButtonClass = 'vjs-menu-button';
17014
17015      // If the inline option is passed, we want to use different styles altogether.
17016      if (this.options_.inline === true) {
17017        menuButtonClass += '-inline';
17018      } else {
17019        menuButtonClass += '-popup';
17020      }
17021      return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
17022    }
17023
17024    /**
17025     * Get or set the localized control text that will be used for accessibility.
17026     *
17027     * > NOTE: This will come from the internal `menuButton_` element.
17028     *
17029     * @param {string} [text]
17030     *        Control text for element.
17031     *
17032     * @param {Element} [el=this.menuButton_.el()]
17033     *        Element to set the title on.
17034     *
17035     * @return {string}
17036     *         - The control text when getting
17037     */
17038    controlText(text, el = this.menuButton_.el()) {
17039      return this.menuButton_.controlText(text, el);
17040    }
17041
17042    /**
17043     * Dispose of the `menu-button` and all child components.
17044     */
17045    dispose() {
17046      this.handleMouseLeave();
17047      super.dispose();
17048    }
17049
17050    /**
17051     * Handle a click on a `MenuButton`.
17052     * See {@link ClickableComponent#handleClick} for instances where this is called.
17053     *
17054     * @param {Event} event
17055     *        The `keydown`, `tap`, or `click` event that caused this function to be
17056     *        called.
17057     *
17058     * @listens tap
17059     * @listens click
17060     */
17061    handleClick(event) {
17062      if (this.buttonPressed_) {
17063        this.unpressButton();
17064      } else {
17065        this.pressButton();
17066      }
17067    }
17068
17069    /**
17070     * Handle `mouseleave` for `MenuButton`.
17071     *
17072     * @param {Event} event
17073     *        The `mouseleave` event that caused this function to be called.
17074     *
17075     * @listens mouseleave
17076     */
17077    handleMouseLeave(event) {
17078      this.removeClass('vjs-hover');
17079      off(document, 'keyup', this.handleMenuKeyUp_);
17080    }
17081
17082    /**
17083     * Set the focus to the actual button, not to this element
17084     */
17085    focus() {
17086      this.menuButton_.focus();
17087    }
17088
17089    /**
17090     * Remove the focus from the actual button, not this element
17091     */
17092    blur() {
17093      this.menuButton_.blur();
17094    }
17095
17096    /**
17097     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
17098     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17099     *
17100     * @param {Event} event
17101     *        The `keydown` event that caused this function to be called.
17102     *
17103     * @listens keydown
17104     */
17105    handleKeyDown(event) {
17106      // Escape or Tab unpress the 'button'
17107      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17108        if (this.buttonPressed_) {
17109          this.unpressButton();
17110        }
17111
17112        // Don't preventDefault for Tab key - we still want to lose focus
17113        if (!keycode.isEventKey(event, 'Tab')) {
17114          event.preventDefault();
17115          // Set focus back to the menu button's button
17116          this.menuButton_.focus();
17117        }
17118        // Up Arrow or Down Arrow also 'press' the button to open the menu
17119      } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
17120        if (!this.buttonPressed_) {
17121          event.preventDefault();
17122          this.pressButton();
17123        }
17124      }
17125    }
17126
17127    /**
17128     * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
17129     * the constructor.
17130     *
17131     * @param {Event} event
17132     *        Key press event
17133     *
17134     * @listens keyup
17135     */
17136    handleMenuKeyUp(event) {
17137      // Escape hides popup menu
17138      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17139        this.removeClass('vjs-hover');
17140      }
17141    }
17142
17143    /**
17144     * This method name now delegates to `handleSubmenuKeyDown`. This means
17145     * anyone calling `handleSubmenuKeyPress` will not see their method calls
17146     * stop working.
17147     *
17148     * @param {Event} event
17149     *        The event that caused this function to be called.
17150     */
17151    handleSubmenuKeyPress(event) {
17152      this.handleSubmenuKeyDown(event);
17153    }
17154
17155    /**
17156     * Handle a `keydown` event on a sub-menu. The listener for this is added in
17157     * the constructor.
17158     *
17159     * @param {Event} event
17160     *        Key press event
17161     *
17162     * @listens keydown
17163     */
17164    handleSubmenuKeyDown(event) {
17165      // Escape or Tab unpress the 'button'
17166      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17167        if (this.buttonPressed_) {
17168          this.unpressButton();
17169        }
17170        // Don't preventDefault for Tab key - we still want to lose focus
17171        if (!keycode.isEventKey(event, 'Tab')) {
17172          event.preventDefault();
17173          // Set focus back to the menu button's button
17174          this.menuButton_.focus();
17175        }
17176      }
17177    }
17178
17179    /**
17180     * Put the current `MenuButton` into a pressed state.
17181     */
17182    pressButton() {
17183      if (this.enabled_) {
17184        this.buttonPressed_ = true;
17185        this.menu.show();
17186        this.menu.lockShowing();
17187        this.menuButton_.el_.setAttribute('aria-expanded', 'true');
17188
17189        // set the focus into the submenu, except on iOS where it is resulting in
17190        // undesired scrolling behavior when the player is in an iframe
17191        if (IS_IOS && isInFrame()) {
17192          // Return early so that the menu isn't focused
17193          return;
17194        }
17195        this.menu.focus();
17196      }
17197    }
17198
17199    /**
17200     * Take the current `MenuButton` out of a pressed state.
17201     */
17202    unpressButton() {
17203      if (this.enabled_) {
17204        this.buttonPressed_ = false;
vendor: 4,795 bytes, lines 17205-17367
17205        this.menu.unlockShowing();
17206        this.menu.hide();
17207        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17208      }
17209    }
17210
17211    /**
17212     * Disable the `MenuButton`. Don't allow it to be clicked.
17213     */
17214    disable() {
17215      this.unpressButton();
17216      this.enabled_ = false;
17217      this.addClass('vjs-disabled');
17218      this.menuButton_.disable();
17219    }
17220
17221    /**
17222     * Enable the `MenuButton`. Allow it to be clicked.
17223     */
17224    enable() {
17225      this.enabled_ = true;
17226      this.removeClass('vjs-disabled');
17227      this.menuButton_.enable();
17228    }
17229  }
17230  Component$1.registerComponent('MenuButton', MenuButton);
17231
17232  /**
17233   * @file track-button.js
17234   */
17235
17236  /**
17237   * The base class for buttons that toggle specific  track types (e.g. subtitles).
17238   *
17239   * @extends MenuButton
17240   */
17241  class TrackButton extends MenuButton {
17242    /**
17243     * Creates an instance of this class.
17244     *
17245     * @param { import('./player').default } player
17246     *        The `Player` that this class should be attached to.
17247     *
17248     * @param {Object} [options]
17249     *        The key/value store of player options.
17250     */
17251    constructor(player, options) {
17252      const tracks = options.tracks;
17253      super(player, options);
17254      if (this.items.length <= 1) {
17255        this.hide();
17256      }
17257      if (!tracks) {
17258        return;
17259      }
17260      const updateHandler = bind_(this, this.update);
17261      tracks.addEventListener('removetrack', updateHandler);
17262      tracks.addEventListener('addtrack', updateHandler);
17263      tracks.addEventListener('labelchange', updateHandler);
17264      this.player_.on('ready', updateHandler);
17265      this.player_.on('dispose', function () {
17266        tracks.removeEventListener('removetrack', updateHandler);
17267        tracks.removeEventListener('addtrack', updateHandler);
17268        tracks.removeEventListener('labelchange', updateHandler);
17269      });
17270    }
17271  }
17272  Component$1.registerComponent('TrackButton', TrackButton);
17273
17274  /**
17275   * @file menu-keys.js
17276   */
17277
17278  /**
17279    * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
17280    * Note that 'Enter' and 'Space' are not included here (otherwise they would
17281    * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
17282   *
17283    * @typedef MenuKeys
17284    * @array
17285    */
17286  const MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
17287
17288  /**
17289   * @file menu-item.js
17290   */
17291
17292  /**
17293   * The component for a menu item. `<li>`
17294   *
17295   * @extends ClickableComponent
17296   */
17297  class MenuItem extends ClickableComponent {
17298    /**
17299     * Creates an instance of the this class.
17300     *
17301     * @param { import('../player').default } player
17302     *        The `Player` that this class should be attached to.
17303     *
17304     * @param {Object} [options={}]
17305     *        The key/value store of player options.
17306     *
17307     */
17308    constructor(player, options) {
17309      super(player, options);
17310      this.selectable = options.selectable;
17311      this.isSelected_ = options.selected || false;
17312      this.multiSelectable = options.multiSelectable;
17313      this.selected(this.isSelected_);
17314      if (this.selectable) {
17315        if (this.multiSelectable) {
17316          this.el_.setAttribute('role', 'menuitemcheckbox');
17317        } else {
17318          this.el_.setAttribute('role', 'menuitemradio');
17319        }
17320      } else {
17321        this.el_.setAttribute('role', 'menuitem');
17322      }
17323    }
17324
17325    /**
17326     * Create the `MenuItem's DOM element
17327     *
17328     * @param {string} [type=li]
17329     *        Element's node type, not actually used, always set to `li`.
17330     *
17331     * @param {Object} [props={}]
17332     *        An object of properties that should be set on the element
17333     *
17334     * @param {Object} [attrs={}]
17335     *        An object of attributes that should be set on the element
17336     *
17337     * @return {Element}
17338     *         The element that gets created.
17339     */
17340    createEl(type, props, attrs) {
17341      // The control is textual, not just an icon
17342      this.nonIconControl = true;
17343      const el = super.createEl('li', Object.assign({
17344        className: 'vjs-menu-item',
17345        tabIndex: -1
17346      }, props), attrs);
17347
17348      // swap icon with menu item text.
17349      const menuItemEl = createEl('span', {
17350        className: 'vjs-menu-item-text',
17351        textContent: this.localize(this.options_.label)
17352      });
17353
17354      // If using SVG icons, the element with vjs-icon-placeholder will be added separately.
17355      if (this.player_.options_.experimentalSvgIcons) {
17356        el.appendChild(menuItemEl);
17357      } else {
17358        el.replaceChild(menuItemEl, el.querySelector('.vjs-icon-placeholder'));
17359      }
17360      return el;
17361    }
17362
17363    /**
17364     * Ignore keys which are used by the menu, but pass any other ones up. See
17365     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17366     *
17367     * @param {KeyboardEvent} event
vendor: 24,562 bytes, lines 17368-18177
17368     *        The `keydown` event that caused this function to be called.
17369     *
17370     * @listens keydown
17371     */
17372    handleKeyDown(event) {
17373      if (!MenuKeys.some(key => keycode.isEventKey(event, key))) {
17374        // Pass keydown handling up for unused keys
17375        super.handleKeyDown(event);
17376      }
17377    }
17378
17379    /**
17380     * Any click on a `MenuItem` puts it into the selected state.
17381     * See {@link ClickableComponent#handleClick} for instances where this is called.
17382     *
17383     * @param {Event} event
17384     *        The `keydown`, `tap`, or `click` event that caused this function to be
17385     *        called.
17386     *
17387     * @listens tap
17388     * @listens click
17389     */
17390    handleClick(event) {
17391      this.selected(true);
17392    }
17393
17394    /**
17395     * Set the state for this menu item as selected or not.
17396     *
17397     * @param {boolean} selected
17398     *        if the menu item is selected or not
17399     */
17400    selected(selected) {
17401      if (this.selectable) {
17402        if (selected) {
17403          this.addClass('vjs-selected');
17404          this.el_.setAttribute('aria-checked', 'true');
17405          // aria-checked isn't fully supported by browsers/screen readers,
17406          // so indicate selected state to screen reader in the control text.
17407          this.controlText(', selected');
17408          this.isSelected_ = true;
17409        } else {
17410          this.removeClass('vjs-selected');
17411          this.el_.setAttribute('aria-checked', 'false');
17412          // Indicate un-selected state to screen reader
17413          this.controlText('');
17414          this.isSelected_ = false;
17415        }
17416      }
17417    }
17418  }
17419  Component$1.registerComponent('MenuItem', MenuItem);
17420
17421  /**
17422   * @file text-track-menu-item.js
17423   */
17424
17425  /**
17426   * The specific menu item type for selecting a language within a text track kind
17427   *
17428   * @extends MenuItem
17429   */
17430  class TextTrackMenuItem extends MenuItem {
17431    /**
17432     * Creates an instance of this class.
17433     *
17434     * @param { import('../../player').default } player
17435     *        The `Player` that this class should be attached to.
17436     *
17437     * @param {Object} [options]
17438     *        The key/value store of player options.
17439     */
17440    constructor(player, options) {
17441      const track = options.track;
17442      const tracks = player.textTracks();
17443
17444      // Modify options for parent MenuItem class's init.
17445      options.label = track.label || track.language || 'Unknown';
17446      options.selected = track.mode === 'showing';
17447      super(player, options);
17448      this.track = track;
17449      // Determine the relevant kind(s) of tracks for this component and filter
17450      // out empty kinds.
17451      this.kinds = (options.kinds || [options.kind || this.track.kind]).filter(Boolean);
17452      const changeHandler = (...args) => {
17453        this.handleTracksChange.apply(this, args);
17454      };
17455      const selectedLanguageChangeHandler = (...args) => {
17456        this.handleSelectedLanguageChange.apply(this, args);
17457      };
17458      player.on(['loadstart', 'texttrackchange'], changeHandler);
17459      tracks.addEventListener('change', changeHandler);
17460      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17461      this.on('dispose', function () {
17462        player.off(['loadstart', 'texttrackchange'], changeHandler);
17463        tracks.removeEventListener('change', changeHandler);
17464        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17465      });
17466
17467      // iOS7 doesn't dispatch change events to TextTrackLists when an
17468      // associated track's mode changes. Without something like
17469      // Object.observe() (also not present on iOS7), it's not
17470      // possible to detect changes to the mode attribute and polyfill
17471      // the change event. As a poor substitute, we manually dispatch
17472      // change events whenever the controls modify the mode.
17473      if (tracks.onchange === undefined) {
17474        let event;
17475        this.on(['tap', 'click'], function () {
17476          if (typeof window.Event !== 'object') {
17477            // Android 2.3 throws an Illegal Constructor error for window.Event
17478            try {
17479              event = new window.Event('change');
17480            } catch (err) {
17481              // continue regardless of error
17482            }
17483          }
17484          if (!event) {
17485            event = document.createEvent('Event');
17486            event.initEvent('change', true, true);
17487          }
17488          tracks.dispatchEvent(event);
17489        });
17490      }
17491
17492      // set the default state based on current tracks
17493      this.handleTracksChange();
17494    }
17495
17496    /**
17497     * This gets called when an `TextTrackMenuItem` is "clicked". See
17498     * {@link ClickableComponent} for more detailed information on what a click can be.
17499     *
17500     * @param {Event} event
17501     *        The `keydown`, `tap`, or `click` event that caused this function to be
17502     *        called.
17503     *
17504     * @listens tap
17505     * @listens click
17506     */
17507    handleClick(event) {
17508      const referenceTrack = this.track;
17509      const tracks = this.player_.textTracks();
17510      super.handleClick(event);
17511      if (!tracks) {
17512        return;
17513      }
17514      for (let i = 0; i < tracks.length; i++) {
17515        const track = tracks[i];
17516
17517        // If the track from the text tracks list is not of the right kind,
17518        // skip it. We do not want to affect tracks of incompatible kind(s).
17519        if (this.kinds.indexOf(track.kind) === -1) {
17520          continue;
17521        }
17522
17523        // If this text track is the component's track and it is not showing,
17524        // set it to showing.
17525        if (track === referenceTrack) {
17526          if (track.mode !== 'showing') {
17527            track.mode = 'showing';
17528          }
17529
17530          // If this text track is not the component's track and it is not
17531          // disabled, set it to disabled.
17532        } else if (track.mode !== 'disabled') {
17533          track.mode = 'disabled';
17534        }
17535      }
17536    }
17537
17538    /**
17539     * Handle text track list change
17540     *
17541     * @param {Event} event
17542     *        The `change` event that caused this function to be called.
17543     *
17544     * @listens TextTrackList#change
17545     */
17546    handleTracksChange(event) {
17547      const shouldBeSelected = this.track.mode === 'showing';
17548
17549      // Prevent redundant selected() calls because they may cause
17550      // screen readers to read the appended control text unnecessarily
17551      if (shouldBeSelected !== this.isSelected_) {
17552        this.selected(shouldBeSelected);
17553      }
17554    }
17555    handleSelectedLanguageChange(event) {
17556      if (this.track.mode === 'showing') {
17557        const selectedLanguage = this.player_.cache_.selectedLanguage;
17558
17559        // Don't replace the kind of track across the same language
17560        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
17561          return;
17562        }
17563        this.player_.cache_.selectedLanguage = {
17564          enabled: true,
17565          language: this.track.language,
17566          kind: this.track.kind
17567        };
17568      }
17569    }
17570    dispose() {
17571      // remove reference to track object on dispose
17572      this.track = null;
17573      super.dispose();
17574    }
17575  }
17576  Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
17577
17578  /**
17579   * @file off-text-track-menu-item.js
17580   */
17581
17582  /**
17583   * A special menu item for turning off a specific type of text track
17584   *
17585   * @extends TextTrackMenuItem
17586   */
17587  class OffTextTrackMenuItem extends TextTrackMenuItem {
17588    /**
17589     * Creates an instance of this class.
17590     *
17591     * @param { import('../../player').default } player
17592     *        The `Player` that this class should be attached to.
17593     *
17594     * @param {Object} [options]
17595     *        The key/value store of player options.
17596     */
17597    constructor(player, options) {
17598      // Create pseudo track info
17599      // Requires options['kind']
17600      options.track = {
17601        player,
17602        // it is no longer necessary to store `kind` or `kinds` on the track itself
17603        // since they are now stored in the `kinds` property of all instances of
17604        // TextTrackMenuItem, but this will remain for backwards compatibility
17605        kind: options.kind,
17606        kinds: options.kinds,
17607        default: false,
17608        mode: 'disabled'
17609      };
17610      if (!options.kinds) {
17611        options.kinds = [options.kind];
17612      }
17613      if (options.label) {
17614        options.track.label = options.label;
17615      } else {
17616        options.track.label = options.kinds.join(' and ') + ' off';
17617      }
17618
17619      // MenuItem is selectable
17620      options.selectable = true;
17621      // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17622      options.multiSelectable = false;
17623      super(player, options);
17624    }
17625
17626    /**
17627     * Handle text track change
17628     *
17629     * @param {Event} event
17630     *        The event that caused this function to run
17631     */
17632    handleTracksChange(event) {
17633      const tracks = this.player().textTracks();
17634      let shouldBeSelected = true;
17635      for (let i = 0, l = tracks.length; i < l; i++) {
17636        const track = tracks[i];
17637        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
17638          shouldBeSelected = false;
17639          break;
17640        }
17641      }
17642
17643      // Prevent redundant selected() calls because they may cause
17644      // screen readers to read the appended control text unnecessarily
17645      if (shouldBeSelected !== this.isSelected_) {
17646        this.selected(shouldBeSelected);
17647      }
17648    }
17649    handleSelectedLanguageChange(event) {
17650      const tracks = this.player().textTracks();
17651      let allHidden = true;
17652      for (let i = 0, l = tracks.length; i < l; i++) {
17653        const track = tracks[i];
17654        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
17655          allHidden = false;
17656          break;
17657        }
17658      }
17659      if (allHidden) {
17660        this.player_.cache_.selectedLanguage = {
17661          enabled: false
17662        };
17663      }
17664    }
17665
17666    /**
17667     * Update control text and label on languagechange
17668     */
17669    handleLanguagechange() {
17670      this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.label);
17671      super.handleLanguagechange();
17672    }
17673  }
17674  Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
17675
17676  /**
17677   * @file text-track-button.js
17678   */
17679
17680  /**
17681   * The base class for buttons that toggle specific text track types (e.g. subtitles)
17682   *
17683   * @extends MenuButton
17684   */
17685  class TextTrackButton extends TrackButton {
17686    /**
17687     * Creates an instance of this class.
17688     *
17689     * @param { import('../../player').default } player
17690     *        The `Player` that this class should be attached to.
17691     *
17692     * @param {Object} [options={}]
17693     *        The key/value store of player options.
17694     */
17695    constructor(player, options = {}) {
17696      options.tracks = player.textTracks();
17697      super(player, options);
17698    }
17699
17700    /**
17701     * Create a menu item for each text track
17702     *
17703     * @param {TextTrackMenuItem[]} [items=[]]
17704     *        Existing array of items to use during creation
17705     *
17706     * @return {TextTrackMenuItem[]}
17707     *         Array of menu items that were created
17708     */
17709    createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
17710      // Label is an override for the [track] off label
17711      // USed to localise captions/subtitles
17712      let label;
17713      if (this.label_) {
17714        label = `${this.label_} off`;
17715      }
17716      // Add an OFF menu item to turn all tracks off
17717      items.push(new OffTextTrackMenuItem(this.player_, {
17718        kinds: this.kinds_,
17719        kind: this.kind_,
17720        label
17721      }));
17722      this.hideThreshold_ += 1;
17723      const tracks = this.player_.textTracks();
17724      if (!Array.isArray(this.kinds_)) {
17725        this.kinds_ = [this.kind_];
17726      }
17727      for (let i = 0; i < tracks.length; i++) {
17728        const track = tracks[i];
17729
17730        // only add tracks that are of an appropriate kind and have a label
17731        if (this.kinds_.indexOf(track.kind) > -1) {
17732          const item = new TrackMenuItem(this.player_, {
17733            track,
17734            kinds: this.kinds_,
17735            kind: this.kind_,
17736            // MenuItem is selectable
17737            selectable: true,
17738            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17739            multiSelectable: false
17740          });
17741          item.addClass(`vjs-${track.kind}-menu-item`);
17742          items.push(item);
17743        }
17744      }
17745      return items;
17746    }
17747  }
17748  Component$1.registerComponent('TextTrackButton', TextTrackButton);
17749
17750  /**
17751   * @file chapters-track-menu-item.js
17752   */
17753
17754  /**
17755   * The chapter track menu item
17756   *
17757   * @extends MenuItem
17758   */
17759  class ChaptersTrackMenuItem extends MenuItem {
17760    /**
17761     * Creates an instance of this class.
17762     *
17763     * @param { import('../../player').default } player
17764     *        The `Player` that this class should be attached to.
17765     *
17766     * @param {Object} [options]
17767     *        The key/value store of player options.
17768     */
17769    constructor(player, options) {
17770      const track = options.track;
17771      const cue = options.cue;
17772      const currentTime = player.currentTime();
17773
17774      // Modify options for parent MenuItem class's init.
17775      options.selectable = true;
17776      options.multiSelectable = false;
17777      options.label = cue.text;
17778      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17779      super(player, options);
17780      this.track = track;
17781      this.cue = cue;
17782    }
17783
17784    /**
17785     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17786     * {@link ClickableComponent} for more detailed information on what a click can be.
17787     *
17788     * @param {Event} [event]
17789     *        The `keydown`, `tap`, or `click` event that caused this function to be
17790     *        called.
17791     *
17792     * @listens tap
17793     * @listens click
17794     */
17795    handleClick(event) {
17796      super.handleClick();
17797      this.player_.currentTime(this.cue.startTime);
17798    }
17799  }
17800  Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17801
17802  /**
17803   * @file chapters-button.js
17804   */
17805
17806  /**
17807   * The button component for toggling and selecting chapters
17808   * Chapters act much differently than other text tracks
17809   * Cues are navigation vs. other tracks of alternative languages
17810   *
17811   * @extends TextTrackButton
17812   */
17813  class ChaptersButton extends TextTrackButton {
17814    /**
17815     * Creates an instance of this class.
17816     *
17817     * @param { import('../../player').default } player
17818     *        The `Player` that this class should be attached to.
17819     *
17820     * @param {Object} [options]
17821     *        The key/value store of player options.
17822     *
17823     * @param {Function} [ready]
17824     *        The function to call when this function is ready.
17825     */
17826    constructor(player, options, ready) {
17827      super(player, options, ready);
17828      this.setIcon('chapters');
17829      this.selectCurrentItem_ = () => {
17830        this.items.forEach(item => {
17831          item.selected(this.track_.activeCues[0] === item.cue);
17832        });
17833      };
17834    }
17835
17836    /**
17837     * Builds the default DOM `className`.
17838     *
17839     * @return {string}
17840     *         The DOM `className` for this object.
17841     */
17842    buildCSSClass() {
17843      return `vjs-chapters-button ${super.buildCSSClass()}`;
17844    }
17845    buildWrapperCSSClass() {
17846      return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
17847    }
17848
17849    /**
17850     * Update the menu based on the current state of its items.
17851     *
17852     * @param {Event} [event]
17853     *        An event that triggered this function to run.
17854     *
17855     * @listens TextTrackList#addtrack
17856     * @listens TextTrackList#removetrack
17857     * @listens TextTrackList#change
17858     */
17859    update(event) {
17860      if (event && event.track && event.track.kind !== 'chapters') {
17861        return;
17862      }
17863      const track = this.findChaptersTrack();
17864      if (track !== this.track_) {
17865        this.setTrack(track);
17866        super.update();
17867      } else if (!this.items || track && track.cues && track.cues.length !== this.items.length) {
17868        // Update the menu initially or if the number of cues has changed since set
17869        super.update();
17870      }
17871    }
17872
17873    /**
17874     * Set the currently selected track for the chapters button.
17875     *
17876     * @param {TextTrack} track
17877     *        The new track to select. Nothing will change if this is the currently selected
17878     *        track.
17879     */
17880    setTrack(track) {
17881      if (this.track_ === track) {
17882        return;
17883      }
17884      if (!this.updateHandler_) {
17885        this.updateHandler_ = this.update.bind(this);
17886      }
17887
17888      // here this.track_ refers to the old track instance
17889      if (this.track_) {
17890        const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17891        if (remoteTextTrackEl) {
17892          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17893        }
17894        this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
17895        this.track_ = null;
17896      }
17897      this.track_ = track;
17898
17899      // here this.track_ refers to the new track instance
17900      if (this.track_) {
17901        this.track_.mode = 'hidden';
17902        const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17903        if (remoteTextTrackEl) {
17904          remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17905        }
17906        this.track_.addEventListener('cuechange', this.selectCurrentItem_);
17907      }
17908    }
17909
17910    /**
17911     * Find the track object that is currently in use by this ChaptersButton
17912     *
17913     * @return {TextTrack|undefined}
17914     *         The current track or undefined if none was found.
17915     */
17916    findChaptersTrack() {
17917      const tracks = this.player_.textTracks() || [];
17918      for (let i = tracks.length - 1; i >= 0; i--) {
17919        // We will always choose the last track as our chaptersTrack
17920        const track = tracks[i];
17921        if (track.kind === this.kind_) {
17922          return track;
17923        }
17924      }
17925    }
17926
17927    /**
17928     * Get the caption for the ChaptersButton based on the track label. This will also
17929     * use the current tracks localized kind as a fallback if a label does not exist.
17930     *
17931     * @return {string}
17932     *         The tracks current label or the localized track kind.
17933     */
17934    getMenuCaption() {
17935      if (this.track_ && this.track_.label) {
17936        return this.track_.label;
17937      }
17938      return this.localize(toTitleCase$1(this.kind_));
17939    }
17940
17941    /**
17942     * Create menu from chapter track
17943     *
17944     * @return { import('../../menu/menu').default }
17945     *         New menu for the chapter buttons
17946     */
17947    createMenu() {
17948      this.options_.title = this.getMenuCaption();
17949      return super.createMenu();
17950    }
17951
17952    /**
17953     * Create a menu item for each text track
17954     *
17955     * @return  { import('./text-track-menu-item').default[] }
17956     *         Array of menu items
17957     */
17958    createItems() {
17959      const items = [];
17960      if (!this.track_) {
17961        return items;
17962      }
17963      const cues = this.track_.cues;
17964      if (!cues) {
17965        return items;
17966      }
17967      for (let i = 0, l = cues.length; i < l; i++) {
17968        const cue = cues[i];
17969        const mi = new ChaptersTrackMenuItem(this.player_, {
17970          track: this.track_,
17971          cue
17972        });
17973        items.push(mi);
17974      }
17975      return items;
17976    }
17977  }
17978
17979  /**
17980   * `kind` of TextTrack to look for to associate it with this menu.
17981   *
17982   * @type {string}
17983   * @private
17984   */
17985  ChaptersButton.prototype.kind_ = 'chapters';
17986
17987  /**
17988   * The text that should display over the `ChaptersButton`s controls. Added for localization.
17989   *
17990   * @type {string}
17991   * @protected
17992   */
17993  ChaptersButton.prototype.controlText_ = 'Chapters';
17994  Component$1.registerComponent('ChaptersButton', ChaptersButton);
17995
17996  /**
17997   * @file descriptions-button.js
17998   */
17999
18000  /**
18001   * The button component for toggling and selecting descriptions
18002   *
18003   * @extends TextTrackButton
18004   */
18005  class DescriptionsButton extends TextTrackButton {
18006    /**
18007     * Creates an instance of this class.
18008     *
18009     * @param { import('../../player').default } player
18010     *        The `Player` that this class should be attached to.
18011     *
18012     * @param {Object} [options]
18013     *        The key/value store of player options.
18014     *
18015     * @param {Function} [ready]
18016     *        The function to call when this component is ready.
18017     */
18018    constructor(player, options, ready) {
18019      super(player, options, ready);
18020      this.setIcon('audio-description');
18021      const tracks = player.textTracks();
18022      const changeHandler = bind_(this, this.handleTracksChange);
18023      tracks.addEventListener('change', changeHandler);
18024      this.on('dispose', function () {
18025        tracks.removeEventListener('change', changeHandler);
18026      });
18027    }
18028
18029    /**
18030     * Handle text track change
18031     *
18032     * @param {Event} event
18033     *        The event that caused this function to run
18034     *
18035     * @listens TextTrackList#change
18036     */
18037    handleTracksChange(event) {
18038      const tracks = this.player().textTracks();
18039      let disabled = false;
18040
18041      // Check whether a track of a different kind is showing
18042      for (let i = 0, l = tracks.length; i < l; i++) {
18043        const track = tracks[i];
18044        if (track.kind !== this.kind_ && track.mode === 'showing') {
18045          disabled = true;
18046          break;
18047        }
18048      }
18049
18050      // If another track is showing, disable this menu button
18051      if (disabled) {
18052        this.disable();
18053      } else {
18054        this.enable();
18055      }
18056    }
18057
18058    /**
18059     * Builds the default DOM `className`.
18060     *
18061     * @return {string}
18062     *         The DOM `className` for this object.
18063     */
18064    buildCSSClass() {
18065      return `vjs-descriptions-button ${super.buildCSSClass()}`;
18066    }
18067    buildWrapperCSSClass() {
18068      return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
18069    }
18070  }
18071
18072  /**
18073   * `kind` of TextTrack to look for to associate it with this menu.
18074   *
18075   * @type {string}
18076   * @private
18077   */
18078  DescriptionsButton.prototype.kind_ = 'descriptions';
18079
18080  /**
18081   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
18082   *
18083   * @type {string}
18084   * @protected
18085   */
18086  DescriptionsButton.prototype.controlText_ = 'Descriptions';
18087  Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
18088
18089  /**
18090   * @file subtitles-button.js
18091   */
18092
18093  /**
18094   * The button component for toggling and selecting subtitles
18095   *
18096   * @extends TextTrackButton
18097   */
18098  class SubtitlesButton extends TextTrackButton {
18099    /**
18100     * Creates an instance of this class.
18101     *
18102     * @param { import('../../player').default } player
18103     *        The `Player` that this class should be attached to.
18104     *
18105     * @param {Object} [options]
18106     *        The key/value store of player options.
18107     *
18108     * @param {Function} [ready]
18109     *        The function to call when this component is ready.
18110     */
18111    constructor(player, options, ready) {
18112      super(player, options, ready);
18113      this.setIcon('subtitles');
18114    }
18115
18116    /**
18117     * Builds the default DOM `className`.
18118     *
18119     * @return {string}
18120     *         The DOM `className` for this object.
18121     */
18122    buildCSSClass() {
18123      return `vjs-subtitles-button ${super.buildCSSClass()}`;
18124    }
18125    buildWrapperCSSClass() {
18126      return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
18127    }
18128  }
18129
18130  /**
18131   * `kind` of TextTrack to look for to associate it with this menu.
18132   *
18133   * @type {string}
18134   * @private
18135   */
18136  SubtitlesButton.prototype.kind_ = 'subtitles';
18137
18138  /**
18139   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
18140   *
18141   * @type {string}
18142   * @protected
18143   */
18144  SubtitlesButton.prototype.controlText_ = 'Subtitles';
18145  Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
18146
18147  /**
18148   * @file caption-settings-menu-item.js
18149   */
18150
18151  /**
18152   * The menu item for caption track settings menu
18153   *
18154   * @extends TextTrackMenuItem
18155   */
18156  class CaptionSettingsMenuItem extends TextTrackMenuItem {
18157    /**
18158     * Creates an instance of this class.
18159     *
18160     * @param { import('../../player').default } player
18161     *        The `Player` that this class should be attached to.
18162     *
18163     * @param {Object} [options]
18164     *        The key/value store of player options.
18165     */
18166    constructor(player, options) {
18167      options.track = {
18168        player,
18169        kind: options.kind,
18170        label: options.kind + ' settings',
18171        selectable: false,
18172        default: false,
18173        mode: 'disabled'
18174      };
18175
18176      // CaptionSettingsMenuItem has no concept of 'selected'
18177      options.selectable = false;
vendor: 8,431 bytes, lines 18178-18464
18178      options.name = 'CaptionSettingsMenuItem';
18179      super(player, options);
18180      this.addClass('vjs-texttrack-settings');
18181      this.controlText(', opens ' + options.kind + ' settings dialog');
18182    }
18183
18184    /**
18185     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
18186     * {@link ClickableComponent} for more detailed information on what a click can be.
18187     *
18188     * @param {Event} [event]
18189     *        The `keydown`, `tap`, or `click` event that caused this function to be
18190     *        called.
18191     *
18192     * @listens tap
18193     * @listens click
18194     */
18195    handleClick(event) {
18196      this.player().getChild('textTrackSettings').open();
18197    }
18198
18199    /**
18200     * Update control text and label on languagechange
18201     */
18202    handleLanguagechange() {
18203      this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.kind + ' settings');
18204      super.handleLanguagechange();
18205    }
18206  }
18207  Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
18208
18209  /**
18210   * @file captions-button.js
18211   */
18212
18213  /**
18214   * The button component for toggling and selecting captions
18215   *
18216   * @extends TextTrackButton
18217   */
18218  class CaptionsButton extends TextTrackButton {
18219    /**
18220     * Creates an instance of this class.
18221     *
18222     * @param { import('../../player').default } player
18223     *        The `Player` that this class should be attached to.
18224     *
18225     * @param {Object} [options]
18226     *        The key/value store of player options.
18227     *
18228     * @param {Function} [ready]
18229     *        The function to call when this component is ready.
18230     */
18231    constructor(player, options, ready) {
18232      super(player, options, ready);
18233      this.setIcon('captions');
18234    }
18235
18236    /**
18237     * Builds the default DOM `className`.
18238     *
18239     * @return {string}
18240     *         The DOM `className` for this object.
18241     */
18242    buildCSSClass() {
18243      return `vjs-captions-button ${super.buildCSSClass()}`;
18244    }
18245    buildWrapperCSSClass() {
18246      return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
18247    }
18248
18249    /**
18250     * Create caption menu items
18251     *
18252     * @return {CaptionSettingsMenuItem[]}
18253     *         The array of current menu items.
18254     */
18255    createItems() {
18256      const items = [];
18257      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18258        items.push(new CaptionSettingsMenuItem(this.player_, {
18259          kind: this.kind_
18260        }));
18261        this.hideThreshold_ += 1;
18262      }
18263      return super.createItems(items);
18264    }
18265  }
18266
18267  /**
18268   * `kind` of TextTrack to look for to associate it with this menu.
18269   *
18270   * @type {string}
18271   * @private
18272   */
18273  CaptionsButton.prototype.kind_ = 'captions';
18274
18275  /**
18276   * The text that should display over the `CaptionsButton`s controls. Added for localization.
18277   *
18278   * @type {string}
18279   * @protected
18280   */
18281  CaptionsButton.prototype.controlText_ = 'Captions';
18282  Component$1.registerComponent('CaptionsButton', CaptionsButton);
18283
18284  /**
18285   * @file subs-caps-menu-item.js
18286   */
18287
18288  /**
18289   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
18290   * in the SubsCapsMenu.
18291   *
18292   * @extends TextTrackMenuItem
18293   */
18294  class SubsCapsMenuItem extends TextTrackMenuItem {
18295    createEl(type, props, attrs) {
18296      const el = super.createEl(type, props, attrs);
18297      const parentSpan = el.querySelector('.vjs-menu-item-text');
18298      if (this.options_.track.kind === 'captions') {
18299        if (this.player_.options_.experimentalSvgIcons) {
18300          this.setIcon('captions', el);
18301        } else {
18302          parentSpan.appendChild(createEl('span', {
18303            className: 'vjs-icon-placeholder'
18304          }, {
18305            'aria-hidden': true
18306          }));
18307        }
18308        parentSpan.appendChild(createEl('span', {
18309          className: 'vjs-control-text',
18310          // space added as the text will visually flow with the
18311          // label
18312          textContent: ` ${this.localize('Captions')}`
18313        }));
18314      }
18315      return el;
18316    }
18317  }
18318  Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
18319
18320  /**
18321   * @file sub-caps-button.js
18322   */
18323
18324  /**
18325   * The button component for toggling and selecting captions and/or subtitles
18326   *
18327   * @extends TextTrackButton
18328   */
18329  class SubsCapsButton extends TextTrackButton {
18330    /**
18331     * Creates an instance of this class.
18332     *
18333     * @param { import('../../player').default } player
18334     *        The `Player` that this class should be attached to.
18335     *
18336     * @param {Object} [options]
18337     *        The key/value store of player options.
18338     *
18339     * @param {Function} [ready]
18340     *        The function to call when this component is ready.
18341     */
18342    constructor(player, options = {}) {
18343      super(player, options);
18344
18345      // Although North America uses "captions" in most cases for
18346      // "captions and subtitles" other locales use "subtitles"
18347      this.label_ = 'subtitles';
18348      this.setIcon('subtitles');
18349      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
18350        this.label_ = 'captions';
18351        this.setIcon('captions');
18352      }
18353      this.menuButton_.controlText(toTitleCase$1(this.label_));
18354    }
18355
18356    /**
18357     * Builds the default DOM `className`.
18358     *
18359     * @return {string}
18360     *         The DOM `className` for this object.
18361     */
18362    buildCSSClass() {
18363      return `vjs-subs-caps-button ${super.buildCSSClass()}`;
18364    }
18365    buildWrapperCSSClass() {
18366      return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
18367    }
18368
18369    /**
18370     * Create caption/subtitles menu items
18371     *
18372     * @return {CaptionSettingsMenuItem[]}
18373     *         The array of current menu items.
18374     */
18375    createItems() {
18376      let items = [];
18377      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18378        items.push(new CaptionSettingsMenuItem(this.player_, {
18379          kind: this.label_
18380        }));
18381        this.hideThreshold_ += 1;
18382      }
18383      items = super.createItems(items, SubsCapsMenuItem);
18384      return items;
18385    }
18386  }
18387
18388  /**
18389   * `kind`s of TextTrack to look for to associate it with this menu.
18390   *
18391   * @type {array}
18392   * @private
18393   */
18394  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
18395
18396  /**
18397   * The text that should display over the `SubsCapsButton`s controls.
18398   *
18399   *
18400   * @type {string}
18401   * @protected
18402   */
18403  SubsCapsButton.prototype.controlText_ = 'Subtitles';
18404  Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
18405
18406  /**
18407   * @file audio-track-menu-item.js
18408   */
18409
18410  /**
18411   * An {@link AudioTrack} {@link MenuItem}
18412   *
18413   * @extends MenuItem
18414   */
18415  class AudioTrackMenuItem extends MenuItem {
18416    /**
18417     * Creates an instance of this class.
18418     *
18419     * @param { import('../../player').default } player
18420     *        The `Player` that this class should be attached to.
18421     *
18422     * @param {Object} [options]
18423     *        The key/value store of player options.
18424     */
18425    constructor(player, options) {
18426      const track = options.track;
18427      const tracks = player.audioTracks();
18428
18429      // Modify options for parent MenuItem class's init.
18430      options.label = track.label || track.language || 'Unknown';
18431      options.selected = track.enabled;
18432      super(player, options);
18433      this.track = track;
18434      this.addClass(`vjs-${track.kind}-menu-item`);
18435      const changeHandler = (...args) => {
18436        this.handleTracksChange.apply(this, args);
18437      };
18438      tracks.addEventListener('change', changeHandler);
18439      this.on('dispose', () => {
18440        tracks.removeEventListener('change', changeHandler);
18441      });
18442    }
18443    createEl(type, props, attrs) {
18444      const el = super.createEl(type, props, attrs);
18445      const parentSpan = el.querySelector('.vjs-menu-item-text');
18446      if (['main-desc', 'description'].indexOf(this.options_.track.kind) >= 0) {
18447        parentSpan.appendChild(createEl('span', {
18448          className: 'vjs-icon-placeholder'
18449        }, {
18450          'aria-hidden': true
18451        }));
18452        parentSpan.appendChild(createEl('span', {
18453          className: 'vjs-control-text',
18454          textContent: ' ' + this.localize('Descriptions')
18455        }));
18456      }
18457      return el;
18458    }
18459
18460    /**
18461     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
18462     * for more detailed information on what a click can be.
18463     *
18464     * @param {Event} [event]
vendor: 19,608 bytes, lines 18465-19155
18465     *        The `keydown`, `tap`, or `click` event that caused this function to be
18466     *        called.
18467     *
18468     * @listens tap
18469     * @listens click
18470     */
18471    handleClick(event) {
18472      super.handleClick(event);
18473
18474      // the audio track list will automatically toggle other tracks
18475      // off for us.
18476      this.track.enabled = true;
18477
18478      // when native audio tracks are used, we want to make sure that other tracks are turned off
18479      if (this.player_.tech_.featuresNativeAudioTracks) {
18480        const tracks = this.player_.audioTracks();
18481        for (let i = 0; i < tracks.length; i++) {
18482          const track = tracks[i];
18483
18484          // skip the current track since we enabled it above
18485          if (track === this.track) {
18486            continue;
18487          }
18488          track.enabled = track === this.track;
18489        }
18490      }
18491    }
18492
18493    /**
18494     * Handle any {@link AudioTrack} change.
18495     *
18496     * @param {Event} [event]
18497     *        The {@link AudioTrackList#change} event that caused this to run.
18498     *
18499     * @listens AudioTrackList#change
18500     */
18501    handleTracksChange(event) {
18502      this.selected(this.track.enabled);
18503    }
18504  }
18505  Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
18506
18507  /**
18508   * @file audio-track-button.js
18509   */
18510
18511  /**
18512   * The base class for buttons that toggle specific {@link AudioTrack} types.
18513   *
18514   * @extends TrackButton
18515   */
18516  class AudioTrackButton extends TrackButton {
18517    /**
18518     * Creates an instance of this class.
18519     *
18520     * @param {Player} player
18521     *        The `Player` that this class should be attached to.
18522     *
18523     * @param {Object} [options={}]
18524     *        The key/value store of player options.
18525     */
18526    constructor(player, options = {}) {
18527      options.tracks = player.audioTracks();
18528      super(player, options);
18529      this.setIcon('audio');
18530    }
18531
18532    /**
18533     * Builds the default DOM `className`.
18534     *
18535     * @return {string}
18536     *         The DOM `className` for this object.
18537     */
18538    buildCSSClass() {
18539      return `vjs-audio-button ${super.buildCSSClass()}`;
18540    }
18541    buildWrapperCSSClass() {
18542      return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
18543    }
18544
18545    /**
18546     * Create a menu item for each audio track
18547     *
18548     * @param {AudioTrackMenuItem[]} [items=[]]
18549     *        An array of existing menu items to use.
18550     *
18551     * @return {AudioTrackMenuItem[]}
18552     *         An array of menu items
18553     */
18554    createItems(items = []) {
18555      // if there's only one audio track, there no point in showing it
18556      this.hideThreshold_ = 1;
18557      const tracks = this.player_.audioTracks();
18558      for (let i = 0; i < tracks.length; i++) {
18559        const track = tracks[i];
18560        items.push(new AudioTrackMenuItem(this.player_, {
18561          track,
18562          // MenuItem is selectable
18563          selectable: true,
18564          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18565          multiSelectable: false
18566        }));
18567      }
18568      return items;
18569    }
18570  }
18571
18572  /**
18573   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
18574   *
18575   * @type {string}
18576   * @protected
18577   */
18578  AudioTrackButton.prototype.controlText_ = 'Audio Track';
18579  Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
18580
18581  /**
18582   * @file playback-rate-menu-item.js
18583   */
18584
18585  /**
18586   * The specific menu item type for selecting a playback rate.
18587   *
18588   * @extends MenuItem
18589   */
18590  class PlaybackRateMenuItem extends MenuItem {
18591    /**
18592     * Creates an instance of this class.
18593     *
18594     * @param { import('../../player').default } player
18595     *        The `Player` that this class should be attached to.
18596     *
18597     * @param {Object} [options]
18598     *        The key/value store of player options.
18599     */
18600    constructor(player, options) {
18601      const label = options.rate;
18602      const rate = parseFloat(label, 10);
18603
18604      // Modify options for parent MenuItem class's init.
18605      options.label = label;
18606      options.selected = rate === player.playbackRate();
18607      options.selectable = true;
18608      options.multiSelectable = false;
18609      super(player, options);
18610      this.label = label;
18611      this.rate = rate;
18612      this.on(player, 'ratechange', e => this.update(e));
18613    }
18614
18615    /**
18616     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
18617     * {@link ClickableComponent} for more detailed information on what a click can be.
18618     *
18619     * @param {Event} [event]
18620     *        The `keydown`, `tap`, or `click` event that caused this function to be
18621     *        called.
18622     *
18623     * @listens tap
18624     * @listens click
18625     */
18626    handleClick(event) {
18627      super.handleClick();
18628      this.player().playbackRate(this.rate);
18629    }
18630
18631    /**
18632     * Update the PlaybackRateMenuItem when the playbackrate changes.
18633     *
18634     * @param {Event} [event]
18635     *        The `ratechange` event that caused this function to run.
18636     *
18637     * @listens Player#ratechange
18638     */
18639    update(event) {
18640      this.selected(this.player().playbackRate() === this.rate);
18641    }
18642  }
18643
18644  /**
18645   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18646   *
18647   * @type {string}
18648   * @private
18649   */
18650  PlaybackRateMenuItem.prototype.contentElType = 'button';
18651  Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18652
18653  /**
18654   * @file playback-rate-menu-button.js
18655   */
18656
18657  /**
18658   * The component for controlling the playback rate.
18659   *
18660   * @extends MenuButton
18661   */
18662  class PlaybackRateMenuButton extends MenuButton {
18663    /**
18664     * Creates an instance of this class.
18665     *
18666     * @param { import('../../player').default } player
18667     *        The `Player` that this class should be attached to.
18668     *
18669     * @param {Object} [options]
18670     *        The key/value store of player options.
18671     */
18672    constructor(player, options) {
18673      super(player, options);
18674      this.menuButton_.el_.setAttribute('aria-describedby', this.labelElId_);
18675      this.updateVisibility();
18676      this.updateLabel();
18677      this.on(player, 'loadstart', e => this.updateVisibility(e));
18678      this.on(player, 'ratechange', e => this.updateLabel(e));
18679      this.on(player, 'playbackrateschange', e => this.handlePlaybackRateschange(e));
18680    }
18681
18682    /**
18683     * Create the `Component`'s DOM element
18684     *
18685     * @return {Element}
18686     *         The element that was created.
18687     */
18688    createEl() {
18689      const el = super.createEl();
18690      this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
18691      this.labelEl_ = createEl('div', {
18692        className: 'vjs-playback-rate-value',
18693        id: this.labelElId_,
18694        textContent: '1x'
18695      });
18696      el.appendChild(this.labelEl_);
18697      return el;
18698    }
18699    dispose() {
18700      this.labelEl_ = null;
18701      super.dispose();
18702    }
18703
18704    /**
18705     * Builds the default DOM `className`.
18706     *
18707     * @return {string}
18708     *         The DOM `className` for this object.
18709     */
18710    buildCSSClass() {
18711      return `vjs-playback-rate ${super.buildCSSClass()}`;
18712    }
18713    buildWrapperCSSClass() {
18714      return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
18715    }
18716
18717    /**
18718     * Create the list of menu items. Specific to each subclass.
18719     *
18720     */
18721    createItems() {
18722      const rates = this.playbackRates();
18723      const items = [];
18724      for (let i = rates.length - 1; i >= 0; i--) {
18725        items.push(new PlaybackRateMenuItem(this.player(), {
18726          rate: rates[i] + 'x'
18727        }));
18728      }
18729      return items;
18730    }
18731
18732    /**
18733     * On playbackrateschange, update the menu to account for the new items.
18734     *
18735     * @listens Player#playbackrateschange
18736     */
18737    handlePlaybackRateschange(event) {
18738      this.update();
18739    }
18740
18741    /**
18742     * Get possible playback rates
18743     *
18744     * @return {Array}
18745     *         All possible playback rates
18746     */
18747    playbackRates() {
18748      const player = this.player();
18749      return player.playbackRates && player.playbackRates() || [];
18750    }
18751
18752    /**
18753     * Get whether playback rates is supported by the tech
18754     * and an array of playback rates exists
18755     *
18756     * @return {boolean}
18757     *         Whether changing playback rate is supported
18758     */
18759    playbackRateSupported() {
18760      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
18761    }
18762
18763    /**
18764     * Hide playback rate controls when they're no playback rate options to select
18765     *
18766     * @param {Event} [event]
18767     *        The event that caused this function to run.
18768     *
18769     * @listens Player#loadstart
18770     */
18771    updateVisibility(event) {
18772      if (this.playbackRateSupported()) {
18773        this.removeClass('vjs-hidden');
18774      } else {
18775        this.addClass('vjs-hidden');
18776      }
18777    }
18778
18779    /**
18780     * Update button label when rate changed
18781     *
18782     * @param {Event} [event]
18783     *        The event that caused this function to run.
18784     *
18785     * @listens Player#ratechange
18786     */
18787    updateLabel(event) {
18788      if (this.playbackRateSupported()) {
18789        this.labelEl_.textContent = this.player().playbackRate() + 'x';
18790      }
18791    }
18792  }
18793
18794  /**
18795   * The text that should display over the `PlaybackRateMenuButton`s controls.
18796   *
18797   * Added for localization.
18798   *
18799   * @type {string}
18800   * @protected
18801   */
18802  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18803  Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18804
18805  /**
18806   * @file spacer.js
18807   */
18808
18809  /**
18810   * Just an empty spacer element that can be used as an append point for plugins, etc.
18811   * Also can be used to create space between elements when necessary.
18812   *
18813   * @extends Component
18814   */
18815  class Spacer extends Component$1 {
18816    /**
18817    * Builds the default DOM `className`.
18818    *
18819    * @return {string}
18820    *         The DOM `className` for this object.
18821    */
18822    buildCSSClass() {
18823      return `vjs-spacer ${super.buildCSSClass()}`;
18824    }
18825
18826    /**
18827     * Create the `Component`'s DOM element
18828     *
18829     * @return {Element}
18830     *         The element that was created.
18831     */
18832    createEl(tag = 'div', props = {}, attributes = {}) {
18833      if (!props.className) {
18834        props.className = this.buildCSSClass();
18835      }
18836      return super.createEl(tag, props, attributes);
18837    }
18838  }
18839  Component$1.registerComponent('Spacer', Spacer);
18840
18841  /**
18842   * @file custom-control-spacer.js
18843   */
18844
18845  /**
18846   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18847   *
18848   * @extends Spacer
18849   */
18850  class CustomControlSpacer extends Spacer {
18851    /**
18852     * Builds the default DOM `className`.
18853     *
18854     * @return {string}
18855     *         The DOM `className` for this object.
18856     */
18857    buildCSSClass() {
18858      return `vjs-custom-control-spacer ${super.buildCSSClass()}`;
18859    }
18860
18861    /**
18862     * Create the `Component`'s DOM element
18863     *
18864     * @return {Element}
18865     *         The element that was created.
18866     */
18867    createEl() {
18868      return super.createEl('div', {
18869        className: this.buildCSSClass(),
18870        // No-flex/table-cell mode requires there be some content
18871        // in the cell to fill the remaining space of the table.
18872        textContent: '\u00a0'
18873      });
18874    }
18875  }
18876  Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
18877
18878  /**
18879   * @file control-bar.js
18880   */
18881
18882  /**
18883   * Container of main controls.
18884   *
18885   * @extends Component
18886   */
18887  class ControlBar extends Component$1 {
18888    /**
18889     * Create the `Component`'s DOM element
18890     *
18891     * @return {Element}
18892     *         The element that was created.
18893     */
18894    createEl() {
18895      return super.createEl('div', {
18896        className: 'vjs-control-bar',
18897        dir: 'ltr'
18898      });
18899    }
18900  }
18901
18902  /**
18903   * Default options for `ControlBar`
18904   *
18905   * @type {Object}
18906   * @private
18907   */
18908  ControlBar.prototype.options_ = {
18909    children: ['playToggle', 'skipBackward', 'skipForward', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'pictureInPictureToggle', 'fullscreenToggle']
18910  };
18911  Component$1.registerComponent('ControlBar', ControlBar);
18912
18913  /**
18914   * @file error-display.js
18915   */
18916
18917  /**
18918   * A display that indicates an error has occurred. This means that the video
18919   * is unplayable.
18920   *
18921   * @extends ModalDialog
18922   */
18923  class ErrorDisplay extends ModalDialog {
18924    /**
18925     * Creates an instance of this class.
18926     *
18927     * @param  { import('./player').default } player
18928     *         The `Player` that this class should be attached to.
18929     *
18930     * @param  {Object} [options]
18931     *         The key/value store of player options.
18932     */
18933    constructor(player, options) {
18934      super(player, options);
18935      this.on(player, 'error', e => {
18936        this.close();
18937        this.open(e);
18938      });
18939    }
18940
18941    /**
18942     * Builds the default DOM `className`.
18943     *
18944     * @return {string}
18945     *         The DOM `className` for this object.
18946     *
18947     * @deprecated Since version 5.
18948     */
18949    buildCSSClass() {
18950      return `vjs-error-display ${super.buildCSSClass()}`;
18951    }
18952
18953    /**
18954     * Gets the localized error message based on the `Player`s error.
18955     *
18956     * @return {string}
18957     *         The `Player`s error message localized or an empty string.
18958     */
18959    content() {
18960      const error = this.player().error();
18961      return error ? this.localize(error.message) : '';
18962    }
18963  }
18964
18965  /**
18966   * The default options for an `ErrorDisplay`.
18967   *
18968   * @private
18969   */
18970  ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, {
18971    pauseOnOpen: false,
18972    fillAlways: true,
18973    temporary: false,
18974    uncloseable: true
18975  });
18976  Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
18977
18978  /**
18979   * @file text-track-settings.js
18980   */
18981  const LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
18982  const COLOR_BLACK = ['#000', 'Black'];
18983  const COLOR_BLUE = ['#00F', 'Blue'];
18984  const COLOR_CYAN = ['#0FF', 'Cyan'];
18985  const COLOR_GREEN = ['#0F0', 'Green'];
18986  const COLOR_MAGENTA = ['#F0F', 'Magenta'];
18987  const COLOR_RED = ['#F00', 'Red'];
18988  const COLOR_WHITE = ['#FFF', 'White'];
18989  const COLOR_YELLOW = ['#FF0', 'Yellow'];
18990  const OPACITY_OPAQUE = ['1', 'Opaque'];
18991  const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18992  const OPACITY_TRANS = ['0', 'Transparent'];
18993
18994  // Configuration for the various <select> elements in the DOM of this component.
18995  //
18996  // Possible keys include:
18997  //
18998  // `default`:
18999  //   The default option index. Only needs to be provided if not zero.
19000  // `parser`:
19001  //   A function which is used to parse the value from the selected option in
19002  //   a customized way.
19003  // `selector`:
19004  //   The selector used to find the associated <select> element.
19005  const selectConfigs = {
19006    backgroundColor: {
19007      selector: '.vjs-bg-color > select',
19008      id: 'captions-background-color-%s',
19009      label: 'Color',
19010      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19011    },
19012    backgroundOpacity: {
19013      selector: '.vjs-bg-opacity > select',
19014      id: 'captions-background-opacity-%s',
19015      label: 'Opacity',
19016      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
19017    },
19018    color: {
19019      selector: '.vjs-text-color > select',
19020      id: 'captions-foreground-color-%s',
19021      label: 'Color',
19022      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19023    },
19024    edgeStyle: {
19025      selector: '.vjs-edge-style > select',
19026      id: '%s',
19027      label: 'Text Edge Style',
19028      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Drop shadow']]
19029    },
19030    fontFamily: {
19031      selector: '.vjs-font-family > select',
19032      id: 'captions-font-family-%s',
19033      label: 'Font Family',
19034      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
19035    },
19036    fontPercent: {
19037      selector: '.vjs-font-percent > select',
19038      id: 'captions-font-size-%s',
19039      label: 'Font Size',
19040      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%']],
19041      default: 2,
19042      parser: v => v === '1.00' ? null : Number(v)
19043    },
19044    textOpacity: {
19045      selector: '.vjs-text-opacity > select',
19046      id: 'captions-foreground-opacity-%s',
19047      label: 'Opacity',
19048      options: [OPACITY_OPAQUE, OPACITY_SEMI]
19049    },
19050    // Options for this object are defined below.
19051    windowColor: {
19052      selector: '.vjs-window-color > select',
19053      id: 'captions-window-color-%s',
19054      label: 'Color'
19055    },
19056    // Options for this object are defined below.
19057    windowOpacity: {
19058      selector: '.vjs-window-opacity > select',
19059      id: 'captions-window-opacity-%s',
19060      label: 'Opacity',
19061      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
19062    }
19063  };
19064  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
19065
19066  /**
19067   * Get the actual value of an option.
19068   *
19069   * @param  {string} value
19070   *         The value to get
19071   *
19072   * @param  {Function} [parser]
19073   *         Optional function to adjust the value.
19074   *
19075   * @return {*}
19076   *         - Will be `undefined` if no value exists
19077   *         - Will be `undefined` if the given value is "none".
19078   *         - Will be the actual value otherwise.
19079   *
19080   * @private
19081   */
19082  function parseOptionValue(value, parser) {
19083    if (parser) {
19084      value = parser(value);
19085    }
19086    if (value && value !== 'none') {
19087      return value;
19088    }
19089  }
19090
19091  /**
19092   * Gets the value of the selected <option> element within a <select> element.
19093   *
19094   * @param  {Element} el
19095   *         the element to look in
19096   *
19097   * @param  {Function} [parser]
19098   *         Optional function to adjust the value.
19099   *
19100   * @return {*}
19101   *         - Will be `undefined` if no value exists
19102   *         - Will be `undefined` if the given value is "none".
19103   *         - Will be the actual value otherwise.
19104   *
19105   * @private
19106   */
19107  function getSelectedOptionValue(el, parser) {
19108    const value = el.options[el.options.selectedIndex].value;
19109    return parseOptionValue(value, parser);
19110  }
19111
19112  /**
19113   * Sets the selected <option> element within a <select> element based on a
19114   * given value.
19115   *
19116   * @param {Element} el
19117   *        The element to look in.
19118   *
19119   * @param {string} value
19120   *        the property to look on.
19121   *
19122   * @param {Function} [parser]
19123   *        Optional function to adjust the value before comparing.
19124   *
19125   * @private
19126   */
19127  function setSelectedOption(el, value, parser) {
19128    if (!value) {
19129      return;
19130    }
19131    for (let i = 0; i < el.options.length; i++) {
19132      if (parseOptionValue(el.options[i].value, parser) === value) {
19133        el.selectedIndex = i;
19134        break;
19135      }
19136    }
19137  }
19138
19139  /**
19140   * Manipulate Text Tracks settings.
19141   *
19142   * @extends ModalDialog
19143   */
19144  class TextTrackSettings extends ModalDialog {
19145    /**
19146     * Creates an instance of this class.
19147     *
19148     * @param { import('../player').default } player
19149     *         The `Player` that this class should be attached to.
19150     *
19151     * @param {Object} [options]
19152     *         The key/value store of player options.
19153     */
19154    constructor(player, options) {
19155      options.temporary = false;
19156      super(player, options);
19157      this.updateDisplay = this.updateDisplay.bind(this);
19158
19159      // fill the modal and pretend we have opened it
19160      this.fill();
19161      this.hasBeenOpened_ = this.hasBeenFilled_ = true;
19162      this.endDialog = createEl('p', {
19163        className: 'vjs-control-text',
19164        textContent: this.localize('End of dialog window.')
19165      });
19166      this.el().appendChild(this.endDialog);
19167      this.setDefaults();
19168
19169      // Grab `persistTextTrackSettings` from the player options if not passed in child options
19170      if (options.persistTextTrackSettings === undefined) {
19171        this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
19172      }
19173      this.on(this.$('.vjs-done-button'), 'click', () => {
19174        this.saveSettings();
19175        this.close();
19176      });
19177      this.on(this.$('.vjs-default-button'), 'click', () => {
19178        this.setDefaults();
19179        this.updateDisplay();
19180      });
19181      each(selectConfigs, config => {
19182        this.on(this.$(config.selector), 'change', this.updateDisplay);
19183      });
19184      if (this.options_.persistTextTrackSettings) {
19185        this.restoreSettings();
19186      }
19187    }
19188    dispose() {
19189      this.endDialog = null;
19190      super.dispose();
19191    }
19192
19193    /**
19194     * Create a <select> element with configured options.
19195     *
19196     * @param {string} key
19197     *        Configuration key to use during creation.
19198     *
19199     * @return {string}
19200     *         An HTML string.
19201     *
19202     * @private
19203     */
19204    createElSelect_(key, legendId = '', type = 'label') {
19205      const config = selectConfigs[key];
19206      const id = config.id.replace('%s', this.id_);
19207      const selectLabelledbyIds = [legendId, id].join(' ').trim();
19208      return [`<${type} id="${id}" class="${type === 'label' ? 'vjs-label' : ''}">`, this.localize(config.label), `</${type}>`, `<select aria-labelledby="${selectLabelledbyIds}">`].concat(config.options.map(o => {
19209        const optionId = id + '-' + o[1].replace(/\W+/g, '');
19210        return [`<option id="${optionId}" value="${o[0]}" `, `aria-labelledby="${selectLabelledbyIds} ${optionId}">`, this.localize(o[1]), '</option>'].join('');
19211      })).concat('</select>').join('');
19212    }
19213
19214    /**
19215     * Create foreground color element for the component
19216     *
19217     * @return {string}
19218     *         An HTML string.
19219     *
19220     * @private
19221     */
19222    createElFgColor_() {
19223      const legendId = `captions-text-legend-${this.id_}`;
19224      return ['<fieldset class="vjs-fg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text'), '</legend>', '<span class="vjs-text-color">', this.createElSelect_('color', legendId), '</span>', '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
19225    }
19226
19227    /**
19228     * Create background color element for the component
19229     *
19230     * @return {string}
19231     *         An HTML string.
19232     *
19233     * @private
19234     */
19235    createElBgColor_() {
19236      const legendId = `captions-background-${this.id_}`;
19237      return ['<fieldset class="vjs-bg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text Background'), '</legend>', '<span class="vjs-bg-color">', this.createElSelect_('backgroundColor', legendId), '</span>', '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
19238    }
19239
19240    /**
19241     * Create window color element for the component
19242     *
19243     * @return {string}
19244     *         An HTML string.
19245     *
19246     * @private
19247     */
19248    createElWinColor_() {
19249      const legendId = `captions-window-${this.id_}`;
19250      return ['<fieldset class="vjs-window vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Caption Area Background'), '</legend>', '<span class="vjs-window-color">', this.createElSelect_('windowColor', legendId), '</span>', '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
19251    }
19252
19253    /**
19254     * Create color elements for the component
19255     *
19256     * @return {Element}
19257     *         The element that was created
19258     *
19259     * @private
19260     */
19261    createElColors_() {
19262      return createEl('div', {
19263        className: 'vjs-track-settings-colors',
19264        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
19265      });
19266    }
19267
19268    /**
19269     * Create font elements for the component
19270     *
19271     * @return {Element}
19272     *         The element that was created.
19273     *
19274     * @private
19275     */
19276    createElFont_() {
19277      return createEl('div', {
19278        className: 'vjs-track-settings-font',
19279        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('')
19280      });
19281    }
19282
19283    /**
19284     * Create controls for the component
19285     *
19286     * @return {Element}
19287     *         The element that was created.
19288     *
19289     * @private
19290     */
19291    createElControls_() {
19292      const defaultsDescription = this.localize('restore all settings to the default values');
19293      return createEl('div', {
19294        className: 'vjs-track-settings-controls',
19295        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('')
19296      });
19297    }
19298    content() {
19299      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
19300    }
19301    label() {
19302      return this.localize('Caption Settings Dialog');
19303    }
19304    description() {
19305      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
19306    }
19307    buildCSSClass() {
19308      return super.buildCSSClass() + ' vjs-text-track-settings';
19309    }
19310
19311    /**
19312     * Gets an object of text track settings (or null).
19313     *
19314     * @return {Object}
19315     *         An object with config values parsed from the DOM or localStorage.
19316     */
19317    getValues() {
19318      return reduce(selectConfigs, (accum, config, key) => {
19319        const value = getSelectedOptionValue(this.$(config.selector), config.parser);
19320        if (value !== undefined) {
19321          accum[key] = value;
19322        }
19323        return accum;
19324      }, {});
19325    }
19326
19327    /**
19328     * Sets text track settings from an object of values.
19329     *
19330     * @param {Object} values
19331     *        An object with config values parsed from the DOM or localStorage.
19332     */
19333    setValues(values) {
19334      each(selectConfigs, (config, key) => {
19335        setSelectedOption(this.$(config.selector), values[key], config.parser);
19336      });
19337    }
19338
19339    /**
19340     * Sets all `<select>` elements to their default values.
19341     */
19342    setDefaults() {
19343      each(selectConfigs, config => {
19344        const index = config.hasOwnProperty('default') ? config.default : 0;
19345        this.$(config.selector).selectedIndex = index;
19346      });
19347    }
19348
19349    /**
19350     * Restore texttrack settings from localStorage
19351     */
19352    restoreSettings() {
19353      let values;
vendor: 8,527 bytes, lines 19354-19612
19354      try {
19355        values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY$1));
19356      } catch (err) {
19357        log$1.warn(err);
19358      }
19359      if (values) {
19360        this.setValues(values);
19361      }
19362    }
19363
19364    /**
19365     * Save text track settings to localStorage
19366     */
19367    saveSettings() {
19368      if (!this.options_.persistTextTrackSettings) {
19369        return;
19370      }
19371      const values = this.getValues();
19372      try {
19373        if (Object.keys(values).length) {
19374          window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
19375        } else {
19376          window.localStorage.removeItem(LOCAL_STORAGE_KEY$1);
19377        }
19378      } catch (err) {
19379        log$1.warn(err);
19380      }
19381    }
19382
19383    /**
19384     * Update display of text track settings
19385     */
19386    updateDisplay() {
19387      const ttDisplay = this.player_.getChild('textTrackDisplay');
19388      if (ttDisplay) {
19389        ttDisplay.updateDisplay();
19390      }
19391    }
19392
19393    /**
19394     * conditionally blur the element and refocus the captions button
19395     *
19396     * @private
19397     */
19398    conditionalBlur_() {
19399      this.previouslyActiveEl_ = null;
19400      const cb = this.player_.controlBar;
19401      const subsCapsBtn = cb && cb.subsCapsButton;
19402      const ccBtn = cb && cb.captionsButton;
19403      if (subsCapsBtn) {
19404        subsCapsBtn.focus();
19405      } else if (ccBtn) {
19406        ccBtn.focus();
19407      }
19408    }
19409
19410    /**
19411     * Repopulate dialog with new localizations on languagechange
19412     */
19413    handleLanguagechange() {
19414      this.fill();
19415    }
19416  }
19417  Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
19418
19419  /**
19420   * @file resize-manager.js
19421   */
19422
19423  /**
19424   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19425   *
19426   * 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}.
19427   *
19428   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19429   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19430   *
19431   * @example <caption>How to disable the resize manager</caption>
19432   * const player = videojs('#vid', {
19433   *   resizeManager: false
19434   * });
19435   *
19436   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19437   *
19438   * @extends Component
19439   */
19440  class ResizeManager extends Component$1 {
19441    /**
19442     * Create the ResizeManager.
19443     *
19444     * @param {Object} player
19445     *        The `Player` that this class should be attached to.
19446     *
19447     * @param {Object} [options]
19448     *        The key/value store of ResizeManager options.
19449     *
19450     * @param {Object} [options.ResizeObserver]
19451     *        A polyfill for ResizeObserver can be passed in here.
19452     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19453     */
19454    constructor(player, options) {
19455      let RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window.ResizeObserver;
19456
19457      // if `null` was passed, we want to disable the ResizeObserver
19458      if (options.ResizeObserver === null) {
19459        RESIZE_OBSERVER_AVAILABLE = false;
19460      }
19461
19462      // Only create an element when ResizeObserver isn't available
19463      const options_ = merge$2({
19464        createEl: !RESIZE_OBSERVER_AVAILABLE,
19465        reportTouchActivity: false
19466      }, options);
19467      super(player, options_);
19468      this.ResizeObserver = options.ResizeObserver || window.ResizeObserver;
19469      this.loadListener_ = null;
19470      this.resizeObserver_ = null;
19471      this.debouncedHandler_ = debounce(() => {
19472        this.resizeHandler();
19473      }, 100, false, this);
19474      if (RESIZE_OBSERVER_AVAILABLE) {
19475        this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
19476        this.resizeObserver_.observe(player.el());
19477      } else {
19478        this.loadListener_ = () => {
19479          if (!this.el_ || !this.el_.contentWindow) {
19480            return;
19481          }
19482          const debouncedHandler_ = this.debouncedHandler_;
19483          let unloadListener_ = this.unloadListener_ = function () {
19484            off(this, 'resize', debouncedHandler_);
19485            off(this, 'unload', unloadListener_);
19486            unloadListener_ = null;
19487          };
19488
19489          // safari and edge can unload the iframe before resizemanager dispose
19490          // we have to dispose of event handlers correctly before that happens
19491          on(this.el_.contentWindow, 'unload', unloadListener_);
19492          on(this.el_.contentWindow, 'resize', debouncedHandler_);
19493        };
19494        this.one('load', this.loadListener_);
19495      }
19496    }
19497    createEl() {
19498      return super.createEl('iframe', {
19499        className: 'vjs-resize-manager',
19500        tabIndex: -1,
19501        title: this.localize('No content')
19502      }, {
19503        'aria-hidden': 'true'
19504      });
19505    }
19506
19507    /**
19508     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19509     *
19510     * @fires Player#playerresize
19511     */
19512    resizeHandler() {
19513      /**
19514       * Called when the player size has changed
19515       *
19516       * @event Player#playerresize
19517       * @type {Event}
19518       */
19519      // make sure player is still around to trigger
19520      // prevents this from causing an error after dispose
19521      if (!this.player_ || !this.player_.trigger) {
19522        return;
19523      }
19524      this.player_.trigger('playerresize');
19525    }
19526    dispose() {
19527      if (this.debouncedHandler_) {
19528        this.debouncedHandler_.cancel();
19529      }
19530      if (this.resizeObserver_) {
19531        if (this.player_.el()) {
19532          this.resizeObserver_.unobserve(this.player_.el());
19533        }
19534        this.resizeObserver_.disconnect();
19535      }
19536      if (this.loadListener_) {
19537        this.off('load', this.loadListener_);
19538      }
19539      if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
19540        this.unloadListener_.call(this.el_.contentWindow);
19541      }
19542      this.ResizeObserver = null;
19543      this.resizeObserver = null;
19544      this.debouncedHandler_ = null;
19545      this.loadListener_ = null;
19546      super.dispose();
19547    }
19548  }
19549  Component$1.registerComponent('ResizeManager', ResizeManager);
19550
19551  const defaults = {
19552    trackingThreshold: 20,
19553    liveTolerance: 15
19554  };
19555
19556  /*
19557    track when we are at the live edge, and other helpers for live playback */
19558
19559  /**
19560   * A class for checking live current time and determining when the player
19561   * is at or behind the live edge.
19562   */
19563  class LiveTracker extends Component$1 {
19564    /**
19565     * Creates an instance of this class.
19566     *
19567     * @param { import('./player').default } player
19568     *        The `Player` that this class should be attached to.
19569     *
19570     * @param {Object} [options]
19571     *        The key/value store of player options.
19572     *
19573     * @param {number} [options.trackingThreshold=20]
19574     *        Number of seconds of live window (seekableEnd - seekableStart) that
19575     *        media needs to have before the liveui will be shown.
19576     *
19577     * @param {number} [options.liveTolerance=15]
19578     *        Number of seconds behind live that we have to be
19579     *        before we will be considered non-live. Note that this will only
19580     *        be used when playing at the live edge. This allows large seekable end
19581     *        changes to not effect whether we are live or not.
19582     */
19583    constructor(player, options) {
19584      // LiveTracker does not need an element
19585      const options_ = merge$2(defaults, options, {
19586        createEl: false
19587      });
19588      super(player, options_);
19589      this.trackLiveHandler_ = () => this.trackLive_();
19590      this.handlePlay_ = e => this.handlePlay(e);
19591      this.handleFirstTimeupdate_ = e => this.handleFirstTimeupdate(e);
19592      this.handleSeeked_ = e => this.handleSeeked(e);
19593      this.seekToLiveEdge_ = e => this.seekToLiveEdge(e);
19594      this.reset_();
19595      this.on(this.player_, 'durationchange', e => this.handleDurationchange(e));
19596      // we should try to toggle tracking on canplay as native playback engines, like Safari
19597      // may not have the proper values for things like seekableEnd until then
19598      this.on(this.player_, 'canplay', () => this.toggleTracking());
19599    }
19600
19601    /**
19602     * all the functionality for tracking when seek end changes
19603     * and for tracking how far past seek end we should be
19604     */
19605    trackLive_() {
19606      const seekable = this.player_.seekable();
19607
19608      // skip undefined seekable
19609      if (!seekable || !seekable.length) {
19610        return;
19611      }
19612      const newTime = Number(window
vendor: 9,638 bytes, lines 19612-19937
19612.performance.now().toFixed(4));
19613      const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
19614      this.lastTime_ = newTime;
19615      this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
19616      const liveCurrentTime = this.liveCurrentTime();
19617      const currentTime = this.player_.currentTime();
19618
19619      // we are behind live if any are true
19620      // 1. the player is paused
19621      // 2. the user seeked to a location 2 seconds away from live
19622      // 3. the difference between live and current time is greater
19623      //    liveTolerance which defaults to 15s
19624      let isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
19625
19626      // we cannot be behind if
19627      // 1. until we have not seen a timeupdate yet
19628      // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
19629      if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
19630        isBehind = false;
19631      }
19632      if (isBehind !== this.behindLiveEdge_) {
19633        this.behindLiveEdge_ = isBehind;
19634        this.trigger('liveedgechange');
19635      }
19636    }
19637
19638    /**
19639     * handle a durationchange event on the player
19640     * and start/stop tracking accordingly.
19641     */
19642    handleDurationchange() {
19643      this.toggleTracking();
19644    }
19645
19646    /**
19647     * start/stop tracking
19648     */
19649    toggleTracking() {
19650      if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
19651        if (this.player_.options_.liveui) {
19652          this.player_.addClass('vjs-liveui');
19653        }
19654        this.startTracking();
19655      } else {
19656        this.player_.removeClass('vjs-liveui');
19657        this.stopTracking();
19658      }
19659    }
19660
19661    /**
19662     * start tracking live playback
19663     */
19664    startTracking() {
19665      if (this.isTracking()) {
19666        return;
19667      }
19668
19669      // If we haven't seen a timeupdate, we need to check whether playback
19670      // began before this component started tracking. This can happen commonly
19671      // when using autoplay.
19672      if (!this.timeupdateSeen_) {
19673        this.timeupdateSeen_ = this.player_.hasStarted();
19674      }
19675      this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
19676      this.trackLive_();
19677      this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19678      if (!this.timeupdateSeen_) {
19679        this.one(this.player_, 'play', this.handlePlay_);
19680        this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19681      } else {
19682        this.on(this.player_, 'seeked', this.handleSeeked_);
19683      }
19684    }
19685
19686    /**
19687     * handle the first timeupdate on the player if it wasn't already playing
19688     * when live tracker started tracking.
19689     */
19690    handleFirstTimeupdate() {
19691      this.timeupdateSeen_ = true;
19692      this.on(this.player_, 'seeked', this.handleSeeked_);
19693    }
19694
19695    /**
19696     * Keep track of what time a seek starts, and listen for seeked
19697     * to find where a seek ends.
19698     */
19699    handleSeeked() {
19700      const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
19701      this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
19702      this.nextSeekedFromUser_ = false;
19703      this.trackLive_();
19704    }
19705
19706    /**
19707     * handle the first play on the player, and make sure that we seek
19708     * right to the live edge.
19709     */
19710    handlePlay() {
19711      this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
19712    }
19713
19714    /**
19715     * Stop tracking, and set all internal variables to
19716     * their initial value.
19717     */
19718    reset_() {
19719      this.lastTime_ = -1;
19720      this.pastSeekEnd_ = 0;
19721      this.lastSeekEnd_ = -1;
19722      this.behindLiveEdge_ = true;
19723      this.timeupdateSeen_ = false;
19724      this.seekedBehindLive_ = false;
19725      this.nextSeekedFromUser_ = false;
19726      this.clearInterval(this.trackingInterval_);
19727      this.trackingInterval_ = null;
19728      this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19729      this.off(this.player_, 'seeked', this.handleSeeked_);
19730      this.off(this.player_, 'play', this.handlePlay_);
19731      this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19732      this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
19733    }
19734
19735    /**
19736     * The next seeked event is from the user. Meaning that any seek
19737     * > 2s behind live will be considered behind live for real and
19738     * liveTolerance will be ignored.
19739     */
19740    nextSeekedFromUser() {
19741      this.nextSeekedFromUser_ = true;
19742    }
19743
19744    /**
19745     * stop tracking live playback
19746     */
19747    stopTracking() {
19748      if (!this.isTracking()) {
19749        return;
19750      }
19751      this.reset_();
19752      this.trigger('liveedgechange');
19753    }
19754
19755    /**
19756     * A helper to get the player seekable end
19757     * so that we don't have to null check everywhere
19758     *
19759     * @return {number}
19760     *         The furthest seekable end or Infinity.
19761     */
19762    seekableEnd() {
19763      const seekable = this.player_.seekable();
19764      const seekableEnds = [];
19765      let i = seekable ? seekable.length : 0;
19766      while (i--) {
19767        seekableEnds.push(seekable.end(i));
19768      }
19769
19770      // grab the furthest seekable end after sorting, or if there are none
19771      // default to Infinity
19772      return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
19773    }
19774
19775    /**
19776     * A helper to get the player seekable start
19777     * so that we don't have to null check everywhere
19778     *
19779     * @return {number}
19780     *         The earliest seekable start or 0.
19781     */
19782    seekableStart() {
19783      const seekable = this.player_.seekable();
19784      const seekableStarts = [];
19785      let i = seekable ? seekable.length : 0;
19786      while (i--) {
19787        seekableStarts.push(seekable.start(i));
19788      }
19789
19790      // grab the first seekable start after sorting, or if there are none
19791      // default to 0
19792      return seekableStarts.length ? seekableStarts.sort()[0] : 0;
19793    }
19794
19795    /**
19796     * Get the live time window aka
19797     * the amount of time between seekable start and
19798     * live current time.
19799     *
19800     * @return {number}
19801     *         The amount of seconds that are seekable in
19802     *         the live video.
19803     */
19804    liveWindow() {
19805      const liveCurrentTime = this.liveCurrentTime();
19806
19807      // if liveCurrenTime is Infinity then we don't have a liveWindow at all
19808      if (liveCurrentTime === Infinity) {
19809        return 0;
19810      }
19811      return liveCurrentTime - this.seekableStart();
19812    }
19813
19814    /**
19815     * Determines if the player is live, only checks if this component
19816     * is tracking live playback or not
19817     *
19818     * @return {boolean}
19819     *         Whether liveTracker is tracking
19820     */
19821    isLive() {
19822      return this.isTracking();
19823    }
19824
19825    /**
19826     * Determines if currentTime is at the live edge and won't fall behind
19827     * on each seekableendchange
19828     *
19829     * @return {boolean}
19830     *         Whether playback is at the live edge
19831     */
19832    atLiveEdge() {
19833      return !this.behindLiveEdge();
19834    }
19835
19836    /**
19837     * get what we expect the live current time to be
19838     *
19839     * @return {number}
19840     *         The expected live current time
19841     */
19842    liveCurrentTime() {
19843      return this.pastSeekEnd() + this.seekableEnd();
19844    }
19845
19846    /**
19847     * The number of seconds that have occurred after seekable end
19848     * changed. This will be reset to 0 once seekable end changes.
19849     *
19850     * @return {number}
19851     *         Seconds past the current seekable end
19852     */
19853    pastSeekEnd() {
19854      const seekableEnd = this.seekableEnd();
19855      if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
19856        this.pastSeekEnd_ = 0;
19857      }
19858      this.lastSeekEnd_ = seekableEnd;
19859      return this.pastSeekEnd_;
19860    }
19861
19862    /**
19863     * If we are currently behind the live edge, aka currentTime will be
19864     * behind on a seekableendchange
19865     *
19866     * @return {boolean}
19867     *         If we are behind the live edge
19868     */
19869    behindLiveEdge() {
19870      return this.behindLiveEdge_;
19871    }
19872
19873    /**
19874     * Whether live tracker is currently tracking or not.
19875     */
19876    isTracking() {
19877      return typeof this.trackingInterval_ === 'number';
19878    }
19879
19880    /**
19881     * Seek to the live edge if we are behind the live edge
19882     */
19883    seekToLiveEdge() {
19884      this.seekedBehindLive_ = false;
19885      if (this.atLiveEdge()) {
19886        return;
19887      }
19888      this.nextSeekedFromUser_ = false;
19889      this.player_.currentTime(this.liveCurrentTime());
19890    }
19891
19892    /**
19893     * Dispose of liveTracker
19894     */
19895    dispose() {
19896      this.stopTracking();
19897      super.dispose();
19898    }
19899  }
19900  Component$1.registerComponent('LiveTracker', LiveTracker);
19901
19902  /**
19903   * Displays an element over the player which contains an optional title and
19904   * description for the current content.
19905   *
19906   * Much of the code for this component originated in the now obsolete
19907   * videojs-dock plugin: https://github.com/brightcove/videojs-dock/
19908   *
19909   * @extends Component
19910   */
19911  class TitleBar extends Component$1 {
19912    constructor(player, options) {
19913      super(player, options);
19914      this.on('statechanged', e => this.updateDom_());
19915      this.updateDom_();
19916    }
19917
19918    /**
19919     * Create the `TitleBar`'s DOM element
19920     *
19921     * @return {Element}
19922     *         The element that was created.
19923     */
19924    createEl() {
19925      this.els = {
19926        title: createEl('div', {
19927          className: 'vjs-title-bar-title',
19928          id: `vjs-title-bar-title-${newGUID()}`
19929        }),
19930        description: createEl('div', {
19931          className: 'vjs-title-bar-description',
19932          id: `vjs-title-bar-description-${newGUID()}`
19933        })
19934      };
19935      return createEl('div', {
19936        className: 'vjs-title-bar'
19937      }, {}, values
vendor: 10,251 bytes, lines 19937-20248
19937$1(this.els));
19938    }
19939
19940    /**
19941     * Updates the DOM based on the component's state object.
19942     */
19943    updateDom_() {
19944      const tech = this.player_.tech_;
19945      const techEl = tech && tech.el_;
19946      const techAriaAttrs = {
19947        title: 'aria-labelledby',
19948        description: 'aria-describedby'
19949      };
19950      ['title', 'description'].forEach(k => {
19951        const value = this.state[k];
19952        const el = this.els[k];
19953        const techAriaAttr = techAriaAttrs[k];
19954        emptyEl(el);
19955        if (value) {
19956          textContent(el, value);
19957        }
19958
19959        // If there is a tech element available, update its ARIA attributes
19960        // according to whether a title and/or description have been provided.
19961        if (techEl) {
19962          techEl.removeAttribute(techAriaAttr);
19963          if (value) {
19964            techEl.setAttribute(techAriaAttr, el.id);
19965          }
19966        }
19967      });
19968      if (this.state.title || this.state.description) {
19969        this.show();
19970      } else {
19971        this.hide();
19972      }
19973    }
19974
19975    /**
19976     * Update the contents of the title bar component with new title and
19977     * description text.
19978     *
19979     * If both title and description are missing, the title bar will be hidden.
19980     *
19981     * If either title or description are present, the title bar will be visible.
19982     *
19983     * NOTE: Any previously set value will be preserved. To unset a previously
19984     * set value, you must pass an empty string or null.
19985     *
19986     * For example:
19987     *
19988     * ```
19989     * update({title: 'foo', description: 'bar'}) // title: 'foo', description: 'bar'
19990     * update({description: 'bar2'}) // title: 'foo', description: 'bar2'
19991     * update({title: ''}) // title: '', description: 'bar2'
19992     * update({title: 'foo', description: null}) // title: 'foo', description: null
19993     * ```
19994     *
19995     * @param  {Object} [options={}]
19996     *         An options object. When empty, the title bar will be hidden.
19997     *
19998     * @param  {string} [options.title]
19999     *         A title to display in the title bar.
20000     *
20001     * @param  {string} [options.description]
20002     *         A description to display in the title bar.
20003     */
20004    update(options) {
20005      this.setState(options);
20006    }
20007
20008    /**
20009     * Dispose the component.
20010     */
20011    dispose() {
20012      const tech = this.player_.tech_;
20013      const techEl = tech && tech.el_;
20014      if (techEl) {
20015        techEl.removeAttribute('aria-labelledby');
20016        techEl.removeAttribute('aria-describedby');
20017      }
20018      super.dispose();
20019      this.els = null;
20020    }
20021  }
20022  Component$1.registerComponent('TitleBar', TitleBar);
20023
20024  /**
20025   * This function is used to fire a sourceset when there is something
20026   * similar to `mediaEl.load()` being called. It will try to find the source via
20027   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20028   * with the source that was found or empty string if we cannot know. If it cannot
20029   * find a source then `sourceset` will not be fired.
20030   *
20031   * @param { import('./html5').default } tech
20032   *        The tech object that sourceset was setup on
20033   *
20034   * @return {boolean}
20035   *         returns false if the sourceset was not fired and true otherwise.
20036   */
20037  const sourcesetLoad = tech => {
20038    const el = tech.el();
20039
20040    // if `el.src` is set, that source will be loaded.
20041    if (el.hasAttribute('src')) {
20042      tech.triggerSourceset(el.src);
20043      return true;
20044    }
20045
20046    /**
20047     * Since there isn't a src property on the media element, source elements will be used for
20048     * implementing the source selection algorithm. This happens asynchronously and
20049     * for most cases were there is more than one source we cannot tell what source will
20050     * be loaded, without re-implementing the source selection algorithm. At this time we are not
20051     * going to do that. There are three special cases that we do handle here though:
20052     *
20053     * 1. If there are no sources, do not fire `sourceset`.
20054     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20055     * 3. If there is more than one `<source>` but all of them have the same `src` url.
20056     *    That will be our src.
20057     */
20058    const sources = tech.$$('source');
20059    const srcUrls = [];
20060    let src = '';
20061
20062    // if there are no sources, do not fire sourceset
20063    if (!sources.length) {
20064      return false;
20065    }
20066
20067    // only count valid/non-duplicate source elements
20068    for (let i = 0; i < sources.length; i++) {
20069      const url = sources[i].src;
20070      if (url && srcUrls.indexOf(url) === -1) {
20071        srcUrls.push(url);
20072      }
20073    }
20074
20075    // there were no valid sources
20076    if (!srcUrls.length) {
20077      return false;
20078    }
20079
20080    // there is only one valid source element url
20081    // use that
20082    if (srcUrls.length === 1) {
20083      src = srcUrls[0];
20084    }
20085    tech.triggerSourceset(src);
20086    return true;
20087  };
20088
20089  /**
20090   * our implementation of an `innerHTML` descriptor for browsers
20091   * that do not have one.
20092   */
20093  const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
20094    get() {
20095      return this.cloneNode(true).innerHTML;
20096    },
20097    set(v) {
20098      // make a dummy node to use innerHTML on
20099      const dummy = document.createElement(this.nodeName.toLowerCase());
20100
20101      // set innerHTML to the value provided
20102      dummy.innerHTML = v;
20103
20104      // make a document fragment to hold the nodes from dummy
20105      const docFrag = document.createDocumentFragment();
20106
20107      // copy all of the nodes created by the innerHTML on dummy
20108      // to the document fragment
20109      while (dummy.childNodes.length) {
20110        docFrag.appendChild(dummy.childNodes[0]);
20111      }
20112
20113      // remove content
20114      this.innerText = '';
20115
20116      // now we add all of that html in one by appending the
20117      // document fragment. This is how innerHTML does it.
20118      window.Element.prototype.appendChild.call(this, docFrag);
20119
20120      // then return the result that innerHTML's setter would
20121      return this.innerHTML;
20122    }
20123  });
20124
20125  /**
20126   * Get a property descriptor given a list of priorities and the
20127   * property to get.
20128   */
20129  const getDescriptor = (priority, prop) => {
20130    let descriptor = {};
20131    for (let i = 0; i < priority.length; i++) {
20132      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
20133      if (descriptor && descriptor.set && descriptor.get) {
20134        break;
20135      }
20136    }
20137    descriptor.enumerable = true;
20138    descriptor.configurable = true;
20139    return descriptor;
20140  };
20141  const getInnerHTMLDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, window.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
20142
20143  /**
20144   * Patches browser internal functions so that we can tell synchronously
20145   * if a `<source>` was appended to the media element. For some reason this
20146   * causes a `sourceset` if the the media element is ready and has no source.
20147   * This happens when:
20148   * - The page has just loaded and the media element does not have a source.
20149   * - The media element was emptied of all sources, then `load()` was called.
20150   *
20151   * It does this by patching the following functions/properties when they are supported:
20152   *
20153   * - `append()` - can be used to add a `<source>` element to the media element
20154   * - `appendChild()` - can be used to add a `<source>` element to the media element
20155   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
20156   * - `innerHTML` -  can be used to add a `<source>` element to the media element
20157   *
20158   * @param {Html5} tech
20159   *        The tech object that sourceset is being setup on.
20160   */
20161  const firstSourceWatch = function (tech) {
20162    const el = tech.el();
20163
20164    // make sure firstSourceWatch isn't setup twice.
20165    if (el.resetSourceWatch_) {
20166      return;
20167    }
20168    const old = {};
20169    const innerDescriptor = getInnerHTMLDescriptor(tech);
20170    const appendWrapper = appendFn => (...args) => {
20171      const retval = appendFn.apply(el, args);
20172      sourcesetLoad(tech);
20173      return retval;
20174    };
20175    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(k => {
20176      if (!el[k]) {
20177        return;
20178      }
20179
20180      // store the old function
20181      old[k] = el[k];
20182
20183      // call the old function with a sourceset if a source
20184      // was loaded
20185      el[k] = appendWrapper(old[k]);
20186    });
20187    Object.defineProperty(el, 'innerHTML', merge$2(innerDescriptor, {
20188      set: appendWrapper(innerDescriptor.set)
20189    }));
20190    el.resetSourceWatch_ = () => {
20191      el.resetSourceWatch_ = null;
20192      Object.keys(old).forEach(k => {
20193        el[k] = old[k];
20194      });
20195      Object.defineProperty(el, 'innerHTML', innerDescriptor);
20196    };
20197
20198    // on the first sourceset, we need to revert our changes
20199    tech.one('sourceset', el.resetSourceWatch_);
20200  };
20201
20202  /**
20203   * our implementation of a `src` descriptor for browsers
20204   * that do not have one
20205   */
20206  const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
20207    get() {
20208      if (this.hasAttribute('src')) {
20209        return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src'));
20210      }
20211      return '';
20212    },
20213    set(v) {
20214      window.Element.prototype.setAttribute.call(this, 'src', v);
20215      return v;
20216    }
20217  });
20218  const getSrcDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
20219
20220  /**
20221   * setup `sourceset` handling on the `Html5` tech. This function
20222   * patches the following element properties/functions:
20223   *
20224   * - `src` - to determine when `src` is set
20225   * - `setAttribute()` - to determine when `src` is set
20226   * - `load()` - this re-triggers the source selection algorithm, and can
20227   *              cause a sourceset.
20228   *
20229   * If there is no source when we are adding `sourceset` support or during a `load()`
20230   * we also patch the functions listed in `firstSourceWatch`.
20231   *
20232   * @param {Html5} tech
20233   *        The tech to patch
20234   */
20235  const setupSourceset = function (tech) {
20236    if (!tech.featuresSourceset) {
20237      return;
20238    }
20239    const el = tech.el();
20240
20241    // make sure sourceset isn't setup twice.
20242    if (el.resetSourceset_) {
20243      return;
20244    }
20245    const srcDescriptor = getSrcDescriptor(tech);
20246    const oldSetAttribute = el.setAttribute;
20247    const oldLoad = el.load;
20248    Object.defineProperty(el, 'src', merge$2
vendor: 9,111 bytes, lines 20248-20486
20248(srcDescriptor, {
20249      set: v => {
20250        const retval = srcDescriptor.set.call(el, v);
20251
20252        // we use the getter here to get the actual value set on src
20253        tech.triggerSourceset(el.src);
20254        return retval;
20255      }
20256    }));
20257    el.setAttribute = (n, v) => {
20258      const retval = oldSetAttribute.call(el, n, v);
20259      if (/src/i.test(n)) {
20260        tech.triggerSourceset(el.src);
20261      }
20262      return retval;
20263    };
20264    el.load = () => {
20265      const retval = oldLoad.call(el);
20266
20267      // if load was called, but there was no source to fire
20268      // sourceset on. We have to watch for a source append
20269      // as that can trigger a `sourceset` when the media element
20270      // has no source
20271      if (!sourcesetLoad(tech)) {
20272        tech.triggerSourceset('');
20273        firstSourceWatch(tech);
20274      }
20275      return retval;
20276    };
20277    if (el.currentSrc) {
20278      tech.triggerSourceset(el.currentSrc);
20279    } else if (!sourcesetLoad(tech)) {
20280      firstSourceWatch(tech);
20281    }
20282    el.resetSourceset_ = () => {
20283      el.resetSourceset_ = null;
20284      el.load = oldLoad;
20285      el.setAttribute = oldSetAttribute;
20286      Object.defineProperty(el, 'src', srcDescriptor);
20287      if (el.resetSourceWatch_) {
20288        el.resetSourceWatch_();
20289      }
20290    };
20291  };
20292
20293  /**
20294   * @file html5.js
20295   */
20296
20297  /**
20298   * HTML5 Media Controller - Wrapper for HTML5 Media API
20299   *
20300   * @mixes Tech~SourceHandlerAdditions
20301   * @extends Tech
20302   */
20303  class Html5 extends Tech {
20304    /**
20305    * Create an instance of this Tech.
20306    *
20307    * @param {Object} [options]
20308    *        The key/value store of player options.
20309    *
20310    * @param {Function} [ready]
20311    *        Callback function to call when the `HTML5` Tech is ready.
20312    */
20313    constructor(options, ready) {
20314      super(options, ready);
20315      const source = options.source;
20316      let crossoriginTracks = false;
20317      this.featuresVideoFrameCallback = this.featuresVideoFrameCallback && this.el_.tagName === 'VIDEO';
20318
20319      // Set the source if one is provided
20320      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
20321      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
20322      // anyway so the error gets fired.
20323      if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
20324        this.setSource(source);
20325      } else {
20326        this.handleLateInit_(this.el_);
20327      }
20328
20329      // setup sourceset after late sourceset/init
20330      if (options.enableSourceset) {
20331        this.setupSourcesetHandling_();
20332      }
20333      this.isScrubbing_ = false;
20334      if (this.el_.hasChildNodes()) {
20335        const nodes = this.el_.childNodes;
20336        let nodesLength = nodes.length;
20337        const removeNodes = [];
20338        while (nodesLength--) {
20339          const node = nodes[nodesLength];
20340          const nodeName = node.nodeName.toLowerCase();
20341          if (nodeName === 'track') {
20342            if (!this.featuresNativeTextTracks) {
20343              // Empty video tag tracks so the built-in player doesn't use them also.
20344              // This may not be fast enough to stop HTML5 browsers from reading the tags
20345              // so we'll need to turn off any default tracks if we're manually doing
20346              // captions and subtitles. videoElement.textTracks
20347              removeNodes.push(node);
20348            } else {
20349              // store HTMLTrackElement and TextTrack to remote list
20350              this.remoteTextTrackEls().addTrackElement_(node);
20351              this.remoteTextTracks().addTrack(node.track);
20352              this.textTracks().addTrack(node.track);
20353              if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
20354                crossoriginTracks = true;
20355              }
20356            }
20357          }
20358        }
20359        for (let i = 0; i < removeNodes.length; i++) {
20360          this.el_.removeChild(removeNodes[i]);
20361        }
20362      }
20363      this.proxyNativeTracks_();
20364      if (this.featuresNativeTextTracks && crossoriginTracks) {
20365        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.');
20366      }
20367
20368      // prevent iOS Safari from disabling metadata text tracks during native playback
20369      this.restoreMetadataTracksInIOSNativePlayer_();
20370
20371      // Determine if native controls should be used
20372      // Our goal should be to get the custom controls on mobile solid everywhere
20373      // so we can remove this all together. Right now this will block custom
20374      // controls on touch enabled laptops like the Chrome Pixel
20375      if ((TOUCH_ENABLED || IS_IPHONE) && options.nativeControlsForTouch === true) {
20376        this.setControls(true);
20377      }
20378
20379      // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
20380      // into a `fullscreenchange` event
20381      this.proxyWebkitFullscreen_();
20382      this.triggerReady();
20383    }
20384
20385    /**
20386     * Dispose of `HTML5` media element and remove all tracks.
20387     */
20388    dispose() {
20389      if (this.el_ && this.el_.resetSourceset_) {
20390        this.el_.resetSourceset_();
20391      }
20392      Html5.disposeMediaElement(this.el_);
20393      this.options_ = null;
20394
20395      // tech will handle clearing of the emulated track list
20396      super.dispose();
20397    }
20398
20399    /**
20400     * Modify the media element so that we can detect when
20401     * the source is changed. Fires `sourceset` just after the source has changed
20402     */
20403    setupSourcesetHandling_() {
20404      setupSourceset(this);
20405    }
20406
20407    /**
20408     * When a captions track is enabled in the iOS Safari native player, all other
20409     * tracks are disabled (including metadata tracks), which nulls all of their
20410     * associated cue points. This will restore metadata tracks to their pre-fullscreen
20411     * state in those cases so that cue points are not needlessly lost.
20412     *
20413     * @private
20414     */
20415    restoreMetadataTracksInIOSNativePlayer_() {
20416      const textTracks = this.textTracks();
20417      let metadataTracksPreFullscreenState;
20418
20419      // captures a snapshot of every metadata track's current state
20420      const takeMetadataTrackSnapshot = () => {
20421        metadataTracksPreFullscreenState = [];
20422        for (let i = 0; i < textTracks.length; i++) {
20423          const track = textTracks[i];
20424          if (track.kind === 'metadata') {
20425            metadataTracksPreFullscreenState.push({
20426              track,
20427              storedMode: track.mode
20428            });
20429          }
20430        }
20431      };
20432
20433      // snapshot each metadata track's initial state, and update the snapshot
20434      // each time there is a track 'change' event
20435      takeMetadataTrackSnapshot();
20436      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20437      this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));
20438      const restoreTrackMode = () => {
20439        for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
20440          const storedTrack = metadataTracksPreFullscreenState[i];
20441          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
20442            storedTrack.track.mode = storedTrack.storedMode;
20443          }
20444        }
20445        // we only want this handler to be executed on the first 'change' event
20446        textTracks.removeEventListener('change', restoreTrackMode);
20447      };
20448
20449      // when we enter fullscreen playback, stop updating the snapshot and
20450      // restore all track modes to their pre-fullscreen state
20451      this.on('webkitbeginfullscreen', () => {
20452        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20453
20454        // remove the listener before adding it just in case it wasn't previously removed
20455        textTracks.removeEventListener('change', restoreTrackMode);
20456        textTracks.addEventListener('change', restoreTrackMode);
20457      });
20458
20459      // start updating the snapshot again after leaving fullscreen
20460      this.on('webkitendfullscreen', () => {
20461        // remove the listener before adding it just in case it wasn't previously removed
20462        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20463        textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20464
20465        // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
20466        textTracks.removeEventListener('change', restoreTrackMode);
20467      });
20468    }
20469
20470    /**
20471     * Attempt to force override of tracks for the given type
20472     *
20473     * @param {string} type - Track type to override, possible values include 'Audio',
20474     * 'Video', and 'Text'.
20475     * @param {boolean} override - If set to true native audio/video will be overridden,
20476     * otherwise native audio/video will potentially be used.
20477     * @private
20478     */
20479    overrideNative_(type, override) {
20480      // If there is no behavioral change don't add/remove listeners
20481      if (override !== this[`featuresNative${type}Tracks`]) {
20482        return;
20483      }
20484      const lowerCaseType = type.toLowerCase();
20485      if (this[`${lowerCaseType}TracksListeners_`]) {
20486        Object.keys(this[`${lowerCaseType}
vendor: 5,859 bytes, lines 20486-20646
20486TracksListeners_`]).forEach(eventName => {
20487          const elTracks = this.el()[`${lowerCaseType}Tracks`];
20488          elTracks.removeEventListener(eventName, this[`${lowerCaseType}TracksListeners_`][eventName]);
20489        });
20490      }
20491      this[`featuresNative${type}Tracks`] = !override;
20492      this[`${lowerCaseType}TracksListeners_`] = null;
20493      this.proxyNativeTracksForType_(lowerCaseType);
20494    }
20495
20496    /**
20497     * Attempt to force override of native audio tracks.
20498     *
20499     * @param {boolean} override - If set to true native audio will be overridden,
20500     * otherwise native audio will potentially be used.
20501     */
20502    overrideNativeAudioTracks(override) {
20503      this.overrideNative_('Audio', override);
20504    }
20505
20506    /**
20507     * Attempt to force override of native video tracks.
20508     *
20509     * @param {boolean} override - If set to true native video will be overridden,
20510     * otherwise native video will potentially be used.
20511     */
20512    overrideNativeVideoTracks(override) {
20513      this.overrideNative_('Video', override);
20514    }
20515
20516    /**
20517     * Proxy native track list events for the given type to our track
20518     * lists if the browser we are playing in supports that type of track list.
20519     *
20520     * @param {string} name - Track type; values include 'audio', 'video', and 'text'
20521     * @private
20522     */
20523    proxyNativeTracksForType_(name) {
20524      const props = NORMAL[name];
20525      const elTracks = this.el()[props.getterName];
20526      const techTracks = this[props.getterName]();
20527      if (!this[`featuresNative${props.capitalName}Tracks`] || !elTracks || !elTracks.addEventListener) {
20528        return;
20529      }
20530      const listeners = {
20531        change: e => {
20532          const event = {
20533            type: 'change',
20534            target: techTracks,
20535            currentTarget: techTracks,
20536            srcElement: techTracks
20537          };
20538          techTracks.trigger(event);
20539
20540          // if we are a text track change event, we should also notify the
20541          // remote text track list. This can potentially cause a false positive
20542          // if we were to get a change event on a non-remote track and
20543          // we triggered the event on the remote text track list which doesn't
20544          // contain that track. However, best practices mean looping through the
20545          // list of tracks and searching for the appropriate mode value, so,
20546          // this shouldn't pose an issue
20547          if (name === 'text') {
20548            this[REMOTE.remoteText.getterName]().trigger(event);
20549          }
20550        },
20551        addtrack(e) {
20552          techTracks.addTrack(e.track);
20553        },
20554        removetrack(e) {
20555          techTracks.removeTrack(e.track);
20556        }
20557      };
20558      const removeOldTracks = function () {
20559        const removeTracks = [];
20560        for (let i = 0; i < techTracks.length; i++) {
20561          let found = false;
20562          for (let j = 0; j < elTracks.length; j++) {
20563            if (elTracks[j] === techTracks[i]) {
20564              found = true;
20565              break;
20566            }
20567          }
20568          if (!found) {
20569            removeTracks.push(techTracks[i]);
20570          }
20571        }
20572        while (removeTracks.length) {
20573          techTracks.removeTrack(removeTracks.shift());
20574        }
20575      };
20576      this[props.getterName + 'Listeners_'] = listeners;
20577      Object.keys(listeners).forEach(eventName => {
20578        const listener = listeners[eventName];
20579        elTracks.addEventListener(eventName, listener);
20580        this.on('dispose', e => elTracks.removeEventListener(eventName, listener));
20581      });
20582
20583      // Remove (native) tracks that are not used anymore
20584      this.on('loadstart', removeOldTracks);
20585      this.on('dispose', e => this.off('loadstart', removeOldTracks));
20586    }
20587
20588    /**
20589     * Proxy all native track list events to our track lists if the browser we are playing
20590     * in supports that type of track list.
20591     *
20592     * @private
20593     */
20594    proxyNativeTracks_() {
20595      NORMAL.names.forEach(name => {
20596        this.proxyNativeTracksForType_(name);
20597      });
20598    }
20599
20600    /**
20601     * Create the `Html5` Tech's DOM element.
20602     *
20603     * @return {Element}
20604     *         The element that gets created.
20605     */
20606    createEl() {
20607      let el = this.options_.tag;
20608
20609      // Check if this browser supports moving the element into the box.
20610      // On the iPhone video will break if you move the element,
20611      // So we have to create a brand new element.
20612      // If we ingested the player div, we do not need to move the media element.
20613      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
20614        // If the original tag is still there, clone and remove it.
20615        if (el) {
20616          const clone = el.cloneNode(true);
20617          if (el.parentNode) {
20618            el.parentNode.insertBefore(clone, el);
20619          }
20620          Html5.disposeMediaElement(el);
20621          el = clone;
20622        } else {
20623          el = document.createElement('video');
20624
20625          // determine if native controls should be used
20626          const tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
20627          const attributes = merge$2({}, tagAttributes);
20628          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
20629            delete attributes.controls;
20630          }
20631          setAttributes(el, Object.assign(attributes, {
20632            id: this.options_.techId,
20633            class: 'vjs-tech'
20634          }));
20635        }
20636        el.playerId = this.options_.playerId;
20637      }
20638      if (typeof this.options_.preload !== 'undefined') {
20639        setAttribute(el, 'preload', this.options_.preload);
20640      }
20641      if (this.options_.disablePictureInPicture !== undefined) {
20642        el.disablePictureInPicture = this.options_.disablePictureInPicture;
20643      }
20644
20645      // Update specific tag settings, in case they were overridden
20646      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
vendor: 11,693 bytes, lines 20647-20999
20647      // when iOS/Safari or other browsers attempt to autoplay.
20648      const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
20649      for (let i = 0; i < settingsAttrs.length; i++) {
20650        const attr = settingsAttrs[i];
20651        const value = this.options_[attr];
20652        if (typeof value !== 'undefined') {
20653          if (value) {
20654            setAttribute(el, attr, attr);
20655          } else {
20656            removeAttribute(el, attr);
20657          }
20658          el[attr] = value;
20659        }
20660      }
20661      return el;
20662    }
20663
20664    /**
20665     * This will be triggered if the loadstart event has already fired, before videojs was
20666     * ready. Two known examples of when this can happen are:
20667     * 1. If we're loading the playback object after it has started loading
20668     * 2. The media is already playing the (often with autoplay on) then
20669     *
20670     * This function will fire another loadstart so that videojs can catchup.
20671     *
20672     * @fires Tech#loadstart
20673     *
20674     * @return {undefined}
20675     *         returns nothing.
20676     */
20677    handleLateInit_(el) {
20678      if (el.networkState === 0 || el.networkState === 3) {
20679        // The video element hasn't started loading the source yet
20680        // or didn't find a source
20681        return;
20682      }
20683      if (el.readyState === 0) {
20684        // NetworkState is set synchronously BUT loadstart is fired at the
20685        // end of the current stack, usually before setInterval(fn, 0).
20686        // So at this point we know loadstart may have already fired or is
20687        // about to fire, and either way the player hasn't seen it yet.
20688        // We don't want to fire loadstart prematurely here and cause a
20689        // double loadstart so we'll wait and see if it happens between now
20690        // and the next loop, and fire it if not.
20691        // HOWEVER, we also want to make sure it fires before loadedmetadata
20692        // which could also happen between now and the next loop, so we'll
20693        // watch for that also.
20694        let loadstartFired = false;
20695        const setLoadstartFired = function () {
20696          loadstartFired = true;
20697        };
20698        this.on('loadstart', setLoadstartFired);
20699        const triggerLoadstart = function () {
20700          // We did miss the original loadstart. Make sure the player
20701          // sees loadstart before loadedmetadata
20702          if (!loadstartFired) {
20703            this.trigger('loadstart');
20704          }
20705        };
20706        this.on('loadedmetadata', triggerLoadstart);
20707        this.ready(function () {
20708          this.off('loadstart', setLoadstartFired);
20709          this.off('loadedmetadata', triggerLoadstart);
20710          if (!loadstartFired) {
20711            // We did miss the original native loadstart. Fire it now.
20712            this.trigger('loadstart');
20713          }
20714        });
20715        return;
20716      }
20717
20718      // From here on we know that loadstart already fired and we missed it.
20719      // The other readyState events aren't as much of a problem if we double
20720      // them, so not going to go to as much trouble as loadstart to prevent
20721      // that unless we find reason to.
20722      const eventsToTrigger = ['loadstart'];
20723
20724      // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
20725      eventsToTrigger.push('loadedmetadata');
20726
20727      // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
20728      if (el.readyState >= 2) {
20729        eventsToTrigger.push('loadeddata');
20730      }
20731
20732      // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
20733      if (el.readyState >= 3) {
20734        eventsToTrigger.push('canplay');
20735      }
20736
20737      // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
20738      if (el.readyState >= 4) {
20739        eventsToTrigger.push('canplaythrough');
20740      }
20741
20742      // We still need to give the player time to add event listeners
20743      this.ready(function () {
20744        eventsToTrigger.forEach(function (type) {
20745          this.trigger(type);
20746        }, this);
20747      });
20748    }
20749
20750    /**
20751     * Set whether we are scrubbing or not.
20752     * This is used to decide whether we should use `fastSeek` or not.
20753     * `fastSeek` is used to provide trick play on Safari browsers.
20754     *
20755     * @param {boolean} isScrubbing
20756     *                  - true for we are currently scrubbing
20757     *                  - false for we are no longer scrubbing
20758     */
20759    setScrubbing(isScrubbing) {
20760      this.isScrubbing_ = isScrubbing;
20761    }
20762
20763    /**
20764     * Get whether we are scrubbing or not.
20765     *
20766     * @return {boolean} isScrubbing
20767     *                  - true for we are currently scrubbing
20768     *                  - false for we are no longer scrubbing
20769     */
20770    scrubbing() {
20771      return this.isScrubbing_;
20772    }
20773
20774    /**
20775     * Set current time for the `HTML5` tech.
20776     *
20777     * @param {number} seconds
20778     *        Set the current time of the media to this.
20779     */
20780    setCurrentTime(seconds) {
20781      try {
20782        if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
20783          this.el_.fastSeek(seconds);
20784        } else {
20785          this.el_.currentTime = seconds;
20786        }
20787      } catch (e) {
20788        log$1(e, 'Video is not ready. (Video.js)');
20789        // this.warning(VideoJS.warnings.videoNotReady);
20790      }
20791    }
20792
20793    /**
20794     * Get the current duration of the HTML5 media element.
20795     *
20796     * @return {number}
20797     *         The duration of the media or 0 if there is no duration.
20798     */
20799    duration() {
20800      // Android Chrome will report duration as Infinity for VOD HLS until after
20801      // playback has started, which triggers the live display erroneously.
20802      // Return NaN if playback has not started and trigger a durationupdate once
20803      // the duration can be reliably known.
20804      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
20805        // Wait for the first `timeupdate` with currentTime > 0 - there may be
20806        // several with 0
20807        const checkProgress = () => {
20808          if (this.el_.currentTime > 0) {
20809            // Trigger durationchange for genuinely live video
20810            if (this.el_.duration === Infinity) {
20811              this.trigger('durationchange');
20812            }
20813            this.off('timeupdate', checkProgress);
20814          }
20815        };
20816        this.on('timeupdate', checkProgress);
20817        return NaN;
20818      }
20819      return this.el_.duration || NaN;
20820    }
20821
20822    /**
20823     * Get the current width of the HTML5 media element.
20824     *
20825     * @return {number}
20826     *         The width of the HTML5 media element.
20827     */
20828    width() {
20829      return this.el_.offsetWidth;
20830    }
20831
20832    /**
20833     * Get the current height of the HTML5 media element.
20834     *
20835     * @return {number}
20836     *         The height of the HTML5 media element.
20837     */
20838    height() {
20839      return this.el_.offsetHeight;
20840    }
20841
20842    /**
20843     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
20844     * `fullscreenchange` event.
20845     *
20846     * @private
20847     * @fires fullscreenchange
20848     * @listens webkitendfullscreen
20849     * @listens webkitbeginfullscreen
20850     * @listens webkitbeginfullscreen
20851     */
20852    proxyWebkitFullscreen_() {
20853      if (!('webkitDisplayingFullscreen' in this.el_)) {
20854        return;
20855      }
20856      const endFn = function () {
20857        this.trigger('fullscreenchange', {
20858          isFullscreen: false
20859        });
20860        // Safari will sometimes set controls on the videoelement when existing fullscreen.
20861        if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
20862          this.el_.controls = false;
20863        }
20864      };
20865      const beginFn = function () {
20866        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
20867          this.one('webkitendfullscreen', endFn);
20868          this.trigger('fullscreenchange', {
20869            isFullscreen: true,
20870            // set a flag in case another tech triggers fullscreenchange
20871            nativeIOSFullscreen: true
20872          });
20873        }
20874      };
20875      this.on('webkitbeginfullscreen', beginFn);
20876      this.on('dispose', () => {
20877        this.off('webkitbeginfullscreen', beginFn);
20878        this.off('webkitendfullscreen', endFn);
20879      });
20880    }
20881
20882    /**
20883     * Check if fullscreen is supported on the video el.
20884     *
20885     * @return {boolean}
20886     *         - True if fullscreen is supported.
20887     *         - False if fullscreen is not supported.
20888     */
20889    supportsFullScreen() {
20890      return typeof this.el_.webkitEnterFullScreen === 'function';
20891    }
20892
20893    /**
20894     * Request that the `HTML5` Tech enter fullscreen.
20895     */
20896    enterFullScreen() {
20897      const video = this.el_;
20898      if (video.paused && video.networkState <= video.HAVE_METADATA) {
20899        // attempt to prime the video element for programmatic access
20900        // this isn't necessary on the desktop but shouldn't hurt
20901        silencePromise(this.el_.play());
20902
20903        // playing and pausing synchronously during the transition to fullscreen
20904        // can get iOS ~6.1 devices into a play/pause loop
20905        this.setTimeout(function () {
20906          video.pause();
20907          try {
20908            video.webkitEnterFullScreen();
20909          } catch (e) {
20910            this.trigger('fullscreenerror', e);
20911          }
20912        }, 0);
20913      } else {
20914        try {
20915          video.webkitEnterFullScreen();
20916        } catch (e) {
20917          this.trigger('fullscreenerror', e);
20918        }
20919      }
20920    }
20921
20922    /**
20923     * Request that the `HTML5` Tech exit fullscreen.
20924     */
20925    exitFullScreen() {
20926      if (!this.el_.webkitDisplayingFullscreen) {
20927        this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
20928        return;
20929      }
20930      this.el_.webkitExitFullScreen();
20931    }
20932
20933    /**
20934     * Create a floating video window always on top of other windows so that users may
20935     * continue consuming media while they interact with other content sites, or
20936     * applications on their device.
20937     *
20938     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
20939     *
20940     * @return {Promise}
20941     *         A promise with a Picture-in-Picture window.
20942     */
20943    requestPictureInPicture() {
20944      return this.el_.requestPictureInPicture();
20945    }
20946
20947    /**
20948     * Native requestVideoFrameCallback if supported by browser/tech, or fallback
20949     * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
20950     * Needs to be checked later than the constructor
20951     * This will be a false positive for clear sources loaded after a Fairplay source
20952     *
20953     * @param {function} cb function to call
20954     * @return {number} id of request
20955     */
20956    requestVideoFrameCallback(cb) {
20957      if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
20958        return this.el_.requestVideoFrameCallback(cb);
20959      }
20960      return super.requestVideoFrameCallback(cb);
20961    }
20962
20963    /**
20964     * Native or fallback requestVideoFrameCallback
20965     *
20966     * @param {number} id request id to cancel
20967     */
20968    cancelVideoFrameCallback(id) {
20969      if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
20970        this.el_.cancelVideoFrameCallback(id);
20971      } else {
20972        super.cancelVideoFrameCallback(id);
20973      }
20974    }
20975
20976    /**
20977     * A getter/setter for the `Html5` Tech's source object.
20978     * > Note: Please use {@link Html5#setSource}
20979     *
20980     * @param {Tech~SourceObject} [src]
20981     *        The source object you want to set on the `HTML5` techs element.
20982     *
20983     * @return {Tech~SourceObject|undefined}
20984     *         - The current source object when a source is not passed in.
20985     *         - undefined when setting
20986     *
20987     * @deprecated Since version 5.
20988     */
20989    src(src) {
20990      if (src === undefined) {
20991        return this.el_.src;
20992      }
20993
20994      // Setting src through `src` instead of `setSrc` will be deprecated
20995      this.setSrc(src);
20996    }
20997
20998    /**
20999     * 
vendor: 11,056 bytes, lines 20999-21351
20999Reset the tech by removing all sources and then calling
21000     * {@link Html5.resetMediaElement}.
21001     */
21002    reset() {
21003      Html5.resetMediaElement(this.el_);
21004    }
21005
21006    /**
21007     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
21008     * the HTML5 media element.
21009     *
21010     * @return {Tech~SourceObject}
21011     *         The current source object from the HTML5 tech. With a fallback to the
21012     *         elements source.
21013     */
21014    currentSrc() {
21015      if (this.currentSource_) {
21016        return this.currentSource_.src;
21017      }
21018      return this.el_.currentSrc;
21019    }
21020
21021    /**
21022     * Set controls attribute for the HTML5 media Element.
21023     *
21024     * @param {string} val
21025     *        Value to set the controls attribute to
21026     */
21027    setControls(val) {
21028      this.el_.controls = !!val;
21029    }
21030
21031    /**
21032     * Create and returns a remote {@link TextTrack} object.
21033     *
21034     * @param {string} kind
21035     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
21036     *
21037     * @param {string} [label]
21038     *        Label to identify the text track
21039     *
21040     * @param {string} [language]
21041     *        Two letter language abbreviation
21042     *
21043     * @return {TextTrack}
21044     *         The TextTrack that gets created.
21045     */
21046    addTextTrack(kind, label, language) {
21047      if (!this.featuresNativeTextTracks) {
21048        return super.addTextTrack(kind, label, language);
21049      }
21050      return this.el_.addTextTrack(kind, label, language);
21051    }
21052
21053    /**
21054     * Creates either native TextTrack or an emulated TextTrack depending
21055     * on the value of `featuresNativeTextTracks`
21056     *
21057     * @param {Object} options
21058     *        The object should contain the options to initialize the TextTrack with.
21059     *
21060     * @param {string} [options.kind]
21061     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
21062     *
21063     * @param {string} [options.label]
21064     *        Label to identify the text track
21065     *
21066     * @param {string} [options.language]
21067     *        Two letter language abbreviation.
21068     *
21069     * @param {boolean} [options.default]
21070     *        Default this track to on.
21071     *
21072     * @param {string} [options.id]
21073     *        The internal id to assign this track.
21074     *
21075     * @param {string} [options.src]
21076     *        A source url for the track.
21077     *
21078     * @return {HTMLTrackElement}
21079     *         The track element that gets created.
21080     */
21081    createRemoteTextTrack(options) {
21082      if (!this.featuresNativeTextTracks) {
21083        return super.createRemoteTextTrack(options);
21084      }
21085      const htmlTrackElement = document.createElement('track');
21086      if (options.kind) {
21087        htmlTrackElement.kind = options.kind;
21088      }
21089      if (options.label) {
21090        htmlTrackElement.label = options.label;
21091      }
21092      if (options.language || options.srclang) {
21093        htmlTrackElement.srclang = options.language || options.srclang;
21094      }
21095      if (options.default) {
21096        htmlTrackElement.default = options.default;
21097      }
21098      if (options.id) {
21099        htmlTrackElement.id = options.id;
21100      }
21101      if (options.src) {
21102        htmlTrackElement.src = options.src;
21103      }
21104      return htmlTrackElement;
21105    }
21106
21107    /**
21108     * Creates a remote text track object and returns an html track element.
21109     *
21110     * @param {Object} options The object should contain values for
21111     * kind, language, label, and src (location of the WebVTT file)
21112     * @param {boolean} [manualCleanup=false] if set to true, the TextTrack
21113     * will not be removed from the TextTrackList and HtmlTrackElementList
21114     * after a source change
21115     * @return {HTMLTrackElement} An Html Track Element.
21116     * This can be an emulated {@link HTMLTrackElement} or a native one.
21117     *
21118     */
21119    addRemoteTextTrack(options, manualCleanup) {
21120      const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
21121      if (this.featuresNativeTextTracks) {
21122        this.el().appendChild(htmlTrackElement);
21123      }
21124      return htmlTrackElement;
21125    }
21126
21127    /**
21128     * Remove remote `TextTrack` from `TextTrackList` object
21129     *
21130     * @param {TextTrack} track
21131     *        `TextTrack` object to remove
21132     */
21133    removeRemoteTextTrack(track) {
21134      super.removeRemoteTextTrack(track);
21135      if (this.featuresNativeTextTracks) {
21136        const tracks = this.$$('track');
21137        let i = tracks.length;
21138        while (i--) {
21139          if (track === tracks[i] || track === tracks[i].track) {
21140            this.el().removeChild(tracks[i]);
21141          }
21142        }
21143      }
21144    }
21145
21146    /**
21147     * Gets available media playback quality metrics as specified by the W3C's Media
21148     * Playback Quality API.
21149     *
21150     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
21151     *
21152     * @return {Object}
21153     *         An object with supported media playback quality metrics
21154     */
21155    getVideoPlaybackQuality() {
21156      if (typeof this.el().getVideoPlaybackQuality === 'function') {
21157        return this.el().getVideoPlaybackQuality();
21158      }
21159      const videoPlaybackQuality = {};
21160      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
21161        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
21162        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
21163      }
21164      if (window.performance) {
21165        videoPlaybackQuality.creationTime = window.performance.now();
21166      }
21167      return videoPlaybackQuality;
21168    }
21169  }
21170
21171  /* HTML5 Support Testing ---------------------------------------------------- */
21172
21173  /**
21174   * Element for testing browser HTML5 media capabilities
21175   *
21176   * @type {Element}
21177   * @constant
21178   * @private
21179   */
21180  defineLazyProperty(Html5, 'TEST_VID', function () {
21181    if (!isReal()) {
21182      return;
21183    }
21184    const video = document.createElement('video');
21185    const track = document.createElement('track');
21186    track.kind = 'captions';
21187    track.srclang = 'en';
21188    track.label = 'English';
21189    video.appendChild(track);
21190    return video;
21191  });
21192
21193  /**
21194   * Check if HTML5 media is supported by this browser/device.
21195   *
21196   * @return {boolean}
21197   *         - True if HTML5 media is supported.
21198   *         - False if HTML5 media is not supported.
21199   */
21200  Html5.isSupported = function () {
21201    // IE with no Media Player is a LIAR! (#984)
21202    try {
21203      Html5.TEST_VID.volume = 0.5;
21204    } catch (e) {
21205      return false;
21206    }
21207    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
21208  };
21209
21210  /**
21211   * Check if the tech can support the given type
21212   *
21213   * @param {string} type
21214   *        The mimetype to check
21215   * @return {string} 'probably', 'maybe', or '' (empty string)
21216   */
21217  Html5.canPlayType = function (type) {
21218    return Html5.TEST_VID.canPlayType(type);
21219  };
21220
21221  /**
21222   * Check if the tech can support the given source
21223   *
21224   * @param {Object} srcObj
21225   *        The source object
21226   * @param {Object} options
21227   *        The options passed to the tech
21228   * @return {string} 'probably', 'maybe', or '' (empty string)
21229   */
21230  Html5.canPlaySource = function (srcObj, options) {
21231    return Html5.canPlayType(srcObj.type);
21232  };
21233
21234  /**
21235   * Check if the volume can be changed in this browser/device.
21236   * Volume cannot be changed in a lot of mobile devices.
21237   * Specifically, it can't be changed from 1 on iOS.
21238   *
21239   * @return {boolean}
21240   *         - True if volume can be controlled
21241   *         - False otherwise
21242   */
21243  Html5.canControlVolume = function () {
21244    // IE will error if Windows Media Player not installed #3315
21245    try {
21246      const volume = Html5.TEST_VID.volume;
21247      Html5.TEST_VID.volume = volume / 2 + 0.1;
21248      const canControl = volume !== Html5.TEST_VID.volume;
21249
21250      // With the introduction of iOS 15, there are cases where the volume is read as
21251      // changed but reverts back to its original state at the start of the next tick.
21252      // To determine whether volume can be controlled on iOS,
21253      // a timeout is set and the volume is checked asynchronously.
21254      // Since `features` doesn't currently work asynchronously, the value is manually set.
21255      if (canControl && IS_IOS) {
21256        window.setTimeout(() => {
21257          if (Html5 && Html5.prototype) {
21258            Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
21259          }
21260        });
21261
21262        // default iOS to false, which will be updated in the timeout above.
21263        return false;
21264      }
21265      return canControl;
21266    } catch (e) {
21267      return false;
21268    }
21269  };
21270
21271  /**
21272   * Check if the volume can be muted in this browser/device.
21273   * Some devices, e.g. iOS, don't allow changing volume
21274   * but permits muting/unmuting.
21275   *
21276   * @return {boolean}
21277   *      - True if volume can be muted
21278   *      - False otherwise
21279   */
21280  Html5.canMuteVolume = function () {
21281    try {
21282      const muted = Html5.TEST_VID.muted;
21283
21284      // in some versions of iOS muted property doesn't always
21285      // work, so we want to set both property and attribute
21286      Html5.TEST_VID.muted = !muted;
21287      if (Html5.TEST_VID.muted) {
21288        setAttribute(Html5.TEST_VID, 'muted', 'muted');
21289      } else {
21290        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
21291      }
21292      return muted !== Html5.TEST_VID.muted;
21293    } catch (e) {
21294      return false;
21295    }
21296  };
21297
21298  /**
21299   * Check if the playback rate can be changed in this browser/device.
21300   *
21301   * @return {boolean}
21302   *         - True if playback rate can be controlled
21303   *         - False otherwise
21304   */
21305  Html5.canControlPlaybackRate = function () {
21306    // Playback rate API is implemented in Android Chrome, but doesn't do anything
21307    // https://github.com/videojs/video.js/issues/3180
21308    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
21309      return false;
21310    }
21311    // IE will error if Windows Media Player not installed #3315
21312    try {
21313      const playbackRate = Html5.TEST_VID.playbackRate;
21314      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
21315      return playbackRate !== Html5.TEST_VID.playbackRate;
21316    } catch (e) {
21317      return false;
21318    }
21319  };
21320
21321  /**
21322   * Check if we can override a video/audio elements attributes, with
21323   * Object.defineProperty.
21324   *
21325   * @return {boolean}
21326   *         - True if builtin attributes can be overridden
21327   *         - False otherwise
21328   */
21329  Html5.canOverrideAttributes = function () {
21330    // if we cannot overwrite the src/innerHTML property, there is no support
21331    // iOS 7 safari for instance cannot do this.
21332    try {
21333      const noop = () => {};
21334      Object.defineProperty(document.createElement('video'), 'src', {
21335        get: noop,
21336        set: noop
21337      });
21338      Object.defineProperty(document.createElement('audio'), 'src', {
21339        get: noop,
21340        set: noop
21341      });
21342      Object.defineProperty(document.createElement('video'), 'innerHTML', {
21343        get: noop,
21344        set: noop
21345      });
21346      Object.defineProperty(document.createElement('audio'), 'innerHTML', {
21347        get: noop,
21348        set: noop
21349      });
21350    } catch (e) {
21351      return false;
vendor: 31,613 bytes, lines 21352-22221
21352    }
21353    return true;
21354  };
21355
21356  /**
21357   * Check to see if native `TextTrack`s are supported by this browser/device.
21358   *
21359   * @return {boolean}
21360   *         - True if native `TextTrack`s are supported.
21361   *         - False otherwise
21362   */
21363  Html5.supportsNativeTextTracks = function () {
21364    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
21365  };
21366
21367  /**
21368   * Check to see if native `VideoTrack`s are supported by this browser/device
21369   *
21370   * @return {boolean}
21371   *        - True if native `VideoTrack`s are supported.
21372   *        - False otherwise
21373   */
21374  Html5.supportsNativeVideoTracks = function () {
21375    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
21376  };
21377
21378  /**
21379   * Check to see if native `AudioTrack`s are supported by this browser/device
21380   *
21381   * @return {boolean}
21382   *        - True if native `AudioTrack`s are supported.
21383   *        - False otherwise
21384   */
21385  Html5.supportsNativeAudioTracks = function () {
21386    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
21387  };
21388
21389  /**
21390   * An array of events available on the Html5 tech.
21391   *
21392   * @private
21393   * @type {Array}
21394   */
21395  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'];
21396
21397  /**
21398   * Boolean indicating whether the `Tech` supports volume control.
21399   *
21400   * @type {boolean}
21401   * @default {@link Html5.canControlVolume}
21402   */
21403  /**
21404   * Boolean indicating whether the `Tech` supports muting volume.
21405   *
21406   * @type {boolean}
21407   * @default {@link Html5.canMuteVolume}
21408   */
21409
21410  /**
21411   * Boolean indicating whether the `Tech` supports changing the speed at which the media
21412   * plays. Examples:
21413   *   - Set player to play 2x (twice) as fast
21414   *   - Set player to play 0.5x (half) as fast
21415   *
21416   * @type {boolean}
21417   * @default {@link Html5.canControlPlaybackRate}
21418   */
21419
21420  /**
21421   * Boolean indicating whether the `Tech` supports the `sourceset` event.
21422   *
21423   * @type {boolean}
21424   * @default
21425   */
21426  /**
21427   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
21428   *
21429   * @type {boolean}
21430   * @default {@link Html5.supportsNativeTextTracks}
21431   */
21432  /**
21433   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
21434   *
21435   * @type {boolean}
21436   * @default {@link Html5.supportsNativeVideoTracks}
21437   */
21438  /**
21439   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
21440   *
21441   * @type {boolean}
21442   * @default {@link Html5.supportsNativeAudioTracks}
21443   */
21444  [['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function ([key, fn]) {
21445    defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
21446  });
21447  Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
21448
21449  /**
21450   * Boolean indicating whether the `HTML5` tech currently supports the media element
21451   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
21452   * false there. Everywhere else this should be true.
21453   *
21454   * @type {boolean}
21455   * @default
21456   */
21457  Html5.prototype.movingMediaElementInDOM = !IS_IOS;
21458
21459  // TODO: Previous comment: No longer appears to be used. Can probably be removed.
21460  //       Is this true?
21461  /**
21462   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
21463   * when going into fullscreen.
21464   *
21465   * @type {boolean}
21466   * @default
21467   */
21468  Html5.prototype.featuresFullscreenResize = true;
21469
21470  /**
21471   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
21472   * If this is false, manual `progress` events will be triggered instead.
21473   *
21474   * @type {boolean}
21475   * @default
21476   */
21477  Html5.prototype.featuresProgressEvents = true;
21478
21479  /**
21480   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
21481   * If this is false, manual `timeupdate` events will be triggered instead.
21482   *
21483   * @default
21484   */
21485  Html5.prototype.featuresTimeupdateEvents = true;
21486
21487  /**
21488   * Whether the HTML5 el supports `requestVideoFrameCallback`
21489   *
21490   * @type {boolean}
21491   */
21492  Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);
21493  Html5.disposeMediaElement = function (el) {
21494    if (!el) {
21495      return;
21496    }
21497    if (el.parentNode) {
21498      el.parentNode.removeChild(el);
21499    }
21500
21501    // remove any child track or source nodes to prevent their loading
21502    while (el.hasChildNodes()) {
21503      el.removeChild(el.firstChild);
21504    }
21505
21506    // remove any src reference. not setting `src=''` because that causes a warning
21507    // in firefox
21508    el.removeAttribute('src');
21509
21510    // force the media element to update its loading state by calling load()
21511    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
21512    if (typeof el.load === 'function') {
21513      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21514      (function () {
21515        try {
21516          el.load();
21517        } catch (e) {
21518          // not supported
21519        }
21520      })();
21521    }
21522  };
21523  Html5.resetMediaElement = function (el) {
21524    if (!el) {
21525      return;
21526    }
21527    const sources = el.querySelectorAll('source');
21528    let i = sources.length;
21529    while (i--) {
21530      el.removeChild(sources[i]);
21531    }
21532
21533    // remove any src reference.
21534    // not setting `src=''` because that throws an error
21535    el.removeAttribute('src');
21536    if (typeof el.load === 'function') {
21537      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21538      (function () {
21539        try {
21540          el.load();
21541        } catch (e) {
21542          // satisfy linter
21543        }
21544      })();
21545    }
21546  };
21547
21548  /* Native HTML5 element property wrapping ----------------------------------- */
21549  // Wrap native boolean attributes with getters that check both property and attribute
21550  // The list is as followed:
21551  // muted, defaultMuted, autoplay, controls, loop, playsinline
21552  [
21553  /**
21554   * Get the value of `muted` from the media element. `muted` indicates
21555   * that the volume for the media should be set to silent. This does not actually change
21556   * the `volume` attribute.
21557   *
21558   * @method Html5#muted
21559   * @return {boolean}
21560   *         - True if the value of `volume` should be ignored and the audio set to silent.
21561   *         - False if the value of `volume` should be used.
21562   *
21563   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21564   */
21565  'muted',
21566  /**
21567   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
21568   * whether the media should start muted or not. Only changes the default state of the
21569   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
21570   * current state.
21571   *
21572   * @method Html5#defaultMuted
21573   * @return {boolean}
21574   *         - The value of `defaultMuted` from the media element.
21575   *         - True indicates that the media should start muted.
21576   *         - False indicates that the media should not start muted
21577   *
21578   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21579   */
21580  'defaultMuted',
21581  /**
21582   * Get the value of `autoplay` from the media element. `autoplay` indicates
21583   * that the media should start to play as soon as the page is ready.
21584   *
21585   * @method Html5#autoplay
21586   * @return {boolean}
21587   *         - The value of `autoplay` from the media element.
21588   *         - True indicates that the media should start as soon as the page loads.
21589   *         - False indicates that the media should not start as soon as the page loads.
21590   *
21591   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21592   */
21593  'autoplay',
21594  /**
21595   * Get the value of `controls` from the media element. `controls` indicates
21596   * whether the native media controls should be shown or hidden.
21597   *
21598   * @method Html5#controls
21599   * @return {boolean}
21600   *         - The value of `controls` from the media element.
21601   *         - True indicates that native controls should be showing.
21602   *         - False indicates that native controls should be hidden.
21603   *
21604   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
21605   */
21606  'controls',
21607  /**
21608   * Get the value of `loop` from the media element. `loop` indicates
21609   * that the media should return to the start of the media and continue playing once
21610   * it reaches the end.
21611   *
21612   * @method Html5#loop
21613   * @return {boolean}
21614   *         - The value of `loop` from the media element.
21615   *         - True indicates that playback should seek back to start once
21616   *           the end of a media is reached.
21617   *         - False indicates that playback should not loop back to the start when the
21618   *           end of the media is reached.
21619   *
21620   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21621   */
21622  'loop',
21623  /**
21624   * Get the value of `playsinline` from the media element. `playsinline` indicates
21625   * to the browser that non-fullscreen playback is preferred when fullscreen
21626   * playback is the native default, such as in iOS Safari.
21627   *
21628   * @method Html5#playsinline
21629   * @return {boolean}
21630   *         - The value of `playsinline` from the media element.
21631   *         - True indicates that the media should play inline.
21632   *         - False indicates that the media should not play inline.
21633   *
21634   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21635   */
21636  'playsinline'].forEach(function (prop) {
21637    Html5.prototype[prop] = function () {
21638      return this.el_[prop] || this.el_.hasAttribute(prop);
21639    };
21640  });
21641
21642  // Wrap native boolean attributes with setters that set both property and attribute
21643  // The list is as followed:
21644  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
21645  // setControls is special-cased above
21646  [
21647  /**
21648   * Set the value of `muted` on the media element. `muted` indicates that the current
21649   * audio level should be silent.
21650   *
21651   * @method Html5#setMuted
21652   * @param {boolean} muted
21653   *        - True if the audio should be set to silent
21654   *        - False otherwise
21655   *
21656   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21657   */
21658  'muted',
21659  /**
21660   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
21661   * audio level should be silent, but will only effect the muted level on initial playback..
21662   *
21663   * @method Html5.prototype.setDefaultMuted
21664   * @param {boolean} defaultMuted
21665   *        - True if the audio should be set to silent
21666   *        - False otherwise
21667   *
21668   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21669   */
21670  'defaultMuted',
21671  /**
21672   * Set the value of `autoplay` on the media element. `autoplay` indicates
21673   * that the media should start to play as soon as the page is ready.
21674   *
21675   * @method Html5#setAutoplay
21676   * @param {boolean} autoplay
21677   *         - True indicates that the media should start as soon as the page loads.
21678   *         - False indicates that the media should not start as soon as the page loads.
21679   *
21680   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21681   */
21682  'autoplay',
21683  /**
21684   * Set the value of `loop` on the media element. `loop` indicates
21685   * that the media should return to the start of the media and continue playing once
21686   * it reaches the end.
21687   *
21688   * @method Html5#setLoop
21689   * @param {boolean} loop
21690   *         - True indicates that playback should seek back to start once
21691   *           the end of a media is reached.
21692   *         - False indicates that playback should not loop back to the start when the
21693   *           end of the media is reached.
21694   *
21695   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21696   */
21697  'loop',
21698  /**
21699   * Set the value of `playsinline` from the media element. `playsinline` indicates
21700   * to the browser that non-fullscreen playback is preferred when fullscreen
21701   * playback is the native default, such as in iOS Safari.
21702   *
21703   * @method Html5#setPlaysinline
21704   * @param {boolean} playsinline
21705   *         - True indicates that the media should play inline.
21706   *         - False indicates that the media should not play inline.
21707   *
21708   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21709   */
21710  'playsinline'].forEach(function (prop) {
21711    Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
21712      this.el_[prop] = v;
21713      if (v) {
21714        this.el_.setAttribute(prop, prop);
21715      } else {
21716        this.el_.removeAttribute(prop);
21717      }
21718    };
21719  });
21720
21721  // Wrap native properties with a getter
21722  // The list is as followed
21723  // paused, currentTime, buffered, volume, poster, preload, error, seeking
21724  // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
21725  // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
21726  [
21727  /**
21728   * Get the value of `paused` from the media element. `paused` indicates whether the media element
21729   * is currently paused or not.
21730   *
21731   * @method Html5#paused
21732   * @return {boolean}
21733   *         The value of `paused` from the media element.
21734   *
21735   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
21736   */
21737  'paused',
21738  /**
21739   * Get the value of `currentTime` from the media element. `currentTime` indicates
21740   * the current second that the media is at in playback.
21741   *
21742   * @method Html5#currentTime
21743   * @return {number}
21744   *         The value of `currentTime` from the media element.
21745   *
21746   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
21747   */
21748  'currentTime',
21749  /**
21750   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
21751   * object that represents the parts of the media that are already downloaded and
21752   * available for playback.
21753   *
21754   * @method Html5#buffered
21755   * @return {TimeRange}
21756   *         The value of `buffered` from the media element.
21757   *
21758   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
21759   */
21760  'buffered',
21761  /**
21762   * Get the value of `volume` from the media element. `volume` indicates
21763   * the current playback volume of audio for a media. `volume` will be a value from 0
21764   * (silent) to 1 (loudest and default).
21765   *
21766   * @method Html5#volume
21767   * @return {number}
21768   *         The value of `volume` from the media element. Value will be between 0-1.
21769   *
21770   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21771   */
21772  'volume',
21773  /**
21774   * Get the value of `poster` from the media element. `poster` indicates
21775   * that the url of an image file that can/will be shown when no media data is available.
21776   *
21777   * @method Html5#poster
21778   * @return {string}
21779   *         The value of `poster` from the media element. Value will be a url to an
21780   *         image.
21781   *
21782   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
21783   */
21784  'poster',
21785  /**
21786   * Get the value of `preload` from the media element. `preload` indicates
21787   * what should download before the media is interacted with. It can have the following
21788   * values:
21789   * - none: nothing should be downloaded
21790   * - metadata: poster and the first few frames of the media may be downloaded to get
21791   *   media dimensions and other metadata
21792   * - auto: allow the media and metadata for the media to be downloaded before
21793   *    interaction
21794   *
21795   * @method Html5#preload
21796   * @return {string}
21797   *         The value of `preload` from the media element. Will be 'none', 'metadata',
21798   *         or 'auto'.
21799   *
21800   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21801   */
21802  'preload',
21803  /**
21804   * Get the value of the `error` from the media element. `error` indicates any
21805   * MediaError that may have occurred during playback. If error returns null there is no
21806   * current error.
21807   *
21808   * @method Html5#error
21809   * @return {MediaError|null}
21810   *         The value of `error` from the media element. Will be `MediaError` if there
21811   *         is a current error and null otherwise.
21812   *
21813   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
21814   */
21815  'error',
21816  /**
21817   * Get the value of `seeking` from the media element. `seeking` indicates whether the
21818   * media is currently seeking to a new position or not.
21819   *
21820   * @method Html5#seeking
21821   * @return {boolean}
21822   *         - The value of `seeking` from the media element.
21823   *         - True indicates that the media is currently seeking to a new position.
21824   *         - False indicates that the media is not seeking to a new position at this time.
21825   *
21826   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
21827   */
21828  'seeking',
21829  /**
21830   * Get the value of `seekable` from the media element. `seekable` returns a
21831   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
21832   *
21833   * @method Html5#seekable
21834   * @return {TimeRange}
21835   *         The value of `seekable` from the media element. A `TimeRange` object
21836   *         indicating the current ranges of time that can be seeked to.
21837   *
21838   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
21839   */
21840  'seekable',
21841  /**
21842   * Get the value of `ended` from the media element. `ended` indicates whether
21843   * the media has reached the end or not.
21844   *
21845   * @method Html5#ended
21846   * @return {boolean}
21847   *         - The value of `ended` from the media element.
21848   *         - True indicates that the media has ended.
21849   *         - False indicates that the media has not ended.
21850   *
21851   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
21852   */
21853  'ended',
21854  /**
21855   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
21856   * the rate at which the media is currently playing back. Examples:
21857   *   - if playbackRate is set to 2, media will play twice as fast.
21858   *   - if playbackRate is set to 0.5, media will play half as fast.
21859   *
21860   * @method Html5#playbackRate
21861   * @return {number}
21862   *         The value of `playbackRate` from the media element. A number indicating
21863   *         the current playback speed of the media, where 1 is normal speed.
21864   *
21865   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21866   */
21867  'playbackRate',
21868  /**
21869   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
21870   * the rate at which the media is currently playing back. This value will not indicate the current
21871   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
21872   *
21873   * Examples:
21874   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
21875   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
21876   *
21877   * @method Html5.prototype.defaultPlaybackRate
21878   * @return {number}
21879   *         The value of `defaultPlaybackRate` from the media element. A number indicating
21880   *         the current playback speed of the media, where 1 is normal speed.
21881   *
21882   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21883   */
21884  'defaultPlaybackRate',
21885  /**
21886   * Get the value of 'disablePictureInPicture' from the video element.
21887   *
21888   * @method Html5#disablePictureInPicture
21889   * @return {boolean} value
21890   *         - The value of `disablePictureInPicture` from the video element.
21891   *         - True indicates that the video can't be played in Picture-In-Picture mode
21892   *         - False indicates that the video can be played in Picture-In-Picture mode
21893   *
21894   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
21895   */
21896  'disablePictureInPicture',
21897  /**
21898   * Get the value of `played` from the media element. `played` returns a `TimeRange`
21899   * object representing points in the media timeline that have been played.
21900   *
21901   * @method Html5#played
21902   * @return {TimeRange}
21903   *         The value of `played` from the media element. A `TimeRange` object indicating
21904   *         the ranges of time that have been played.
21905   *
21906   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
21907   */
21908  'played',
21909  /**
21910   * Get the value of `networkState` from the media element. `networkState` indicates
21911   * the current network state. It returns an enumeration from the following list:
21912   * - 0: NETWORK_EMPTY
21913   * - 1: NETWORK_IDLE
21914   * - 2: NETWORK_LOADING
21915   * - 3: NETWORK_NO_SOURCE
21916   *
21917   * @method Html5#networkState
21918   * @return {number}
21919   *         The value of `networkState` from the media element. This will be a number
21920   *         from the list in the description.
21921   *
21922   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
21923   */
21924  'networkState',
21925  /**
21926   * Get the value of `readyState` from the media element. `readyState` indicates
21927   * the current state of the media element. It returns an enumeration from the
21928   * following list:
21929   * - 0: HAVE_NOTHING
21930   * - 1: HAVE_METADATA
21931   * - 2: HAVE_CURRENT_DATA
21932   * - 3: HAVE_FUTURE_DATA
21933   * - 4: HAVE_ENOUGH_DATA
21934   *
21935   * @method Html5#readyState
21936   * @return {number}
21937   *         The value of `readyState` from the media element. This will be a number
21938   *         from the list in the description.
21939   *
21940   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
21941   */
21942  'readyState',
21943  /**
21944   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
21945   * the current width of the video in css pixels.
21946   *
21947   * @method Html5#videoWidth
21948   * @return {number}
21949   *         The value of `videoWidth` from the video element. This will be a number
21950   *         in css pixels.
21951   *
21952   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21953   */
21954  'videoWidth',
21955  /**
21956   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
21957   * the current height of the video in css pixels.
21958   *
21959   * @method Html5#videoHeight
21960   * @return {number}
21961   *         The value of `videoHeight` from the video element. This will be a number
21962   *         in css pixels.
21963   *
21964   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21965   */
21966  'videoHeight',
21967  /**
21968   * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
21969   * to the browser that should sent the cookies along with the requests for the
21970   * different assets/playlists
21971   *
21972   * @method Html5#crossOrigin
21973   * @return {string}
21974   *         - anonymous indicates that the media should not sent cookies.
21975   *         - use-credentials indicates that the media should sent cookies along the requests.
21976   *
21977   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
21978   */
21979  'crossOrigin'].forEach(function (prop) {
21980    Html5.prototype[prop] = function () {
21981      return this.el_[prop];
21982    };
21983  });
21984
21985  // Wrap native properties with a setter in this format:
21986  // set + toTitleCase(name)
21987  // The list is as follows:
21988  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
21989  // setDisablePictureInPicture, setCrossOrigin
21990  [
21991  /**
21992   * Set the value of `volume` on the media element. `volume` indicates the current
21993   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
21994   * so on.
21995   *
21996   * @method Html5#setVolume
21997   * @param {number} percentAsDecimal
21998   *        The volume percent as a decimal. Valid range is from 0-1.
21999   *
22000   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22001   */
22002  'volume',
22003  /**
22004   * Set the value of `src` on the media element. `src` indicates the current
22005   * {@link Tech~SourceObject} for the media.
22006   *
22007   * @method Html5#setSrc
22008   * @param {Tech~SourceObject} src
22009   *        The source object to set as the current source.
22010   *
22011   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
22012   */
22013  'src',
22014  /**
22015   * Set the value of `poster` on the media element. `poster` is the url to
22016   * an image file that can/will be shown when no media data is available.
22017   *
22018   * @method Html5#setPoster
22019   * @param {string} poster
22020   *        The url to an image that should be used as the `poster` for the media
22021   *        element.
22022   *
22023   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
22024   */
22025  'poster',
22026  /**
22027   * Set the value of `preload` on the media element. `preload` indicates
22028   * what should download before the media is interacted with. It can have the following
22029   * values:
22030   * - none: nothing should be downloaded
22031   * - metadata: poster and the first few frames of the media may be downloaded to get
22032   *   media dimensions and other metadata
22033   * - auto: allow the media and metadata for the media to be downloaded before
22034   *    interaction
22035   *
22036   * @method Html5#setPreload
22037   * @param {string} preload
22038   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
22039   *         or 'auto'.
22040   *
22041   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22042   */
22043  'preload',
22044  /**
22045   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
22046   * the rate at which the media should play back. Examples:
22047   *   - if playbackRate is set to 2, media will play twice as fast.
22048   *   - if playbackRate is set to 0.5, media will play half as fast.
22049   *
22050   * @method Html5#setPlaybackRate
22051   * @return {number}
22052   *         The value of `playbackRate` from the media element. A number indicating
22053   *         the current playback speed of the media, where 1 is normal speed.
22054   *
22055   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22056   */
22057  'playbackRate',
22058  /**
22059   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
22060   * the rate at which the media should play back upon initial startup. Changing this value
22061   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
22062   *
22063   * Example Values:
22064   *   - if playbackRate is set to 2, media will play twice as fast.
22065   *   - if playbackRate is set to 0.5, media will play half as fast.
22066   *
22067   * @method Html5.prototype.setDefaultPlaybackRate
22068   * @return {number}
22069   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22070   *         the current playback speed of the media, where 1 is normal speed.
22071   *
22072   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
22073   */
22074  'defaultPlaybackRate',
22075  /**
22076   * Prevents the browser from suggesting a Picture-in-Picture context menu
22077   * or to request Picture-in-Picture automatically in some cases.
22078   *
22079   * @method Html5#setDisablePictureInPicture
22080   * @param {boolean} value
22081   *         The true value will disable Picture-in-Picture mode.
22082   *
22083   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
22084   */
22085  'disablePictureInPicture',
22086  /**
22087   * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
22088   * to the browser that should sent the cookies along with the requests for the
22089   * different assets/playlists
22090   *
22091   * @method Html5#setCrossOrigin
22092   * @param {string} crossOrigin
22093   *         - anonymous indicates that the media should not sent cookies.
22094   *         - use-credentials indicates that the media should sent cookies along the requests.
22095   *
22096   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
22097   */
22098  'crossOrigin'].forEach(function (prop) {
22099    Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
22100      this.el_[prop] = v;
22101    };
22102  });
22103
22104  // wrap native functions with a function
22105  // The list is as follows:
22106  // pause, load, play
22107  [
22108  /**
22109   * A wrapper around the media elements `pause` function. This will call the `HTML5`
22110   * media elements `pause` function.
22111   *
22112   * @method Html5#pause
22113   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
22114   */
22115  'pause',
22116  /**
22117   * A wrapper around the media elements `load` function. This will call the `HTML5`s
22118   * media element `load` function.
22119   *
22120   * @method Html5#load
22121   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
22122   */
22123  'load',
22124  /**
22125   * A wrapper around the media elements `play` function. This will call the `HTML5`s
22126   * media element `play` function.
22127   *
22128   * @method Html5#play
22129   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
22130   */
22131  'play'].forEach(function (prop) {
22132    Html5.prototype[prop] = function () {
22133      return this.el_[prop]();
22134    };
22135  });
22136  Tech.withSourceHandlers(Html5);
22137
22138  /**
22139   * Native source handler for Html5, simply passes the source to the media element.
22140   *
22141   * @property {Tech~SourceObject} source
22142   *        The source object
22143   *
22144   * @property {Html5} tech
22145   *        The instance of the HTML5 tech.
22146   */
22147  Html5.nativeSourceHandler = {};
22148
22149  /**
22150   * Check if the media element can play the given mime type.
22151   *
22152   * @param {string} type
22153   *        The mimetype to check
22154   *
22155   * @return {string}
22156   *         'probably', 'maybe', or '' (empty string)
22157   */
22158  Html5.nativeSourceHandler.canPlayType = function (type) {
22159    // IE without MediaPlayer throws an error (#519)
22160    try {
22161      return Html5.TEST_VID.canPlayType(type);
22162    } catch (e) {
22163      return '';
22164    }
22165  };
22166
22167  /**
22168   * Check if the media element can handle a source natively.
22169   *
22170   * @param {Tech~SourceObject} source
22171   *         The source object
22172   *
22173   * @param {Object} [options]
22174   *         Options to be passed to the tech.
22175   *
22176   * @return {string}
22177   *         'probably', 'maybe', or '' (empty string).
22178   */
22179  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
22180    // If a type was provided we should rely on that
22181    if (source.type) {
22182      return Html5.nativeSourceHandler.canPlayType(source.type);
22183
22184      // If no type, fall back to checking 'video/[EXTENSION]'
22185    } else if (source.src) {
22186      const ext = getFileExtension(source.src);
22187      return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
22188    }
22189    return '';
22190  };
22191
22192  /**
22193   * Pass the source to the native media element.
22194   *
22195   * @param {Tech~SourceObject} source
22196   *        The source object
22197   *
22198   * @param {Html5} tech
22199   *        The instance of the Html5 tech
22200   *
22201   * @param {Object} [options]
22202   *        The options to pass to the source
22203   */
22204  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
22205    tech.setSrc(source.src);
22206  };
22207
22208  /**
22209   * A noop for the native dispose function, as cleanup is not needed.
22210   */
22211  Html5.nativeSourceHandler.dispose = function () {};
22212
22213  // Register the native source handler
22214  Html5.registerSourceHandler(Html5.nativeSourceHandler);
22215  Tech.registerTech('Html5', Html5);
22216
22217  /**
22218   * @file player.js
22219   */
22220
22221  /
22221/ The following tech events are simply re-triggered
22222  // on the player when they happen
22223  const TECH_EVENTS_RETRIGGER = [
22224  /**
22225   * Fired while the user agent is downloading media data.
22226   *
22227   * @event Player#progress
22228   * @type {Event}
22229   */
22230  /**
22231   * Retrigger the `progress` event that was triggered by the {@link Tech}.
22232   *
22233   * @private
22234   * @method Player#handleTechProgress_
22235   * @fires Player#progress
22236   * @listens Tech#progress
22237   */
22238  'progress',
22239  /**
22240   * Fires when the loading of an audio/video is aborted.
22241   *
22242   * @event Player#abort
22243   * @type {Event}
22244   */
22245  /**
22246   * Retrigger the `abort` event that was triggered by the {@link Tech}.
22247   *
22248   * @private
22249   * @method Player#handleTechAbort_
22250   * @fires Player#abort
22251   * @listens Tech#abort
22252   */
22253  'abort',
22254  /**
22255   * Fires when the browser is intentionally not getting media data.
22256   *
22257   * @event Player#suspend
22258   * @type {Event}
22259   */
22260  /**
22261   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
22262   *
22263   * @private
22264   * @method Player#handleTechSuspend_
22265   * @fires Player#suspend
22266   * @listens Tech#suspend
22267   */
22268  'suspend',
22269  /**
22270   * Fires when the current playlist is empty.
22271   *
22272   * @event Player#emptied
22273   * @type {Event}
22274   */
22275  /**
22276   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
22277   *
22278   * @private
22279   * @method Player#handleTechEmptied_
22280   * @fires Player#emptied
22281   * @listens Tech#emptied
22282   */
22283  'emptied',
22284  /**
22285   * Fires when the browser is trying to get media data, but data is not available.
22286   *
22287   * @event Player#stalled
22288   * @type {Event}
22289   */
22290  /**
22291   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
22292   *
22293   * @private
22294   * @method Player#handleTechStalled_
22295   * @fires Player#stalled
22296   * @listens Tech#stalled
22297   */
22298  'stalled',
22299  /**
22300   * Fires when the browser has loaded meta data for the audio/video.
22301   *
22302   * @event Player#loadedmetadata
22303   * @type {Event}
22304   */
22305  /**
22306   * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
22307   *
22308   * @private
22309   * @method Player#handleTechLoadedmetadata_
22310   * @fires Player#loadedmetadata
22311   * @listens Tech#loadedmetadata
22312   */
22313  'loadedmetadata',
22314  /**
22315   * Fires when the browser has loaded the current frame of the audio/video.
22316   *
22317   * @event Player#loadeddata
22318   * @type {event}
22319   */
22320  /**
22321   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
22322   *
22323   * @private
22324   * @method Player#handleTechLoaddeddata_
22325   * @fires Player#loadeddata
22326   * @listens Tech#loadeddata
22327   */
22328  'loadeddata',
22329  /**
22330   * Fires when the current playback position has changed.
22331   *
22332   * @event Player#timeupdate
22333   * @type {event}
22334   */
22335  /**
22336   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
22337   *
22338   * @private
22339   * @method Player#handleTechTimeUpdate_
22340   * @fires Player#timeupdate
22341   * @listens Tech#timeupdate
22342   */
22343  'timeupdate',
22344  /**
22345   * Fires when the video's intrinsic dimensions change
22346   *
22347   * @event Player#resize
22348   * @type {event}
22349   */
22350  /**
22351   * Retrigger the `resize` event that was triggered by the {@link Tech}.
22352   *
22353   * @private
22354   * @method Player#handleTechResize_
22355   * @fires Player#resize
22356   * @listens Tech#resize
22357   */
22358  'resize',
22359  /**
22360   * Fires when the volume has been changed
22361   *
22362   * @event Player#volumechange
22363   * @type {event}
22364   */
22365  /**
22366   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
22367   *
22368   * @private
22369   * @method Player#handleTechVolumechange_
22370   * @fires Player#volumechange
22371   * @listens Tech#volumechange
22372   */
22373  'volumechange',
22374  /**
22375   * Fires when the text track has been changed
22376   *
22377   * @event Player#texttrackchange
22378   * @type {event}
22379   */
22380  /**
22381   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
22382   *
22383   * @private
22384   * @method Player#handleTechTexttrackchange_
22385   * @fires Player#texttrackchange
22386   * @listens Tech#texttrackchange
22387   */
22388  'texttrackchange'];
22389
22390  // events to queue when playback rate is zero
22391  // this is a hash for the sole purpose of mapping non-camel-cased event names
22392  // to camel-cased function names
22393  const TECH_EVENTS_QUEUE = {
22394    canplay: 'CanPlay',
22395    canplaythrough: 'CanPlayThrough',
22396    playing: 'Playing',
22397    seeked: 'Seeked'
22398  };
22399  const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
22400  const BREAKPOINT_CLASSES = {};
22401
22402  // grep: vjs-layout-tiny
22403  // grep: vjs-layout-x-small
22404  // grep: vjs-layout-small
22405  // grep: vjs-layout-medium
22406  // grep: vjs-layout-large
22407  // grep: vjs-layout-x-large
22408  // grep: vjs-layout-huge
22409  BREAKPOINT_ORDER.forEach(k => {
22410    const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
22411    BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
22412  });
22413  const DEFAULT_BREAKPOINTS = {
22414    tiny: 210,
22415    xsmall: 320,
22416    small: 425,
22417    medium: 768,
22418    large: 1440,
22419    xlarge: 2560,
22420    huge: Infinity
22421  };
22422
22423  /**
22424   * An instance of the `Player` class is created when any of the Video.js setup methods
22425   * are used to initialize a video.
22426   *
22427   * After an instance has been created it can be accessed globally in three ways:
22428   * 1. By calling `videojs.getPlayer('example_video_1');`
22429   * 2. By calling `videojs('example_video_1');` (not recommended)
22430   * 2. By using it directly via `videojs.players.example_video_1;`
22431   *
22432   * @extends Component
22433   * @global
22434   */
22435  class Player extends Component$1 {
22436    /**
22437     * Create an instance of this class.
22438     *
22439     * @param {Element} tag
22440     *        The original video DOM element used for configuring options.
22441     *
22442     * @param {Object} [options]
22443     *        Object of option names and values.
22444     *
22445     * @param {Function} [ready]
22446     *        Ready callback function.
22447     */
22448    constructor(tag, options, ready) {
22449      // Make sure tag ID exists
22450      // also here.. probably better
22451      tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
22452
22453      // Set Options
22454      // The options argument overrides options set in the video tag
22455      // which overrides globally set options.
22456      // This latter part coincides with the load order
22457      // (tag must exist before Player)
22458      options = Object.assign(Player.getTagSettings(tag), options);
22459
22460      // Delay the initialization of children because we need to set up
22461      // player properties first, and can't use `this` before `super()`
22462      options.initChildren = false;
22463
22464      // Same with creating the element
22465      options.createEl = false;
22466
22467      // don't auto mixin the evented mixin
22468      options.evented = false;
22469
22470      // we don't want the player to report touch activity on itself
22471      // see enableTouchActivity in Component
22472      options.reportTouchActivity = false;
22473
22474      // If language is not set, get the closest lang attribute
22475      if (!options.language) {
22476        const closest = tag.closest('[lang]');
22477        if (closest) {
22478          options.language = closest.getAttribute('lang');
22479        }
22480      }
22481
22482      // Run base component initializing with new options
22483      super(null, options, ready);
22484
22485      // Create bound methods for document listeners.
22486      this.boundDocumentFullscreenChange_ = e => this.documentFullscreenChange_(e);
22487      this.boundFullWindowOnEscKey_ = e => this.fullWindowOnEscKey(e);
22488      this.boundUpdateStyleEl_ = e => this.updateStyleEl_(e);
22489      this.boundApplyInitTime_ = e => this.applyInitTime_(e);
22490      this.boundUpdateCurrentBreakpoint_ = e => this.updateCurrentBreakpoint_(e);
22491      this.boundHandleTechClick_ = e => this.handleTechClick_(e);
22492      this.boundHandleTechDoubleClick_ = e => this.handleTechDoubleClick_(e);
22493      this.boundHandleTechTouchStart_ = e => this.handleTechTouchStart_(e);
22494      this.boundHandleTechTouchMove_ = e => this.handleTechTouchMove_(e);
22495      this.boundHandleTechTouchEnd_ = e => this.handleTechTouchEnd_(e);
22496      this.boundHandleTechTap_ = e => this.handleTechTap_(e);
22497
22498      // default isFullscreen_ to false
22499      this.isFullscreen_ = false;
vendor: 13,558 bytes, lines 22500-22881
22500
22501      // create logger
22502      this.log = createLogger(this.id_);
22503
22504      // Hold our own reference to fullscreen api so it can be mocked in tests
22505      this.fsApi_ = FullscreenApi;
22506
22507      // Tracks when a tech changes the poster
22508      this.isPosterFromTech_ = false;
22509
22510      // Holds callback info that gets queued when playback rate is zero
22511      // and a seek is happening
22512      this.queuedCallbacks_ = [];
22513
22514      // Turn off API access because we're loading a new tech that might load asynchronously
22515      this.isReady_ = false;
22516
22517      // Init state hasStarted_
22518      this.hasStarted_ = false;
22519
22520      // Init state userActive_
22521      this.userActive_ = false;
22522
22523      // Init debugEnabled_
22524      this.debugEnabled_ = false;
22525
22526      // Init state audioOnlyMode_
22527      this.audioOnlyMode_ = false;
22528
22529      // Init state audioPosterMode_
22530      this.audioPosterMode_ = false;
22531
22532      // Init state audioOnlyCache_
22533      this.audioOnlyCache_ = {
22534        playerHeight: null,
22535        hiddenChildren: []
22536      };
22537
22538      // if the global option object was accidentally blown away by
22539      // someone, bail early with an informative error
22540      if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
22541        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
22542      }
22543
22544      // Store the original tag used to set options
22545      this.tag = tag;
22546
22547      // Store the tag attributes used to restore html5 element
22548      this.tagAttributes = tag && getAttributes(tag);
22549
22550      // Update current language
22551      this.language(this.options_.language);
22552
22553      // Update Supported Languages
22554      if (options.languages) {
22555        // Normalise player option languages to lowercase
22556        const languagesToLower = {};
22557        Object.getOwnPropertyNames(options.languages).forEach(function (name) {
22558          languagesToLower[name.toLowerCase()] = options.languages[name];
22559        });
22560        this.languages_ = languagesToLower;
22561      } else {
22562        this.languages_ = Player.prototype.options_.languages;
22563      }
22564      this.resetCache_();
22565
22566      // Set poster
22567      /** @type string */
22568      this.poster_ = options.poster || '';
22569
22570      // Set controls
22571      /** @type {boolean} */
22572      this.controls_ = !!options.controls;
22573
22574      // Original tag settings stored in options
22575      // now remove immediately so native controls don't flash.
22576      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
22577      tag.controls = false;
22578      tag.removeAttribute('controls');
22579      this.changingSrc_ = false;
22580      this.playCallbacks_ = [];
22581      this.playTerminatedQueue_ = [];
22582
22583      // the attribute overrides the option
22584      if (tag.hasAttribute('autoplay')) {
22585        this.autoplay(true);
22586      } else {
22587        // otherwise use the setter to validate and
22588        // set the correct value.
22589        this.autoplay(this.options_.autoplay);
22590      }
22591
22592      // check plugins
22593      if (options.plugins) {
22594        Object.keys(options.plugins).forEach(name => {
22595          if (typeof this[name] !== 'function') {
22596            throw new Error(`plugin "${name}" does not exist`);
22597          }
22598        });
22599      }
22600
22601      /*
22602       * Store the internal state of scrubbing
22603       *
22604       * @private
22605       * @return {Boolean} True if the user is scrubbing
22606       */
22607      this.scrubbing_ = false;
22608      this.el_ = this.createEl();
22609
22610      // Make this an evented object and use `el_` as its event bus.
22611      evented(this, {
22612        eventBusKey: 'el_'
22613      });
22614
22615      // listen to document and player fullscreenchange handlers so we receive those events
22616      // before a user can receive them so we can update isFullscreen appropriately.
22617      // make sure that we listen to fullscreenchange events before everything else to make sure that
22618      // our isFullscreen method is updated properly for internal components as well as external.
22619      if (this.fsApi_.requestFullscreen) {
22620        on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22621        this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22622      }
22623      if (this.fluid_) {
22624        this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
22625      }
22626      // We also want to pass the original player options to each component and plugin
22627      // as well so they don't need to reach back into the player for options later.
22628      // We also need to do another copy of this.options_ so we don't end up with
22629      // an infinite loop.
22630      const playerOptionsCopy = merge$2(this.options_);
22631
22632      // Load plugins
22633      if (options.plugins) {
22634        Object.keys(options.plugins).forEach(name => {
22635          this[name](options.plugins[name]);
22636        });
22637      }
22638
22639      // Enable debug mode to fire debugon event for all plugins.
22640      if (options.debug) {
22641        this.debug(true);
22642      }
22643      this.options_.playerOptions = playerOptionsCopy;
22644      this.middleware_ = [];
22645      this.playbackRates(options.playbackRates);
22646      if (options.experimentalSvgIcons) {
22647        // Add SVG Sprite to the DOM
22648        const parser = new window.DOMParser();
22649        const parsedSVG = parser.parseFromString(icons, 'image/svg+xml');
22650        const errorNode = parsedSVG.querySelector('parsererror');
22651        if (errorNode) {
22652          log$1.warn('Failed to load SVG Icons. Falling back to Font Icons.');
22653          this.options_.experimentalSvgIcons = null;
22654        } else {
22655          const sprite = parsedSVG.documentElement;
22656          sprite.style.display = 'none';
22657          this.el_.appendChild(sprite);
22658          this.addClass('vjs-svg-icons-enabled');
22659        }
22660      }
22661      this.initChildren();
22662
22663      // Set isAudio based on whether or not an audio tag was used
22664      this.isAudio(tag.nodeName.toLowerCase() === 'audio');
22665
22666      // Update controls className. Can't do this when the controls are initially
22667      // set because the element doesn't exist yet.
22668      if (this.controls()) {
22669        this.addClass('vjs-controls-enabled');
22670      } else {
22671        this.addClass('vjs-controls-disabled');
22672      }
22673
22674      // Set ARIA label and region role depending on player type
22675      this.el_.setAttribute('role', 'region');
22676      if (this.isAudio()) {
22677        this.el_.setAttribute('aria-label', this.localize('Audio Player'));
22678      } else {
22679        this.el_.setAttribute('aria-label', this.localize('Video Player'));
22680      }
22681      if (this.isAudio()) {
22682        this.addClass('vjs-audio');
22683      }
22684
22685      // TODO: Make this smarter. Toggle user state between touching/mousing
22686      // using events, since devices can have both touch and mouse events.
22687      // TODO: Make this check be performed again when the window switches between monitors
22688      // (See https://github.com/videojs/video.js/issues/5683)
22689      if (TOUCH_ENABLED) {
22690        this.addClass('vjs-touch-enabled');
22691      }
22692
22693      // iOS Safari has broken hover handling
22694      if (!IS_IOS) {
22695        this.addClass('vjs-workinghover');
22696      }
22697
22698      // Make player easily findable by ID
22699      Player.players[this.id_] = this;
22700
22701      // Add a major version class to aid css in plugins
22702      const majorVersion = version$5.split('.')[0];
22703      this.addClass(`vjs-v${majorVersion}`);
22704
22705      // When the player is first initialized, trigger activity so components
22706      // like the control bar show themselves if needed
22707      this.userActive(true);
22708      this.reportUserActivity();
22709      this.one('play', e => this.listenForUserActivity_(e));
22710      this.on('keydown', e => this.handleKeyDown(e));
22711      this.on('languagechange', e => this.handleLanguagechange(e));
22712      this.breakpoints(this.options_.breakpoints);
22713      this.responsive(this.options_.responsive);
22714
22715      // Calling both the audio mode methods after the player is fully
22716      // setup to be able to listen to the events triggered by them
22717      this.on('ready', () => {
22718        // Calling the audioPosterMode method first so that
22719        // the audioOnlyMode can take precedence when both options are set to true
22720        this.audioPosterMode(this.options_.audioPosterMode);
22721        this.audioOnlyMode(this.options_.audioOnlyMode);
22722      });
22723    }
22724
22725    /**
22726     * Destroys the video player and does any necessary cleanup.
22727     *
22728     * This is especially helpful if you are dynamically adding and removing videos
22729     * to/from the DOM.
22730     *
22731     * @fires Player#dispose
22732     */
22733    dispose() {
22734      /**
22735       * Called when the player is being disposed of.
22736       *
22737       * @event Player#dispose
22738       * @type {Event}
22739       */
22740      this.trigger('dispose');
22741      // prevent dispose from being called twice
22742      this.off('dispose');
22743
22744      // Make sure all player-specific document listeners are unbound. This is
22745      off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22746      off(document, 'keydown', this.boundFullWindowOnEscKey_);
22747      if (this.styleEl_ && this.styleEl_.parentNode) {
22748        this.styleEl_.parentNode.removeChild(this.styleEl_);
22749        this.styleEl_ = null;
22750      }
22751
22752      // Kill reference to this player
22753      Player.players[this.id_] = null;
22754      if (this.tag && this.tag.player) {
22755        this.tag.player = null;
22756      }
22757      if (this.el_ && this.el_.player) {
22758        this.el_.player = null;
22759      }
22760      if (this.tech_) {
22761        this.tech_.dispose();
22762        this.isPosterFromTech_ = false;
22763        this.poster_ = '';
22764      }
22765      if (this.playerElIngest_) {
22766        this.playerElIngest_ = null;
22767      }
22768      if (this.tag) {
22769        this.tag = null;
22770      }
22771      clearCacheForPlayer(this);
22772
22773      // remove all event handlers for track lists
22774      // all tracks and track listeners are removed on
22775      // tech dispose
22776      ALL.names.forEach(name => {
22777        const props = ALL[name];
22778        const list = this[props.getterName]();
22779
22780        // if it is not a native list
22781        // we have to manually remove event listeners
22782        if (list && list.off) {
22783          list.off();
22784        }
22785      });
22786
22787      // the actual .el_ is removed here, or replaced if
22788      super.dispose({
22789        restoreEl: this.options_.restoreEl
22790      });
22791    }
22792
22793    /**
22794     * Create the `Player`'s DOM element.
22795     *
22796     * @return {Element}
22797     *         The DOM element that gets created.
22798     */
22799    createEl() {
22800      let tag = this.tag;
22801      let el;
22802      let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
22803      const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
22804      if (playerElIngest) {
22805        el = this.el_ = tag.parentNode;
22806      } else if (!divEmbed) {
22807        el = this.el_ = super.createEl('div');
22808      }
22809
22810      // Copy over all the attributes from the tag, including ID and class
22811      // ID will now reference player box, not the video tag
22812      const attrs = getAttributes(tag);
22813      if (divEmbed) {
22814        el = this.el_ = tag;
22815        tag = this.tag = document.createElement('video');
22816        while (el.children.length) {
22817          tag.appendChild(el.firstChild);
22818        }
22819        if (!hasClass(el, 'video-js')) {
22820          addClass(el, 'video-js');
22821        }
22822        el.appendChild(tag);
22823        playerElIngest = this.playerElIngest_ = el;
22824        // move properties over from our custom `video-js` element
22825        // to our new `video` element. This will move things like
22826        // `src` or `controls` that were set via js before the player
22827        // was initialized.
22828        Object.keys(el).forEach(k => {
22829          try {
22830            tag[k] = el[k];
22831          } catch (e) {
22832            // we got a a property like outerHTML which we can't actually copy, ignore it
22833          }
22834        });
22835      }
22836
22837      // set tabindex to -1 to remove the video element from the focus order
22838      tag.setAttribute('tabindex', '-1');
22839      attrs.tabindex = '-1';
22840
22841      // Workaround for #4583 on Chrome (on Windows) with JAWS.
22842      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
22843      // Note that we can't detect if JAWS is being used, but this ARIA attribute
22844      // doesn't change behavior of Chrome if JAWS is not being used
22845      if (IS_CHROME && IS_WINDOWS) {
22846        tag.setAttribute('role', 'application');
22847        attrs.role = 'application';
22848      }
22849
22850      // Remove width/height attrs from tag so CSS can make it 100% width/height
22851      tag.removeAttribute('width');
22852      tag.removeAttribute('height');
22853      if ('width' in attrs) {
22854        delete attrs.width;
22855      }
22856      if ('height' in attrs) {
22857        delete attrs.height;
22858      }
22859      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
22860        // don't copy over the class attribute to the player element when we're in a div embed
22861        // the class is already set up properly in the divEmbed case
22862        // and we want to make sure that the `video-js` class doesn't get lost
22863        if (!(divEmbed && attr === 'class')) {
22864          el.setAttribute(attr, attrs[attr]);
22865        }
22866        if (divEmbed) {
22867          tag.setAttribute(attr, attrs[attr]);
22868        }
22869      });
22870
22871      // Update tag id/class for use as HTML5 playback tech
22872      // Might think we should do this after embedding in container so .vjs-tech class
22873      // doesn't flash 100% width/height, but class only applies with .video-js parent
22874      tag.playerId = tag.id;
22875      tag.id += '_html5_api';
22876      tag.className = 'vjs-tech';
22877
22878      // Make player findable on elements
22879      tag.player = el.player = this;
22880      // Default state of video is paused
22881      this.addClass('vjs-paused'
vendor: 9,008 bytes, lines 22881-23146
22881);
22882
22883      // Add a style element in the player that we'll use to set the width/height
22884      // of the player in a way that's still overridable by CSS, just like the
22885      // video element
22886      if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
22887        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
22888        const defaultsStyleEl = $('.vjs-styles-defaults');
22889        const head = $('head');
22890        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
22891      }
22892      this.fill_ = false;
22893      this.fluid_ = false;
22894
22895      // Pass in the width/height/aspectRatio options which will update the style el
22896      this.width(this.options_.width);
22897      this.height(this.options_.height);
22898      this.fill(this.options_.fill);
22899      this.fluid(this.options_.fluid);
22900      this.aspectRatio(this.options_.aspectRatio);
22901      // support both crossOrigin and crossorigin to reduce confusion and issues around the name
22902      this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
22903
22904      // Hide any links within the video/audio tag,
22905      // because IE doesn't hide them completely from screen readers.
22906      const links = tag.getElementsByTagName('a');
22907      for (let i = 0; i < links.length; i++) {
22908        const linkEl = links.item(i);
22909        addClass(linkEl, 'vjs-hidden');
22910        linkEl.setAttribute('hidden', 'hidden');
22911      }
22912
22913      // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
22914      // keep track of the original for later so we can know if the source originally failed
22915      tag.initNetworkState_ = tag.networkState;
22916
22917      // Wrap video tag in div (el/box) container
22918      if (tag.parentNode && !playerElIngest) {
22919        tag.parentNode.insertBefore(el, tag);
22920      }
22921
22922      // insert the tag as the first child of the player element
22923      // then manually add it to the children array so that this.addChild
22924      // will work properly for other components
22925      //
22926      // Breaks iPhone, fixed in HTML5 setup.
22927      prependTo(tag, el);
22928      this.children_.unshift(tag);
22929
22930      // Set lang attr on player to ensure CSS :lang() in consistent with player
22931      // if it's been set to something different to the doc
22932      this.el_.setAttribute('lang', this.language_);
22933      this.el_.setAttribute('translate', 'no');
22934      this.el_ = el;
22935      return el;
22936    }
22937
22938    /**
22939     * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
22940     * sets the `crossOrigin` property on the `<video>` tag to control the CORS
22941     * behavior.
22942     *
22943     * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
22944     *
22945     * @param {string|null} [value]
22946     *        The value to set the `Player`'s crossOrigin to. If an argument is
22947     *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
22948     *
22949     * @return {string|null|undefined}
22950     *         - The current crossOrigin value of the `Player` when getting.
22951     *         - undefined when setting
22952     */
22953    crossOrigin(value) {
22954      // `null` can be set to unset a value
22955      if (typeof value === 'undefined') {
22956        return this.techGet_('crossOrigin');
22957      }
22958      if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
22959        log$1.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
22960        return;
22961      }
22962      this.techCall_('setCrossOrigin', value);
22963      if (this.posterImage) {
22964        this.posterImage.crossOrigin(value);
22965      }
22966      return;
22967    }
22968
22969    /**
22970     * A getter/setter for the `Player`'s width. Returns the player's configured value.
22971     * To get the current width use `currentWidth()`.
22972     *
22973     * @param {number|string} [value]
22974     *        CSS value to set the `Player`'s width to.
22975     *
22976     * @return {number|undefined}
22977     *         - The current width of the `Player` when getting.
22978     *         - Nothing when setting
22979     */
22980    width(value) {
22981      return this.dimension('width', value);
22982    }
22983
22984    /**
22985     * A getter/setter for the `Player`'s height. Returns the player's configured value.
22986     * To get the current height use `currentheight()`.
22987     *
22988     * @param {number|string} [value]
22989     *        CSS value to set the `Player`'s height to.
22990     *
22991     * @return {number|undefined}
22992     *         - The current height of the `Player` when getting.
22993     *         - Nothing when setting
22994     */
22995    height(value) {
22996      return this.dimension('height', value);
22997    }
22998
22999    /**
23000     * A getter/setter for the `Player`'s width & height.
23001     *
23002     * @param {string} dimension
23003     *        This string can be:
23004     *        - 'width'
23005     *        - 'height'
23006     *
23007     * @param {number|string} [value]
23008     *        Value for dimension specified in the first argument.
23009     *
23010     * @return {number}
23011     *         The dimension arguments value when getting (width/height).
23012     */
23013    dimension(dimension, value) {
23014      const privDimension = dimension + '_';
23015      if (value === undefined) {
23016        return this[privDimension] || 0;
23017      }
23018      if (value === '' || value === 'auto') {
23019        // If an empty string is given, reset the dimension to be automatic
23020        this[privDimension] = undefined;
23021        this.updateStyleEl_();
23022        return;
23023      }
23024      const parsedVal = parseFloat(value);
23025      if (isNaN(parsedVal)) {
23026        log$1.error(`Improper value "${value}" supplied for for ${dimension}`);
23027        return;
23028      }
23029      this[privDimension] = parsedVal;
23030      this.updateStyleEl_();
23031    }
23032
23033    /**
23034     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
23035     *
23036     * Turning this on will turn off fill mode.
23037     *
23038     * @param {boolean} [bool]
23039     *        - A value of true adds the class.
23040     *        - A value of false removes the class.
23041     *        - No value will be a getter.
23042     *
23043     * @return {boolean|undefined}
23044     *         - The value of fluid when getting.
23045     *         - `undefined` when setting.
23046     */
23047    fluid(bool) {
23048      if (bool === undefined) {
23049        return !!this.fluid_;
23050      }
23051      this.fluid_ = !!bool;
23052      if (isEvented(this)) {
23053        this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23054      }
23055      if (bool) {
23056        this.addClass('vjs-fluid');
23057        this.fill(false);
23058        addEventedCallback(this, () => {
23059          this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23060        });
23061      } else {
23062        this.removeClass('vjs-fluid');
23063      }
23064      this.updateStyleEl_();
23065    }
23066
23067    /**
23068     * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
23069     *
23070     * Turning this on will turn off fluid mode.
23071     *
23072     * @param {boolean} [bool]
23073     *        - A value of true adds the class.
23074     *        - A value of false removes the class.
23075     *        - No value will be a getter.
23076     *
23077     * @return {boolean|undefined}
23078     *         - The value of fluid when getting.
23079     *         - `undefined` when setting.
23080     */
23081    fill(bool) {
23082      if (bool === undefined) {
23083        return !!this.fill_;
23084      }
23085      this.fill_ = !!bool;
23086      if (bool) {
23087        this.addClass('vjs-fill');
23088        this.fluid(false);
23089      } else {
23090        this.removeClass('vjs-fill');
23091      }
23092    }
23093
23094    /**
23095     * Get/Set the aspect ratio
23096     *
23097     * @param {string} [ratio]
23098     *        Aspect ratio for player
23099     *
23100     * @return {string|undefined}
23101     *         returns the current aspect ratio when getting
23102     */
23103
23104    /**
23105     * A getter/setter for the `Player`'s aspect ratio.
23106     *
23107     * @param {string} [ratio]
23108     *        The value to set the `Player`'s aspect ratio to.
23109     *
23110     * @return {string|undefined}
23111     *         - The current aspect ratio of the `Player` when getting.
23112     *         - undefined when setting
23113     */
23114    aspectRatio(ratio) {
23115      if (ratio === undefined) {
23116        return this.aspectRatio_;
23117      }
23118
23119      // Check for width:height format
23120      if (!/^\d+\:\d+$/.test(ratio)) {
23121        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
23122      }
23123      this.aspectRatio_ = ratio;
23124
23125      // We're assuming if you set an aspect ratio you want fluid mode,
23126      // because in fixed mode you could calculate width and height yourself.
23127      this.fluid(true);
23128      this.updateStyleEl_();
23129    }
23130
23131    /**
23132     * Update styles of the `Player` element (height, width and aspect ratio).
23133     *
23134     * @private
23135     * @listens Tech#loadedmetadata
23136     */
23137    updateStyleEl_() {
23138      if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) {
23139        const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
23140        const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
23141        const techEl = this.tech_ && this.tech_.el();
23142        if (techEl) {
23143          if (width >= 0) {
23144            techEl.width = width;
23145          }
23146          if (height >= 0) {
vendor: 9,567 bytes, lines 23147-23385
23147            techEl.height = height;
23148          }
23149        }
23150        return;
23151      }
23152      let width;
23153      let height;
23154      let aspectRatio;
23155      let idClass;
23156
23157      // The aspect ratio is either used directly or to calculate width and height.
23158      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
23159        // Use any aspectRatio that's been specifically set
23160        aspectRatio = this.aspectRatio_;
23161      } else if (this.videoWidth() > 0) {
23162        // Otherwise try to get the aspect ratio from the video metadata
23163        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
23164      } else {
23165        // Or use a default. The video element's is 2:1, but 16:9 is more common.
23166        aspectRatio = '16:9';
23167      }
23168
23169      // Get the ratio as a decimal we can use to calculate dimensions
23170      const ratioParts = aspectRatio.split(':');
23171      const ratioMultiplier = ratioParts[1] / ratioParts[0];
23172      if (this.width_ !== undefined) {
23173        // Use any width that's been specifically set
23174        width = this.width_;
23175      } else if (this.height_ !== undefined) {
23176        // Or calculate the width from the aspect ratio if a height has been set
23177        width = this.height_ / ratioMultiplier;
23178      } else {
23179        // Or use the video's metadata, or use the video el's default of 300
23180        width = this.videoWidth() || 300;
23181      }
23182      if (this.height_ !== undefined) {
23183        // Use any height that's been specifically set
23184        height = this.height_;
23185      } else {
23186        // Otherwise calculate the height from the ratio and the width
23187        height = width * ratioMultiplier;
23188      }
23189
23190      // Ensure the CSS class is valid by starting with an alpha character
23191      if (/^[^a-zA-Z]/.test(this.id())) {
23192        idClass = 'dimensions-' + this.id();
23193      } else {
23194        idClass = this.id() + '-dimensions';
23195      }
23196
23197      // Ensure the right class is still on the player for the style element
23198      this.addClass(idClass);
23199      setTextContent(this.styleEl_, `
23200      .${idClass} {
23201        width: ${width}px;
23202        height: ${height}px;
23203      }
23204
23205      .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
23206        padding-top: ${ratioMultiplier * 100}%;
23207      }
23208    `);
23209    }
23210
23211    /**
23212     * Load/Create an instance of playback {@link Tech} including element
23213     * and API methods. Then append the `Tech` element in `Player` as a child.
23214     *
23215     * @param {string} techName
23216     *        name of the playback technology
23217     *
23218     * @param {string} source
23219     *        video source
23220     *
23221     * @private
23222     */
23223    loadTech_(techName, source) {
23224      // Pause and remove current playback technology
23225      if (this.tech_) {
23226        this.unloadTech_();
23227      }
23228      const titleTechName = toTitleCase$1(techName);
23229      const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
23230
23231      // get rid of the HTML5 video tag as soon as we are using another tech
23232      if (titleTechName !== 'Html5' && this.tag) {
23233        Tech.getTech('Html5').disposeMediaElement(this.tag);
23234        this.tag.player = null;
23235        this.tag = null;
23236      }
23237      this.techName_ = titleTechName;
23238
23239      // Turn off API access because we're loading a new tech that might load asynchronously
23240      this.isReady_ = false;
23241      let autoplay = this.autoplay();
23242
23243      // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
23244      // because the player is going to handle autoplay on `loadstart`
23245      if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
23246        autoplay = false;
23247      }
23248
23249      // Grab tech-specific options from player options and add source and parent element to use.
23250      const techOptions = {
23251        source,
23252        autoplay,
23253        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
23254        'playerId': this.id(),
23255        'techId': `${this.id()}_${camelTechName}_api`,
23256        'playsinline': this.options_.playsinline,
23257        'preload': this.options_.preload,
23258        'loop': this.options_.loop,
23259        'disablePictureInPicture': this.options_.disablePictureInPicture,
23260        'muted': this.options_.muted,
23261        'poster': this.poster(),
23262        'language': this.language(),
23263        'playerElIngest': this.playerElIngest_ || false,
23264        'vtt.js': this.options_['vtt.js'],
23265        'canOverridePoster': !!this.options_.techCanOverridePoster,
23266        'enableSourceset': this.options_.enableSourceset
23267      };
23268      ALL.names.forEach(name => {
23269        const props = ALL[name];
23270        techOptions[props.getterName] = this[props.privateName];
23271      });
23272      Object.assign(techOptions, this.options_[titleTechName]);
23273      Object.assign(techOptions, this.options_[camelTechName]);
23274      Object.assign(techOptions, this.options_[techName.toLowerCase()]);
23275      if (this.tag) {
23276        techOptions.tag = this.tag;
23277      }
23278      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
23279        techOptions.startTime = this.cache_.currentTime;
23280      }
23281
23282      // Initialize tech instance
23283      const TechClass = Tech.getTech(techName);
23284      if (!TechClass) {
23285        throw new Error(`No Tech named '${titleTechName}' exists! '${titleTechName}' should be registered using videojs.registerTech()'`);
23286      }
23287      this.tech_ = new TechClass(techOptions);
23288
23289      // player.triggerReady is always async, so don't need this to be async
23290      this.tech_.ready(bind_(this, this.handleTechReady_), true);
23291      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
23292
23293      // Listen to all HTML5-defined events and trigger them on the player
23294      TECH_EVENTS_RETRIGGER.forEach(event => {
23295        this.on(this.tech_, event, e => this[`handleTech${toTitleCase$1(event)}_`](e));
23296      });
23297      Object.keys(TECH_EVENTS_QUEUE).forEach(event => {
23298        this.on(this.tech_, event, eventObj => {
23299          if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
23300            this.queuedCallbacks_.push({
23301              callback: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
23302              event: eventObj
23303            });
23304            return;
23305          }
23306          this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
23307        });
23308      });
23309      this.on(this.tech_, 'loadstart', e => this.handleTechLoadStart_(e));
23310      this.on(this.tech_, 'sourceset', e => this.handleTechSourceset_(e));
23311      this.on(this.tech_, 'waiting', e => this.handleTechWaiting_(e));
23312      this.on(this.tech_, 'ended', e => this.handleTechEnded_(e));
23313      this.on(this.tech_, 'seeking', e => this.handleTechSeeking_(e));
23314      this.on(this.tech_, 'play', e => this.handleTechPlay_(e));
23315      this.on(this.tech_, 'pause', e => this.handleTechPause_(e));
23316      this.on(this.tech_, 'durationchange', e => this.handleTechDurationChange_(e));
23317      this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
23318      this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err));
23319      this.on(this.tech_, 'enterpictureinpicture', e => this.handleTechEnterPictureInPicture_(e));
23320      this.on(this.tech_, 'leavepictureinpicture', e => this.handleTechLeavePictureInPicture_(e));
23321      this.on(this.tech_, 'error', e => this.handleTechError_(e));
23322      this.on(this.tech_, 'posterchange', e => this.handleTechPosterChange_(e));
23323      this.on(this.tech_, 'textdata', e => this.handleTechTextData_(e));
23324      this.on(this.tech_, 'ratechange', e => this.handleTechRateChange_(e));
23325      this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
23326      this.usingNativeControls(this.techGet_('controls'));
23327      if (this.controls() && !this.usingNativeControls()) {
23328        this.addTechControlsListeners_();
23329      }
23330
23331      // Add the tech element in the DOM if it was not already there
23332      // Make sure to not insert the original video element if using Html5
23333      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
23334        prependTo(this.tech_.el(), this.el());
23335      }
23336
23337      // Get rid of the original video tag reference after the first tech is loaded
23338      if (this.tag) {
23339        this.tag.player = null;
23340        this.tag = null;
23341      }
23342    }
23343
23344    /**
23345     * Unload and dispose of the current playback {@link Tech}.
23346     *
23347     * @private
23348     */
23349    unloadTech_() {
23350      // Save the current text tracks so that we can reuse the same text tracks with the next tech
23351      ALL.names.forEach(name => {
23352        const props = ALL[name];
23353        this[props.privateName] = this[props.getterName]();
23354      });
23355      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
23356      this.isReady_ = false;
23357      this.tech_.dispose();
23358      this.tech_ = false;
23359      if (this.isPosterFromTech_) {
23360        this.poster_ = '';
23361        this.trigger('posterchange');
23362      }
23363      this.isPosterFromTech_ = false;
23364    }
23365
23366    /**
23367     * Return a reference to the current {@link Tech}.
23368     * It will print a warning by default about the danger of using the tech directly
23369     * but any argument that is passed in will silence the warning.
23370     *
23371     * @param {*} [safety]
23372     *        Anything passed in to silence the warning
23373     *
23374     * @return {Tech}
23375     *         The Tech
23376     */
23377    tech(safety) {
23378      if (safety === undefined) {
23379        log$1.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n');
23380      }
23381      return this.tech_;
23382    }
23383
23384    /**
23385     * 
23385Set up click and touch listeners for the playback element
23386     *
23387     * - On desktops: a click on the video itself will toggle playback
23388     * - On mobile devices: a click on the video toggles controls
23389     *   which is done by toggling the user state between active and
23390     *   inactive
23391     * - A tap can signal that a user has become active or has become inactive
23392     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
23393     *   quick tap should hide them again (signaling the user is in an inactive
23394     *   viewing state)
23395     * - In addition to this, we still want the user to be considered inactive after
23396     *   a few seconds of inactivity.
23397     *
23398     * > Note: the only part of iOS interaction we can't mimic with this setup
23399     * is a touch and hold on the video element counting as activity in order to
23400     * keep the controls showing, but that shouldn't be an issue. A touch and hold
23401     * on any controls will still keep the user active
23402     *
23403     * @private
23404     */
23405    addTechControlsListeners_() {
23406      // Make sure to remove all the previous listeners in case we are called multiple times.
23407      this.removeTechControlsListeners_();
23408      this.on(this.tech_, 'click', this.boundHandleTechClick_);
23409      this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23410
23411      // If the controls were hidden we don't want that to change without a tap event
23412      // so we'll check if the controls were already showing before reporting user
23413      // activity
23414      this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23415      this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23416      this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23417
23418      // The tap listener needs to come after the touchend listener because the tap
23419      // listener cancels out any reportedUserActivity when setting userActive(false)
23420      this.on(this.tech_, 'tap', this.boundHandleTechTap_);
23421    }
23422
23423    /**
23424     * Remove the listeners used for click and tap controls. This is needed for
23425     * toggling to controls disabled, where a tap/touch should do nothing.
23426     *
23427     * @private
23428     */
23429    removeTechControlsListeners_() {
23430      // We don't want to just use `this.off()` because there might be other needed
23431      // listeners added by techs that extend this.
23432      this.off(this.tech_, 'tap', this.boundHandleTechTap_);
23433      this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23434      this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23435      this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23436      this.off(this.tech_, 'click', this.boundHandleTechClick_);
23437      this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23438    }
23439
23440    /**
23441     * Player waits for the tech to be ready
23442     *
23443     * @private
23444     */
23445    handleTechReady_() {
23446      this.triggerReady();
23447
23448      // Keep the same volume as before
23449      if (this.cache_.volume) {
23450        this.techCall_('setVolume', this.cache_.volume);
23451      }
23452
23453      // Look if the tech found a higher resolution poster while loading
23454      this.handleTechPosterChange_();
23455
23456      // Update the duration if available
23457      this.handleTechDurationChange_();
23458    }
23459
23460    /**
23461     * Retrigger the `loadstart` event that was triggered by the {@link Tech}.
23462     *
23463     * @fires Player#loadstart
23464     * @listens Tech#loadstart
23465     * @private
23466     */
23467    handleTechLoadStart_() {
23468      // TODO: Update to use `emptied` event instead. See #1277.
23469
23470      this.removeClass('vjs-ended', 'vjs-seeking');
23471
23472      // reset the error state
23473      this.error(null);
23474
23475      // Update the duration
23476      this.handleTechDurationChange_();
23477      if (!this.paused()) {
23478        /**
23479         * Fired when the user agent begins looking for media data
23480         *
23481         * @event Player#loadstart
23482         * @type {Event}
23483         */
23484        this.trigger('loadstart');
23485      } else {
23486        // reset the hasStarted state
23487        this.hasStarted(false);
23488        this.trigger('loadstart');
23489      }
23490
23491      // autoplay happens after loadstart for the browser,
23492      // so we mimic that behavior
23493      this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
23494    }
23495
23496    /**
23497     * Handle autoplay string values, rather than the typical boolean
23498     * values that should be handled by the tech. Note that this is not
23499     * part of any specification. Valid values and what they do can be
23500     * found on the autoplay getter at Player#autoplay()
23501     */
23502    manualAutoplay_(type) {
23503      if (!this.tech_ || typeof type !== 'string') {
23504        return;
23505      }
23506
23507      // Save original muted() value, set muted to true, and attempt to play().
23508      // On promise rejection, restore muted from saved value
23509      const resolveMuted = () => {
23510        const previouslyMuted = this.muted();
23511        this.muted(true);
23512        const restoreMuted = () => {
23513          this.muted(previouslyMuted);
23514        };
23515
23516        // restore muted on play terminatation
23517        this.playTerminatedQueue_.push(restoreMuted);
23518        const mutedPromise = this.play();
23519        if (!isPromise(mutedPromise)) {
23520          return;
23521        }
23522        return mutedPromise.catch(err => {
23523          restoreMuted();
23524          throw new Error(`Rejection at manualAutoplay. Restoring muted value. ${err ? err : ''}`);
23525        });
23526      };
23527      let promise;
23528
23529      // if muted defaults to true
23530      // the only thing we can do is call play
23531      if (type === 'any' && !this.muted()) {
23532        promise = this.play();
vendor: 17,112 bytes, lines 23533-24089
23533        if (isPromise(promise)) {
23534          promise = promise.catch(resolveMuted);
23535        }
23536      } else if (type === 'muted' && !this.muted()) {
23537        promise = resolveMuted();
23538      } else {
23539        promise = this.play();
23540      }
23541      if (!isPromise(promise)) {
23542        return;
23543      }
23544      return promise.then(() => {
23545        this.trigger({
23546          type: 'autoplay-success',
23547          autoplay: type
23548        });
23549      }).catch(() => {
23550        this.trigger({
23551          type: 'autoplay-failure',
23552          autoplay: type
23553        });
23554      });
23555    }
23556
23557    /**
23558     * Update the internal source caches so that we return the correct source from
23559     * `src()`, `currentSource()`, and `currentSources()`.
23560     *
23561     * > Note: `currentSources` will not be updated if the source that is passed in exists
23562     *         in the current `currentSources` cache.
23563     *
23564     *
23565     * @param {Tech~SourceObject} srcObj
23566     *        A string or object source to update our caches to.
23567     */
23568    updateSourceCaches_(srcObj = '') {
23569      let src = srcObj;
23570      let type = '';
23571      if (typeof src !== 'string') {
23572        src = srcObj.src;
23573        type = srcObj.type;
23574      }
23575
23576      // make sure all the caches are set to default values
23577      // to prevent null checking
23578      this.cache_.source = this.cache_.source || {};
23579      this.cache_.sources = this.cache_.sources || [];
23580
23581      // try to get the type of the src that was passed in
23582      if (src && !type) {
23583        type = findMimetype(this, src);
23584      }
23585
23586      // update `currentSource` cache always
23587      this.cache_.source = merge$2({}, srcObj, {
23588        src,
23589        type
23590      });
23591      const matchingSources = this.cache_.sources.filter(s => s.src && s.src === src);
23592      const sourceElSources = [];
23593      const sourceEls = this.$$('source');
23594      const matchingSourceEls = [];
23595      for (let i = 0; i < sourceEls.length; i++) {
23596        const sourceObj = getAttributes(sourceEls[i]);
23597        sourceElSources.push(sourceObj);
23598        if (sourceObj.src && sourceObj.src === src) {
23599          matchingSourceEls.push(sourceObj.src);
23600        }
23601      }
23602
23603      // if we have matching source els but not matching sources
23604      // the current source cache is not up to date
23605      if (matchingSourceEls.length && !matchingSources.length) {
23606        this.cache_.sources = sourceElSources;
23607        // if we don't have matching source or source els set the
23608        // sources cache to the `currentSource` cache
23609      } else if (!matchingSources.length) {
23610        this.cache_.sources = [this.cache_.source];
23611      }
23612
23613      // update the tech `src` cache
23614      this.cache_.src = src;
23615    }
23616
23617    /**
23618     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
23619     * causing the media element to reload.
23620     *
23621     * It will fire for the initial source and each subsequent source.
23622     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
23623     *
23624     * The event object for this event contains a `src` property that will contain the source
23625     * that was available when the event was triggered. This is generally only necessary if Video.js
23626     * is switching techs while the source was being changed.
23627     *
23628     * It is also fired when `load` is called on the player (or media element)
23629     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
23630     * says that the resource selection algorithm needs to be aborted and restarted.
23631     * In this case, it is very likely that the `src` property will be set to the
23632     * empty string `""` to indicate we do not know what the source will be but
23633     * that it is changing.
23634     *
23635     * *This event is currently still experimental and may change in minor releases.*
23636     * __To use this, pass `enableSourceset` option to the player.__
23637     *
23638     * @event Player#sourceset
23639     * @type {Event}
23640     * @prop {string} src
23641     *                The source url available when the `sourceset` was triggered.
23642     *                It will be an empty string if we cannot know what the source is
23643     *                but know that the source will change.
23644     */
23645    /**
23646     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
23647     *
23648     * @fires Player#sourceset
23649     * @listens Tech#sourceset
23650     * @private
23651     */
23652    handleTechSourceset_(event) {
23653      // only update the source cache when the source
23654      // was not updated using the player api
23655      if (!this.changingSrc_) {
23656        let updateSourceCaches = src => this.updateSourceCaches_(src);
23657        const playerSrc = this.currentSource().src;
23658        const eventSrc = event.src;
23659
23660        // if we have a playerSrc that is not a blob, and a tech src that is a blob
23661        if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
23662          // if both the tech source and the player source were updated we assume
23663          // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
23664          if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
23665            updateSourceCaches = () => {};
23666          }
23667        }
23668
23669        // update the source to the initial source right away
23670        // in some cases this will be empty string
23671        updateSourceCaches(eventSrc);
23672
23673        // if the `sourceset` `src` was an empty string
23674        // wait for a `loadstart` to update the cache to `currentSrc`.
23675        // If a sourceset happens before a `loadstart`, we reset the state
23676        if (!event.src) {
23677          this.tech_.any(['sourceset', 'loadstart'], e => {
23678            // if a sourceset happens before a `loadstart` there
23679            // is nothing to do as this `handleTechSourceset_`
23680            // will be called again and this will be handled there.
23681            if (e.type === 'sourceset') {
23682              return;
23683            }
23684            const techSrc = this.techGet_('currentSrc');
23685            this.lastSource_.tech = techSrc;
23686            this.updateSourceCaches_(techSrc);
23687          });
23688        }
23689      }
23690      this.lastSource_ = {
23691        player: this.currentSource().src,
23692        tech: event.src
23693      };
23694      this.trigger({
23695        src: event.src,
23696        type: 'sourceset'
23697      });
23698    }
23699
23700    /**
23701     * Add/remove the vjs-has-started class
23702     *
23703     *
23704     * @param {boolean} request
23705     *        - true: adds the class
23706     *        - false: remove the class
23707     *
23708     * @return {boolean}
23709     *         the boolean value of hasStarted_
23710     */
23711    hasStarted(request) {
23712      if (request === undefined) {
23713        // act as getter, if we have no request to change
23714        return this.hasStarted_;
23715      }
23716      if (request === this.hasStarted_) {
23717        return;
23718      }
23719      this.hasStarted_ = request;
23720      if (this.hasStarted_) {
23721        this.addClass('vjs-has-started');
23722      } else {
23723        this.removeClass('vjs-has-started');
23724      }
23725    }
23726
23727    /**
23728     * Fired whenever the media begins or resumes playback
23729     *
23730     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
23731     * @fires Player#play
23732     * @listens Tech#play
23733     * @private
23734     */
23735    handleTechPlay_() {
23736      this.removeClass('vjs-ended', 'vjs-paused');
23737      this.addClass('vjs-playing');
23738
23739      // hide the poster when the user hits play
23740      this.hasStarted(true);
23741      /**
23742       * Triggered whenever an {@link Tech#play} event happens. Indicates that
23743       * playback has started or resumed.
23744       *
23745       * @event Player#play
23746       * @type {Event}
23747       */
23748      this.trigger('play');
23749    }
23750
23751    /**
23752     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
23753     *
23754     * If there were any events queued while the playback rate was zero, fire
23755     * those events now.
23756     *
23757     * @private
23758     * @method Player#handleTechRateChange_
23759     * @fires Player#ratechange
23760     * @listens Tech#ratechange
23761     */
23762    handleTechRateChange_() {
23763      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
23764        this.queuedCallbacks_.forEach(queued => queued.callback(queued.event));
23765        this.queuedCallbacks_ = [];
23766      }
23767      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
23768      /**
23769       * Fires when the playing speed of the audio/video is changed
23770       *
23771       * @event Player#ratechange
23772       * @type {event}
23773       */
23774      this.trigger('ratechange');
23775    }
23776
23777    /**
23778     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
23779     *
23780     * @fires Player#waiting
23781     * @listens Tech#waiting
23782     * @private
23783     */
23784    handleTechWaiting_() {
23785      this.addClass('vjs-waiting');
23786      /**
23787       * A readyState change on the DOM element has caused playback to stop.
23788       *
23789       * @event Player#waiting
23790       * @type {Event}
23791       */
23792      this.trigger('waiting');
23793
23794      // Browsers may emit a timeupdate event after a waiting event. In order to prevent
23795      // premature removal of the waiting class, wait for the time to change.
23796      const timeWhenWaiting = this.currentTime();
23797      const timeUpdateListener = () => {
23798        if (timeWhenWaiting !== this.currentTime()) {
23799          this.removeClass('vjs-waiting');
23800          this.off('timeupdate', timeUpdateListener);
23801        }
23802      };
23803      this.on('timeupdate', timeUpdateListener);
23804    }
23805
23806    /**
23807     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
23808     * > Note: This is not consistent between browsers. See #1351
23809     *
23810     * @fires Player#canplay
23811     * @listens Tech#canplay
23812     * @private
23813     */
23814    handleTechCanPlay_() {
23815      this.removeClass('vjs-waiting');
23816      /**
23817       * The media has a readyState of HAVE_FUTURE_DATA or greater.
23818       *
23819       * @event Player#canplay
23820       * @type {Event}
23821       */
23822      this.trigger('canplay');
23823    }
23824
23825    /**
23826     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
23827     *
23828     * @fires Player#canplaythrough
23829     * @listens Tech#canplaythrough
23830     * @private
23831     */
23832    handleTechCanPlayThrough_() {
23833      this.removeClass('vjs-waiting');
23834      /**
23835       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
23836       * entire media file can be played without buffering.
23837       *
23838       * @event Player#canplaythrough
23839       * @type {Event}
23840       */
23841      this.trigger('canplaythrough');
23842    }
23843
23844    /**
23845     * Retrigger the `playing` event that was triggered by the {@link Tech}.
23846     *
23847     * @fires Player#playing
23848     * @listens Tech#playing
23849     * @private
23850     */
23851    handleTechPlaying_() {
23852      this.removeClass('vjs-waiting');
23853      /**
23854       * The media is no longer blocked from playback, and has started playing.
23855       *
23856       * @event Player#playing
23857       * @type {Event}
23858       */
23859      this.trigger('playing');
23860    }
23861
23862    /**
23863     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
23864     *
23865     * @fires Player#seeking
23866     * @listens Tech#seeking
23867     * @private
23868     */
23869    handleTechSeeking_() {
23870      this.addClass('vjs-seeking');
23871      /**
23872       * Fired whenever the player is jumping to a new time
23873       *
23874       * @event Player#seeking
23875       * @type {Event}
23876       */
23877      this.trigger('seeking');
23878    }
23879
23880    /**
23881     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
23882     *
23883     * @fires Player#seeked
23884     * @listens Tech#seeked
23885     * @private
23886     */
23887    handleTechSeeked_() {
23888      this.removeClass('vjs-seeking', 'vjs-ended');
23889      /**
23890       * Fired when the player has finished jumping to a new time
23891       *
23892       * @event Player#seeked
23893       * @type {Event}
23894       */
23895      this.trigger('seeked');
23896    }
23897
23898    /**
23899     * Retrigger the `pause` event that was triggered by the {@link Tech}.
23900     *
23901     * @fires Player#pause
23902     * @listens Tech#pause
23903     * @private
23904     */
23905    handleTechPause_() {
23906      this.removeClass('vjs-playing');
23907      this.addClass('vjs-paused');
23908      /**
23909       * Fired whenever the media has been paused
23910       *
23911       * @event Player#pause
23912       * @type {Event}
23913       */
23914      this.trigger('pause');
23915    }
23916
23917    /**
23918     * Retrigger the `ended` event that was triggered by the {@link Tech}.
23919     *
23920     * @fires Player#ended
23921     * @listens Tech#ended
23922     * @private
23923     */
23924    handleTechEnded_() {
23925      this.addClass('vjs-ended');
23926      this.removeClass('vjs-waiting');
23927      if (this.options_.loop) {
23928        this.currentTime(0);
23929        this.play();
23930      } else if (!this.paused()) {
23931        this.pause();
23932      }
23933
23934      /**
23935       * Fired when the end of the media resource is reached (currentTime == duration)
23936       *
23937       * @event Player#ended
23938       * @type {Event}
23939       */
23940      this.trigger('ended');
23941    }
23942
23943    /**
23944     * Fired when the duration of the media resource is first known or changed
23945     *
23946     * @listens Tech#durationchange
23947     * @private
23948     */
23949    handleTechDurationChange_() {
23950      this.duration(this.techGet_('duration'));
23951    }
23952
23953    /**
23954     * Handle a click on the media element to play/pause
23955     *
23956     * @param {Event} event
23957     *        the event that caused this function to trigger
23958     *
23959     * @listens Tech#click
23960     * @private
23961     */
23962    handleTechClick_(event) {
23963      // When controls are disabled a click should not toggle playback because
23964      // the click is considered a control
23965      if (!this.controls_) {
23966        return;
23967      }
23968      if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.click === undefined || this.options_.userActions.click !== false) {
23969        if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.click === 'function') {
23970          this.options_.userActions.click.call(this, event);
23971        } else if (this.paused()) {
23972          silencePromise(this.play());
23973        } else {
23974          this.pause();
23975        }
23976      }
23977    }
23978
23979    /**
23980     * Handle a double-click on the media element to enter/exit fullscreen
23981     *
23982     * @param {Event} event
23983     *        the event that caused this function to trigger
23984     *
23985     * @listens Tech#dblclick
23986     * @private
23987     */
23988    handleTechDoubleClick_(event) {
23989      if (!this.controls_) {
23990        return;
23991      }
23992
23993      // we do not want to toggle fullscreen state
23994      // when double-clicking inside a control bar or a modal
23995      const inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), el => el.contains(event.target));
23996      if (!inAllowedEls) {
23997        /*
23998         * options.userActions.doubleClick
23999         *
24000         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
24001         * Set to `false` to disable double-click handling
24002         * Set to a function to substitute an external double-click handler
24003         */
24004        if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
24005          if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
24006            this.options_.userActions.doubleClick.call(this, event);
24007          } else if (this.isFullscreen()) {
24008            this.exitFullscreen();
24009          } else {
24010            this.requestFullscreen();
24011          }
24012        }
24013      }
24014    }
24015
24016    /**
24017     * Handle a tap on the media element. It will toggle the user
24018     * activity state, which hides and shows the controls.
24019     *
24020     * @listens Tech#tap
24021     * @private
24022     */
24023    handleTechTap_() {
24024      this.userActive(!this.userActive());
24025    }
24026
24027    /**
24028     * Handle touch to start
24029     *
24030     * @listens Tech#touchstart
24031     * @private
24032     */
24033    handleTechTouchStart_() {
24034      this.userWasActive = this.userActive();
24035    }
24036
24037    /**
24038     * Handle touch to move
24039     *
24040     * @listens Tech#touchmove
24041     * @private
24042     */
24043    handleTechTouchMove_() {
24044      if (this.userWasActive) {
24045        this.reportUserActivity();
24046      }
24047    }
24048
24049    /**
24050     * Handle touch to end
24051     *
24052     * @param {Event} event
24053     *        the touchend event that triggered
24054     *        this function
24055     *
24056     * @listens Tech#touchend
24057     * @private
24058     */
24059    handleTechTouchEnd_(event) {
24060      // Stop the mouse events from also happening
24061      if (event.cancelable) {
24062        event.preventDefault();
24063      }
24064    }
24065
24066    /**
24067     * @private
24068     */
24069    toggleFullscreenClass_() {
24070      if (this.isFullscreen()) {
24071        this.addClass('vjs-fullscreen');
24072      } else {
24073        this.removeClass('vjs-fullscreen');
24074      }
24075    }
24076
24077    /**
24078     * when the document fschange event triggers it calls this
24079     */
24080    documentFullscreenChange_(e) {
24081      const targetPlayer = e.target.player;
24082
24083      // if another player was fullscreen
24084      // do a null check for targetPlayer because older firefox's would put document as e.target
24085      if (targetPlayer && targetPlayer !== this) {
24086        return;
24087      }
24088      const el = this.el();
24089      let isFs = document
vendor: 10,170 bytes, lines 24089-24436
24089[this.fsApi_.fullscreenElement] === el;
24090      if (!isFs && el.matches) {
24091        isFs = el.matches(':' + this.fsApi_.fullscreen);
24092      }
24093      this.isFullscreen(isFs);
24094    }
24095
24096    /**
24097     * Handle Tech Fullscreen Change
24098     *
24099     * @param {Event} event
24100     *        the fullscreenchange event that triggered this function
24101     *
24102     * @param {Object} data
24103     *        the data that was sent with the event
24104     *
24105     * @private
24106     * @listens Tech#fullscreenchange
24107     * @fires Player#fullscreenchange
24108     */
24109    handleTechFullscreenChange_(event, data) {
24110      if (data) {
24111        if (data.nativeIOSFullscreen) {
24112          this.addClass('vjs-ios-native-fs');
24113          this.tech_.one('webkitendfullscreen', () => {
24114            this.removeClass('vjs-ios-native-fs');
24115          });
24116        }
24117        this.isFullscreen(data.isFullscreen);
24118      }
24119    }
24120    handleTechFullscreenError_(event, err) {
24121      this.trigger('fullscreenerror', err);
24122    }
24123
24124    /**
24125     * @private
24126     */
24127    togglePictureInPictureClass_() {
24128      if (this.isInPictureInPicture()) {
24129        this.addClass('vjs-picture-in-picture');
24130      } else {
24131        this.removeClass('vjs-picture-in-picture');
24132      }
24133    }
24134
24135    /**
24136     * Handle Tech Enter Picture-in-Picture.
24137     *
24138     * @param {Event} event
24139     *        the enterpictureinpicture event that triggered this function
24140     *
24141     * @private
24142     * @listens Tech#enterpictureinpicture
24143     */
24144    handleTechEnterPictureInPicture_(event) {
24145      this.isInPictureInPicture(true);
24146    }
24147
24148    /**
24149     * Handle Tech Leave Picture-in-Picture.
24150     *
24151     * @param {Event} event
24152     *        the leavepictureinpicture event that triggered this function
24153     *
24154     * @private
24155     * @listens Tech#leavepictureinpicture
24156     */
24157    handleTechLeavePictureInPicture_(event) {
24158      this.isInPictureInPicture(false);
24159    }
24160
24161    /**
24162     * Fires when an error occurred during the loading of an audio/video.
24163     *
24164     * @private
24165     * @listens Tech#error
24166     */
24167    handleTechError_() {
24168      const error = this.tech_.error();
24169      if (error) {
24170        this.error(error);
24171      }
24172    }
24173
24174    /**
24175     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
24176     *
24177     * @fires Player#textdata
24178     * @listens Tech#textdata
24179     * @private
24180     */
24181    handleTechTextData_() {
24182      let data = null;
24183      if (arguments.length > 1) {
24184        data = arguments[1];
24185      }
24186
24187      /**
24188       * Fires when we get a textdata event from tech
24189       *
24190       * @event Player#textdata
24191       * @type {Event}
24192       */
24193      this.trigger('textdata', data);
24194    }
24195
24196    /**
24197     * Get object for cached values.
24198     *
24199     * @return {Object}
24200     *         get the current object cache
24201     */
24202    getCache() {
24203      return this.cache_;
24204    }
24205
24206    /**
24207     * Resets the internal cache object.
24208     *
24209     * Using this function outside the player constructor or reset method may
24210     * have unintended side-effects.
24211     *
24212     * @private
24213     */
24214    resetCache_() {
24215      this.cache_ = {
24216        // Right now, the currentTime is not _really_ cached because it is always
24217        // retrieved from the tech (see: currentTime). However, for completeness,
24218        // we set it to zero here to ensure that if we do start actually caching
24219        // it, we reset it along with everything else.
24220        currentTime: 0,
24221        initTime: 0,
24222        inactivityTimeout: this.options_.inactivityTimeout,
24223        duration: NaN,
24224        lastVolume: 1,
24225        lastPlaybackRate: this.defaultPlaybackRate(),
24226        media: null,
24227        src: '',
24228        source: {},
24229        sources: [],
24230        playbackRates: [],
24231        volume: 1
24232      };
24233    }
24234
24235    /**
24236     * Pass values to the playback tech
24237     *
24238     * @param {string} [method]
24239     *        the method to call
24240     *
24241     * @param {Object} [arg]
24242     *        the argument to pass
24243     *
24244     * @private
24245     */
24246    techCall_(method, arg) {
24247      // If it's not ready yet, call method when it is
24248
24249      this.ready(function () {
24250        if (method in allowedSetters) {
24251          return set(this.middleware_, this.tech_, method, arg);
24252        } else if (method in allowedMediators) {
24253          return mediate(this.middleware_, this.tech_, method, arg);
24254        }
24255        try {
24256          if (this.tech_) {
24257            this.tech_[method](arg);
24258          }
24259        } catch (e) {
24260          log$1(e);
24261          throw e;
24262        }
24263      }, true);
24264    }
24265
24266    /**
24267     * Mediate attempt to call playback tech method
24268     * and return the value of the method called.
24269     *
24270     * @param {string} method
24271     *        Tech method
24272     *
24273     * @return {*}
24274     *         Value returned by the tech method called, undefined if tech
24275     *         is not ready or tech method is not present
24276     *
24277     * @private
24278     */
24279    techGet_(method) {
24280      if (!this.tech_ || !this.tech_.isReady_) {
24281        return;
24282      }
24283      if (method in allowedGetters) {
24284        return get(this.middleware_, this.tech_, method);
24285      } else if (method in allowedMediators) {
24286        return mediate(this.middleware_, this.tech_, method);
24287      }
24288
24289      // Log error when playback tech object is present but method
24290      // is undefined or unavailable
24291      try {
24292        return this.tech_[method]();
24293      } catch (e) {
24294        // When building additional tech libs, an expected method may not be defined yet
24295        if (this.tech_[method] === undefined) {
24296          log$1(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e);
24297          throw e;
24298        }
24299
24300        // When a method isn't available on the object it throws a TypeError
24301        if (e.name === 'TypeError') {
24302          log$1(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
24303          this.tech_.isReady_ = false;
24304          throw e;
24305        }
24306
24307        // If error unknown, just log and throw
24308        log$1(e);
24309        throw e;
24310      }
24311    }
24312
24313    /**
24314     * Attempt to begin playback at the first opportunity.
24315     *
24316     * @return {Promise|undefined}
24317     *         Returns a promise if the browser supports Promises (or one
24318     *         was passed in as an option). This promise will be resolved on
24319     *         the return value of play. If this is undefined it will fulfill the
24320     *         promise chain otherwise the promise chain will be fulfilled when
24321     *         the promise from play is fulfilled.
24322     */
24323    play() {
24324      return new Promise(resolve => {
24325        this.play_(resolve);
24326      });
24327    }
24328
24329    /**
24330     * The actual logic for play, takes a callback that will be resolved on the
24331     * return value of play. This allows us to resolve to the play promise if there
24332     * is one on modern browsers.
24333     *
24334     * @private
24335     * @param {Function} [callback]
24336     *        The callback that should be called when the techs play is actually called
24337     */
24338    play_(callback = silencePromise) {
24339      this.playCallbacks_.push(callback);
24340      const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
24341      const isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS);
24342
24343      // treat calls to play_ somewhat like the `one` event function
24344      if (this.waitToPlay_) {
24345        this.off(['ready', 'loadstart'], this.waitToPlay_);
24346        this.waitToPlay_ = null;
24347      }
24348
24349      // if the player/tech is not ready or the src itself is not ready
24350      // queue up a call to play on `ready` or `loadstart`
24351      if (!this.isReady_ || !isSrcReady) {
24352        this.waitToPlay_ = e => {
24353          this.play_();
24354        };
24355        this.one(['ready', 'loadstart'], this.waitToPlay_);
24356
24357        // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
24358        // in that case, we need to prime the video element by calling load so it'll be ready in time
24359        if (!isSrcReady && isSafariOrIOS) {
24360          this.load();
24361        }
24362        return;
24363      }
24364
24365      // If the player/tech is ready and we have a source, we can attempt playback.
24366      const val = this.techGet_('play');
24367
24368      // For native playback, reset the progress bar if we get a play call from a replay.
24369      const isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
24370      if (isNativeReplay) {
24371        this.resetProgressBar_();
24372      }
24373      // play was terminated if the returned value is null
24374      if (val === null) {
24375        this.runPlayTerminatedQueue_();
24376      } else {
24377        this.runPlayCallbacks_(val);
24378      }
24379    }
24380
24381    /**
24382     * These functions will be run when if play is terminated. If play
24383     * runPlayCallbacks_ is run these function will not be run. This allows us
24384     * to differentiate between a terminated play and an actual call to play.
24385     */
24386    runPlayTerminatedQueue_() {
24387      const queue = this.playTerminatedQueue_.slice(0);
24388      this.playTerminatedQueue_ = [];
24389      queue.forEach(function (q) {
24390        q();
24391      });
24392    }
24393
24394    /**
24395     * When a callback to play is delayed we have to run these
24396     * callbacks when play is actually called on the tech. This function
24397     * runs the callbacks that were delayed and accepts the return value
24398     * from the tech.
24399     *
24400     * @param {undefined|Promise} val
24401     *        The return value from the tech.
24402     */
24403    runPlayCallbacks_(val) {
24404      const callbacks = this.playCallbacks_.slice(0);
24405      this.playCallbacks_ = [];
24406      // clear play terminatedQueue since we finished a real play
24407      this.playTerminatedQueue_ = [];
24408      callbacks.forEach(function (cb) {
24409        cb(val);
24410      });
24411    }
24412
24413    /**
24414     * Pause the video playback
24415     */
24416    pause() {
24417      this.techCall_('pause');
24418    }
24419
24420    /**
24421     * Check if the player is paused or has yet to play
24422     *
24423     * @return {boolean}
24424     *         - false: if the media is currently playing
24425     *         - true: if media is not currently playing
24426     */
24427    paused() {
24428      // The initial state of paused should be true (in Safari it's actually false)
24429      return this.techGet_('paused') === false ? false : true;
24430    }
24431
24432    /**
24433     * Get a TimeRange object representing the current ranges of time that the user
24434     * has played.
24435     *
24436     * @return {
24436 import('./utils/time').TimeRange }
24437     *         A time range object that represents all the increments of time that have
24438     *         been played.
24439     */
24440    played() {
24441      return this.techGet_('played') || createTimeRanges$1(0, 0);
24442    }
24443
24444    /**
24445     * Sets or returns whether or not the user is "scrubbing". Scrubbing is
24446     * when the user has clicked the progress bar handle and is
24447     * dragging it along the progress bar.
24448     *
24449     * @param {boolean} [isScrubbing]
24450     *        whether the user is or is not scrubbing
24451     *
24452     * @return {boolean|undefined}
24453     *         - The value of scrubbing when getting
24454     *         - Nothing when setting
24455     */
24456    scrubbing(isScrubbing) {
24457      if (typeof isScrubbing === 'undefined') {
24458        return this.scrubbing_;
24459      }
24460      this.scrubbing_ = !!isScrubbing;
24461      this.techCall_('setScrubbing', this.scrubbing_);
24462      if (isScrubbing) {
24463        this.addClass('vjs-scrubbing');
24464      } else {
24465        this.removeClass('vjs-scrubbing');
24466      }
24467    }
24468
24469    /**
24470     * Get or set the current time (in seconds)
24471     *
24472     * @param {number|string} [seconds]
24473     *        The time to seek to in seconds
24474     *
24475     * @return {number|undefined}
24476     *         - the current time in seconds when getting
24477     *         - Nothing when setting
24478     */
24479    currentTime(seconds) {
24480      if (seconds === undefined) {
24481        // cache last currentTime and return. default to 0 seconds
24482        //
24483        // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
vendor: 4,287 bytes, lines 24484-24613
24484        // currentTime when scrubbing, but may not provide much performance benefit after all.
24485        // Should be tested. Also something has to read the actual current time or the cache will
24486        // never get updated.
24487        this.cache_.currentTime = this.techGet_('currentTime') || 0;
24488        return this.cache_.currentTime;
24489      }
24490      if (seconds < 0) {
24491        seconds = 0;
24492      }
24493      if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
24494        this.cache_.initTime = seconds;
24495        this.off('canplay', this.boundApplyInitTime_);
24496        this.one('canplay', this.boundApplyInitTime_);
24497        return;
24498      }
24499      this.techCall_('setCurrentTime', seconds);
24500      this.cache_.initTime = 0;
24501      if (isFinite(seconds)) {
24502        this.cache_.currentTime = Number(seconds);
24503      }
24504    }
24505
24506    /**
24507     * Apply the value of initTime stored in cache as currentTime.
24508     *
24509     * @private
24510     */
24511    applyInitTime_() {
24512      this.currentTime(this.cache_.initTime);
24513    }
24514
24515    /**
24516     * Normally gets the length in time of the video in seconds;
24517     * in all but the rarest use cases an argument will NOT be passed to the method
24518     *
24519     * > **NOTE**: The video must have started loading before the duration can be
24520     * known, and depending on preload behaviour may not be known until the video starts
24521     * playing.
24522     *
24523     * @fires Player#durationchange
24524     *
24525     * @param {number} [seconds]
24526     *        The duration of the video to set in seconds
24527     *
24528     * @return {number|undefined}
24529     *         - The duration of the video in seconds when getting
24530     *         - Nothing when setting
24531     */
24532    duration(seconds) {
24533      if (seconds === undefined) {
24534        // return NaN if the duration is not known
24535        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
24536      }
24537      seconds = parseFloat(seconds);
24538
24539      // Standardize on Infinity for signaling video is live
24540      if (seconds < 0) {
24541        seconds = Infinity;
24542      }
24543      if (seconds !== this.cache_.duration) {
24544        // Cache the last set value for optimized scrubbing
24545        this.cache_.duration = seconds;
24546        if (seconds === Infinity) {
24547          this.addClass('vjs-live');
24548        } else {
24549          this.removeClass('vjs-live');
24550        }
24551        if (!isNaN(seconds)) {
24552          // Do not fire durationchange unless the duration value is known.
24553          // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
24554
24555          /**
24556           * @event Player#durationchange
24557           * @type {Event}
24558           */
24559          this.trigger('durationchange');
24560        }
24561      }
24562    }
24563
24564    /**
24565     * Calculates how much time is left in the video. Not part
24566     * of the native video API.
24567     *
24568     * @return {number}
24569     *         The time remaining in seconds
24570     */
24571    remainingTime() {
24572      return this.duration() - this.currentTime();
24573    }
24574
24575    /**
24576     * A remaining time function that is intended to be used when
24577     * the time is to be displayed directly to the user.
24578     *
24579     * @return {number}
24580     *         The rounded time remaining in seconds
24581     */
24582    remainingTimeDisplay() {
24583      return Math.floor(this.duration()) - Math.floor(this.currentTime());
24584    }
24585
24586    //
24587    // Kind of like an array of portions of the video that have been downloaded.
24588
24589    /**
24590     * Get a TimeRange object with an array of the times of the video
24591     * that have been downloaded. If you just want the percent of the
24592     * video that's been downloaded, use bufferedPercent.
24593     *
24594     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
24595     *
24596     * @return { import('./utils/time').TimeRange }
24597     *         A mock {@link TimeRanges} object (following HTML spec)
24598     */
24599    buffered() {
24600      let buffered = this.techGet_('buffered');
24601      if (!buffered || !buffered.length) {
24602        buffered = createTimeRanges$1(0, 0);
24603      }
24604      return buffered;
24605    }
24606
24607    /**
24608     * Get the TimeRanges of the media that are currently available
24609     * for seeking to.
24610     *
24611     * @see [Seekable Spec]{@link https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable}
24612     *
24613     * @return { import('./utils/time').TimeRange 
vendor: 12,814 bytes, lines 24613-24985
24613}
24614     *         A mock {@link TimeRanges} object (following HTML spec)
24615     */
24616    seekable() {
24617      let seekable = this.techGet_('seekable');
24618      if (!seekable || !seekable.length) {
24619        seekable = createTimeRanges$1(0, 0);
24620      }
24621      return seekable;
24622    }
24623
24624    /**
24625     * Returns whether the player is in the "seeking" state.
24626     *
24627     * @return {boolean} True if the player is in the seeking state, false if not.
24628     */
24629    seeking() {
24630      return this.techGet_('seeking');
24631    }
24632
24633    /**
24634     * Returns whether the player is in the "ended" state.
24635     *
24636     * @return {boolean} True if the player is in the ended state, false if not.
24637     */
24638    ended() {
24639      return this.techGet_('ended');
24640    }
24641
24642    /**
24643     * Returns the current state of network activity for the element, from
24644     * the codes in the list below.
24645     * - NETWORK_EMPTY (numeric value 0)
24646     *   The element has not yet been initialised. All attributes are in
24647     *   their initial states.
24648     * - NETWORK_IDLE (numeric value 1)
24649     *   The element's resource selection algorithm is active and has
24650     *   selected a resource, but it is not actually using the network at
24651     *   this time.
24652     * - NETWORK_LOADING (numeric value 2)
24653     *   The user agent is actively trying to download data.
24654     * - NETWORK_NO_SOURCE (numeric value 3)
24655     *   The element's resource selection algorithm is active, but it has
24656     *   not yet found a resource to use.
24657     *
24658     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
24659     * @return {number} the current network activity state
24660     */
24661    networkState() {
24662      return this.techGet_('networkState');
24663    }
24664
24665    /**
24666     * Returns a value that expresses the current state of the element
24667     * with respect to rendering the current playback position, from the
24668     * codes in the list below.
24669     * - HAVE_NOTHING (numeric value 0)
24670     *   No information regarding the media resource is available.
24671     * - HAVE_METADATA (numeric value 1)
24672     *   Enough of the resource has been obtained that the duration of the
24673     *   resource is available.
24674     * - HAVE_CURRENT_DATA (numeric value 2)
24675     *   Data for the immediate current playback position is available.
24676     * - HAVE_FUTURE_DATA (numeric value 3)
24677     *   Data for the immediate current playback position is available, as
24678     *   well as enough data for the user agent to advance the current
24679     *   playback position in the direction of playback.
24680     * - HAVE_ENOUGH_DATA (numeric value 4)
24681     *   The user agent estimates that enough data is available for
24682     *   playback to proceed uninterrupted.
24683     *
24684     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
24685     * @return {number} the current playback rendering state
24686     */
24687    readyState() {
24688      return this.techGet_('readyState');
24689    }
24690
24691    /**
24692     * Get the percent (as a decimal) of the video that's been downloaded.
24693     * This method is not a part of the native HTML video API.
24694     *
24695     * @return {number}
24696     *         A decimal between 0 and 1 representing the percent
24697     *         that is buffered 0 being 0% and 1 being 100%
24698     */
24699    bufferedPercent() {
24700      return bufferedPercent(this.buffered(), this.duration());
24701    }
24702
24703    /**
24704     * Get the ending time of the last buffered time range
24705     * This is used in the progress bar to encapsulate all time ranges.
24706     *
24707     * @return {number}
24708     *         The end of the last buffered time range
24709     */
24710    bufferedEnd() {
24711      const buffered = this.buffered();
24712      const duration = this.duration();
24713      let end = buffered.end(buffered.length - 1);
24714      if (end > duration) {
24715        end = duration;
24716      }
24717      return end;
24718    }
24719
24720    /**
24721     * Get or set the current volume of the media
24722     *
24723     * @param  {number} [percentAsDecimal]
24724     *         The new volume as a decimal percent:
24725     *         - 0 is muted/0%/off
24726     *         - 1.0 is 100%/full
24727     *         - 0.5 is half volume or 50%
24728     *
24729     * @return {number|undefined}
24730     *         The current volume as a percent when getting
24731     */
24732    volume(percentAsDecimal) {
24733      let vol;
24734      if (percentAsDecimal !== undefined) {
24735        // Force value to between 0 and 1
24736        vol = Math.max(0, Math.min(1, percentAsDecimal));
24737        this.cache_.volume = vol;
24738        this.techCall_('setVolume', vol);
24739        if (vol > 0) {
24740          this.lastVolume_(vol);
24741        }
24742        return;
24743      }
24744
24745      // Default to 1 when returning current volume.
24746      vol = parseFloat(this.techGet_('volume'));
24747      return isNaN(vol) ? 1 : vol;
24748    }
24749
24750    /**
24751     * Get the current muted state, or turn mute on or off
24752     *
24753     * @param {boolean} [muted]
24754     *        - true to mute
24755     *        - false to unmute
24756     *
24757     * @return {boolean|undefined}
24758     *         - true if mute is on and getting
24759     *         - false if mute is off and getting
24760     *         - nothing if setting
24761     */
24762    muted(muted) {
24763      if (muted !== undefined) {
24764        this.techCall_('setMuted', muted);
24765        return;
24766      }
24767      return this.techGet_('muted') || false;
24768    }
24769
24770    /**
24771     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
24772     * indicates the state of muted on initial playback.
24773     *
24774     * ```js
24775     *   var myPlayer = videojs('some-player-id');
24776     *
24777     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
24778     *
24779     *   // get, should be false
24780     *   console.log(myPlayer.defaultMuted());
24781     *   // set to true
24782     *   myPlayer.defaultMuted(true);
24783     *   // get should be true
24784     *   console.log(myPlayer.defaultMuted());
24785     * ```
24786     *
24787     * @param {boolean} [defaultMuted]
24788     *        - true to mute
24789     *        - false to unmute
24790     *
24791     * @return {boolean|undefined}
24792     *         - true if defaultMuted is on and getting
24793     *         - false if defaultMuted is off and getting
24794     *         - Nothing when setting
24795     */
24796    defaultMuted(defaultMuted) {
24797      if (defaultMuted !== undefined) {
24798        this.techCall_('setDefaultMuted', defaultMuted);
24799      }
24800      return this.techGet_('defaultMuted') || false;
24801    }
24802
24803    /**
24804     * Get the last volume, or set it
24805     *
24806     * @param  {number} [percentAsDecimal]
24807     *         The new last volume as a decimal percent:
24808     *         - 0 is muted/0%/off
24809     *         - 1.0 is 100%/full
24810     *         - 0.5 is half volume or 50%
24811     *
24812     * @return {number|undefined}
24813     *         - The current value of lastVolume as a percent when getting
24814     *         - Nothing when setting
24815     *
24816     * @private
24817     */
24818    lastVolume_(percentAsDecimal) {
24819      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
24820        this.cache_.lastVolume = percentAsDecimal;
24821        return;
24822      }
24823      return this.cache_.lastVolume;
24824    }
24825
24826    /**
24827     * Check if current tech can support native fullscreen
24828     * (e.g. with built in controls like iOS)
24829     *
24830     * @return {boolean}
24831     *         if native fullscreen is supported
24832     */
24833    supportsFullScreen() {
24834      return this.techGet_('supportsFullScreen') || false;
24835    }
24836
24837    /**
24838     * Check if the player is in fullscreen mode or tell the player that it
24839     * is or is not in fullscreen mode.
24840     *
24841     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
24842     * property and instead document.fullscreenElement is used. But isFullscreen is
24843     * still a valuable property for internal player workings.
24844     *
24845     * @param  {boolean} [isFS]
24846     *         Set the players current fullscreen state
24847     *
24848     * @return {boolean|undefined}
24849     *         - true if fullscreen is on and getting
24850     *         - false if fullscreen is off and getting
24851     *         - Nothing when setting
24852     */
24853    isFullscreen(isFS) {
24854      if (isFS !== undefined) {
24855        const oldValue = this.isFullscreen_;
24856        this.isFullscreen_ = Boolean(isFS);
24857
24858        // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
24859        // this is the only place where we trigger fullscreenchange events for older browsers
24860        // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
24861        if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
24862          /**
24863             * @event Player#fullscreenchange
24864             * @type {Event}
24865             */
24866          this.trigger('fullscreenchange');
24867        }
24868        this.toggleFullscreenClass_();
24869        return;
24870      }
24871      return this.isFullscreen_;
24872    }
24873
24874    /**
24875     * Increase the size of the video to full screen
24876     * In some browsers, full screen is not supported natively, so it enters
24877     * "full window mode", where the video fills the browser window.
24878     * In browsers and devices that support native full screen, sometimes the
24879     * browser's default controls will be shown, and not the Video.js custom skin.
24880     * This includes most mobile devices (iOS, Android) and older versions of
24881     * Safari.
24882     *
24883     * @param  {Object} [fullscreenOptions]
24884     *         Override the player fullscreen options
24885     *
24886     * @fires Player#fullscreenchange
24887     */
24888    requestFullscreen(fullscreenOptions) {
24889      if (this.isInPictureInPicture()) {
24890        this.exitPictureInPicture();
24891      }
24892      const self = this;
24893      return new Promise((resolve, reject) => {
24894        function offHandler() {
24895          self.off('fullscreenerror', errorHandler);
24896          self.off('fullscreenchange', changeHandler);
24897        }
24898        function changeHandler() {
24899          offHandler();
24900          resolve();
24901        }
24902        function errorHandler(e, err) {
24903          offHandler();
24904          reject(err);
24905        }
24906        self.one('fullscreenchange', changeHandler);
24907        self.one('fullscreenerror', errorHandler);
24908        const promise = self.requestFullscreenHelper_(fullscreenOptions);
24909        if (promise) {
24910          promise.then(offHandler, offHandler);
24911          promise.then(resolve, reject);
24912        }
24913      });
24914    }
24915    requestFullscreenHelper_(fullscreenOptions) {
24916      let fsOptions;
24917
24918      // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
24919      // Use defaults or player configured option unless passed directly to this method.
24920      if (!this.fsApi_.prefixed) {
24921        fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
24922        if (fullscreenOptions !== undefined) {
24923          fsOptions = fullscreenOptions;
24924        }
24925      }
24926
24927      // This method works as follows:
24928      // 1. if a fullscreen api is available, use it
24929      //   1. call requestFullscreen with potential options
24930      //   2. if we got a promise from above, use it to update isFullscreen()
24931      // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
24932      //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
24933      // 3. otherwise, use "fullWindow" mode
24934      if (this.fsApi_.requestFullscreen) {
24935        const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
24936
24937        // Even on browsers with promise support this may not return a promise
24938        if (promise) {
24939          promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false));
24940        }
24941        return promise;
24942      } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
24943        // we can't take the video.js controls fullscreen but we can go fullscreen
24944        // with native controls
24945        this.techCall_('enterFullScreen');
24946      } else {
24947        // fullscreen isn't supported so we'll just stretch the video element to
24948        // fill the viewport
24949        this.enterFullWindow();
24950      }
24951    }
24952
24953    /**
24954     * Return the video to its normal size after having been in full screen mode
24955     *
24956     * @fires Player#fullscreenchange
24957     */
24958    exitFullscreen() {
24959      const self = this;
24960      return new Promise((resolve, reject) => {
24961        function offHandler() {
24962          self.off('fullscreenerror', errorHandler);
24963          self.off('fullscreenchange', changeHandler);
24964        }
24965        function changeHandler() {
24966          offHandler();
24967          resolve();
24968        }
24969        function errorHandler(e, err) {
24970          offHandler();
24971          reject(err);
24972        }
24973        self.one('fullscreenchange', changeHandler);
24974        self.one('fullscreenerror', errorHandler);
24975        const promise = self.exitFullscreenHelper_();
24976        if (promise) {
24977          promise.then(offHandler, offHandler);
24978          // map the promise to our resolve/reject methods
24979          promise.then(resolve, reject);
24980        }
24981      });
24982    }
24983    exitFullscreenHelper_() {
24984      if (this.fsApi_.requestFullscreen) {
24985        const promise = document
vendor: 10,438 bytes, lines 24985-25283
24985[this.fsApi_.exitFullscreen]();
24986
24987        // Even on browsers with promise support this may not return a promise
24988        if (promise) {
24989          // we're splitting the promise here, so, we want to catch the
24990          // potential error so that this chain doesn't have unhandled errors
24991          silencePromise(promise.then(() => this.isFullscreen(false)));
24992        }
24993        return promise;
24994      } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
24995        this.techCall_('exitFullScreen');
24996      } else {
24997        this.exitFullWindow();
24998      }
24999    }
25000
25001    /**
25002     * When fullscreen isn't supported we can stretch the
25003     * video container to as wide as the browser will let us.
25004     *
25005     * @fires Player#enterFullWindow
25006     */
25007    enterFullWindow() {
25008      this.isFullscreen(true);
25009      this.isFullWindow = true;
25010
25011      // Storing original doc overflow value to return to when fullscreen is off
25012      this.docOrigOverflow = document.documentElement.style.overflow;
25013
25014      // Add listener for esc key to exit fullscreen
25015      on(document, 'keydown', this.boundFullWindowOnEscKey_);
25016
25017      // Hide any scroll bars
25018      document.documentElement.style.overflow = 'hidden';
25019
25020      // Apply fullscreen styles
25021      addClass(document.body, 'vjs-full-window');
25022
25023      /**
25024       * @event Player#enterFullWindow
25025       * @type {Event}
25026       */
25027      this.trigger('enterFullWindow');
25028    }
25029
25030    /**
25031     * Check for call to either exit full window or
25032     * full screen on ESC key
25033     *
25034     * @param {string} event
25035     *        Event to check for key press
25036     */
25037    fullWindowOnEscKey(event) {
25038      if (keycode.isEventKey(event, 'Esc')) {
25039        if (this.isFullscreen() === true) {
25040          if (!this.isFullWindow) {
25041            this.exitFullscreen();
25042          } else {
25043            this.exitFullWindow();
25044          }
25045        }
25046      }
25047    }
25048
25049    /**
25050     * Exit full window
25051     *
25052     * @fires Player#exitFullWindow
25053     */
25054    exitFullWindow() {
25055      this.isFullscreen(false);
25056      this.isFullWindow = false;
25057      off(document, 'keydown', this.boundFullWindowOnEscKey_);
25058
25059      // Unhide scroll bars.
25060      document.documentElement.style.overflow = this.docOrigOverflow;
25061
25062      // Remove fullscreen styles
25063      removeClass(document.body, 'vjs-full-window');
25064
25065      // Resize the box, controller, and poster to original sizes
25066      // this.positionAll();
25067      /**
25068       * @event Player#exitFullWindow
25069       * @type {Event}
25070       */
25071      this.trigger('exitFullWindow');
25072    }
25073
25074    /**
25075     * Get or set disable Picture-in-Picture mode.
25076     *
25077     * @param {boolean} [value]
25078     *                  - true will disable Picture-in-Picture mode
25079     *                  - false will enable Picture-in-Picture mode
25080     */
25081    disablePictureInPicture(value) {
25082      if (value === undefined) {
25083        return this.techGet_('disablePictureInPicture');
25084      }
25085      this.techCall_('setDisablePictureInPicture', value);
25086      this.options_.disablePictureInPicture = value;
25087      this.trigger('disablepictureinpicturechanged');
25088    }
25089
25090    /**
25091     * Check if the player is in Picture-in-Picture mode or tell the player that it
25092     * is or is not in Picture-in-Picture mode.
25093     *
25094     * @param  {boolean} [isPiP]
25095     *         Set the players current Picture-in-Picture state
25096     *
25097     * @return {boolean|undefined}
25098     *         - true if Picture-in-Picture is on and getting
25099     *         - false if Picture-in-Picture is off and getting
25100     *         - nothing if setting
25101     */
25102    isInPictureInPicture(isPiP) {
25103      if (isPiP !== undefined) {
25104        this.isInPictureInPicture_ = !!isPiP;
25105        this.togglePictureInPictureClass_();
25106        return;
25107      }
25108      return !!this.isInPictureInPicture_;
25109    }
25110
25111    /**
25112     * Create a floating video window always on top of other windows so that users may
25113     * continue consuming media while they interact with other content sites, or
25114     * applications on their device.
25115     *
25116     * This can use document picture-in-picture or element picture in picture
25117     *
25118     * Set `enableDocumentPictureInPicture` to `true` to use docPiP on a supported browser
25119     * Else set `disablePictureInPicture` to `false` to disable elPiP on a supported browser
25120     *
25121     *
25122     * @see [Spec]{@link https://w3c.github.io/picture-in-picture/}
25123     * @see [Spec]{@link https://wicg.github.io/document-picture-in-picture/}
25124     *
25125     * @fires Player#enterpictureinpicture
25126     *
25127     * @return {Promise}
25128     *         A promise with a Picture-in-Picture window.
25129     */
25130    requestPictureInPicture() {
25131      if (this.options_.enableDocumentPictureInPicture && window.documentPictureInPicture) {
25132        const pipContainer = document.createElement(this.el().tagName);
25133        pipContainer.classList = this.el().classList;
25134        pipContainer.classList.add('vjs-pip-container');
25135        if (this.posterImage) {
25136          pipContainer.appendChild(this.posterImage.el().cloneNode(true));
25137        }
25138        if (this.titleBar) {
25139          pipContainer.appendChild(this.titleBar.el().cloneNode(true));
25140        }
25141        pipContainer.appendChild(createEl('p', {
25142          className: 'vjs-pip-text'
25143        }, {}, this.localize('Playing in picture-in-picture')));
25144        return window.documentPictureInPicture.requestWindow({
25145          // The aspect ratio won't be correct, Chrome bug https://crbug.com/1407629
25146          width: this.videoWidth(),
25147          height: this.videoHeight()
25148        }).then(pipWindow => {
25149          copyStyleSheetsToWindow(pipWindow);
25150          this.el_.parentNode.insertBefore(pipContainer, this.el_);
25151          pipWindow.document.body.appendChild(this.el_);
25152          pipWindow.document.body.classList.add('vjs-pip-window');
25153          this.player_.isInPictureInPicture(true);
25154          this.player_.trigger('enterpictureinpicture');
25155
25156          // Listen for the PiP closing event to move the video back.
25157          pipWindow.addEventListener('pagehide', event => {
25158            const pipVideo = event.target.querySelector('.video-js');
25159            pipContainer.parentNode.replaceChild(pipVideo, pipContainer);
25160            this.player_.isInPictureInPicture(false);
25161            this.player_.trigger('leavepictureinpicture');
25162          });
25163          return pipWindow;
25164        });
25165      }
25166      if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
25167        /**
25168         * This event fires when the player enters picture in picture mode
25169         *
25170         * @event Player#enterpictureinpicture
25171         * @type {Event}
25172         */
25173        return this.techGet_('requestPictureInPicture');
25174      }
25175      return Promise.reject('No PiP mode is available');
25176    }
25177
25178    /**
25179     * Exit Picture-in-Picture mode.
25180     *
25181     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
25182     *
25183     * @fires Player#leavepictureinpicture
25184     *
25185     * @return {Promise}
25186     *         A promise.
25187     */
25188    exitPictureInPicture() {
25189      if (window.documentPictureInPicture && window.documentPictureInPicture.window) {
25190        // With documentPictureInPicture, Player#leavepictureinpicture is fired in the pagehide handler
25191        window.documentPictureInPicture.window.close();
25192        return Promise.resolve();
25193      }
25194      if ('pictureInPictureEnabled' in document) {
25195        /**
25196         * This event fires when the player leaves picture in picture mode
25197         *
25198         * @event Player#leavepictureinpicture
25199         * @type {Event}
25200         */
25201        return document.exitPictureInPicture();
25202      }
25203    }
25204
25205    /**
25206     * Called when this Player has focus and a key gets pressed down, or when
25207     * any Component of this player receives a key press that it doesn't handle.
25208     * This allows player-wide hotkeys (either as defined below, or optionally
25209     * by an external function).
25210     *
25211     * @param {KeyboardEvent} event
25212     *        The `keydown` event that caused this function to be called.
25213     *
25214     * @listens keydown
25215     */
25216    handleKeyDown(event) {
25217      const {
25218        userActions
25219      } = this.options_;
25220
25221      // Bail out if hotkeys are not configured.
25222      if (!userActions || !userActions.hotkeys) {
25223        return;
25224      }
25225
25226      // Function that determines whether or not to exclude an element from
25227      // hotkeys handling.
25228      const excludeElement = el => {
25229        const tagName = el.tagName.toLowerCase();
25230
25231        // The first and easiest test is for `contenteditable` elements.
25232        if (el.isContentEditable) {
25233          return true;
25234        }
25235
25236        // Inputs matching these types will still trigger hotkey handling as
25237        // they are not text inputs.
25238        const allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
25239        if (tagName === 'input') {
25240          return allowedInputTypes.indexOf(el.type) === -1;
25241        }
25242
25243        // The final test is by tag name. These tags will be excluded entirely.
25244        const excludedTags = ['textarea'];
25245        return excludedTags.indexOf(tagName) !== -1;
25246      };
25247
25248      // Bail out if the user is focused on an interactive form element.
25249      if (excludeElement(this.el_.ownerDocument.activeElement)) {
25250        return;
25251      }
25252      if (typeof userActions.hotkeys === 'function') {
25253        userActions.hotkeys.call(this, event);
25254      } else {
25255        this.handleHotkeys(event);
25256      }
25257    }
25258
25259    /**
25260     * Called when this Player receives a hotkey keydown event.
25261     * Supported player-wide hotkeys are:
25262     *
25263     *   f          - toggle fullscreen
25264     *   m          - toggle mute
25265     *   k or Space - toggle play/pause
25266     *
25267     * @param {Event} event
25268     *        The `keydown` event that caused this function to be called.
25269     */
25270    handleHotkeys(event) {
25271      const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
25272
25273      // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
25274      const {
25275        fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
25276        muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
25277        playPauseKey = keydownEvent => keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space')
25278      } = hotkeys;
25279      if (fullscreenKey.call(this, event)) {
25280        event.preventDefault();
25281        event.stopPropagation();
25282        const FSToggle = Component$1.getComponent('FullscreenToggle');
25283        if (document
vendor: 23,977 bytes, lines 25283-26038
25283[this.fsApi_.fullscreenEnabled] !== false) {
25284          FSToggle.prototype.handleClick.call(this, event);
25285        }
25286      } else if (muteKey.call(this, event)) {
25287        event.preventDefault();
25288        event.stopPropagation();
25289        const MuteToggle = Component$1.getComponent('MuteToggle');
25290        MuteToggle.prototype.handleClick.call(this, event);
25291      } else if (playPauseKey.call(this, event)) {
25292        event.preventDefault();
25293        event.stopPropagation();
25294        const PlayToggle = Component$1.getComponent('PlayToggle');
25295        PlayToggle.prototype.handleClick.call(this, event);
25296      }
25297    }
25298
25299    /**
25300     * Check whether the player can play a given mimetype
25301     *
25302     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
25303     *
25304     * @param {string} type
25305     *        The mimetype to check
25306     *
25307     * @return {string}
25308     *         'probably', 'maybe', or '' (empty string)
25309     */
25310    canPlayType(type) {
25311      let can;
25312
25313      // Loop through each playback technology in the options order
25314      for (let i = 0, j = this.options_.techOrder; i < j.length; i++) {
25315        const techName = j[i];
25316        let tech = Tech.getTech(techName);
25317
25318        // Support old behavior of techs being registered as components.
25319        // Remove once that deprecated behavior is removed.
25320        if (!tech) {
25321          tech = Component$1.getComponent(techName);
25322        }
25323
25324        // Check if the current tech is defined before continuing
25325        if (!tech) {
25326          log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
25327          continue;
25328        }
25329
25330        // Check if the browser supports this technology
25331        if (tech.isSupported()) {
25332          can = tech.canPlayType(type);
25333          if (can) {
25334            return can;
25335          }
25336        }
25337      }
25338      return '';
25339    }
25340
25341    /**
25342     * Select source based on tech-order or source-order
25343     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
25344     * defaults to tech-order selection
25345     *
25346     * @param {Array} sources
25347     *        The sources for a media asset
25348     *
25349     * @return {Object|boolean}
25350     *         Object of source and tech order or false
25351     */
25352    selectSource(sources) {
25353      // Get only the techs specified in `techOrder` that exist and are supported by the
25354      // current platform
25355      const techs = this.options_.techOrder.map(techName => {
25356        return [techName, Tech.getTech(techName)];
25357      }).filter(([techName, tech]) => {
25358        // Check if the current tech is defined before continuing
25359        if (tech) {
25360          // Check if the browser supports this technology
25361          return tech.isSupported();
25362        }
25363        log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
25364        return false;
25365      });
25366
25367      // Iterate over each `innerArray` element once per `outerArray` element and execute
25368      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
25369      // that value.
25370      const findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
25371        let found;
25372        outerArray.some(outerChoice => {
25373          return innerArray.some(innerChoice => {
25374            found = tester(outerChoice, innerChoice);
25375            if (found) {
25376              return true;
25377            }
25378          });
25379        });
25380        return found;
25381      };
25382      let foundSourceAndTech;
25383      const flip = fn => (a, b) => fn(b, a);
25384      const finder = ([techName, tech], source) => {
25385        if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
25386          return {
25387            source,
25388            tech: techName
25389          };
25390        }
25391      };
25392
25393      // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
25394      // to select from them based on their priority.
25395      if (this.options_.sourceOrder) {
25396        // Source-first ordering
25397        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
25398      } else {
25399        // Tech-first ordering
25400        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
25401      }
25402      return foundSourceAndTech || false;
25403    }
25404
25405    /**
25406     * Executes source setting and getting logic
25407     *
25408     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25409     *        A SourceObject, an array of SourceObjects, or a string referencing
25410     *        a URL to a media source. It is _highly recommended_ that an object
25411     *        or array of objects is used here, so that source selection
25412     *        algorithms can take the `type` into account.
25413     *
25414     *        If not provided, this method acts as a getter.
25415     * @param {boolean} [isRetry]
25416     *        Indicates whether this is being called internally as a result of a retry
25417     *
25418     * @return {string|undefined}
25419     *         If the `source` argument is missing, returns the current source
25420     *         URL. Otherwise, returns nothing/undefined.
25421     */
25422    handleSrc_(source, isRetry) {
25423      // getter usage
25424      if (typeof source === 'undefined') {
25425        return this.cache_.src || '';
25426      }
25427
25428      // Reset retry behavior for new source
25429      if (this.resetRetryOnError_) {
25430        this.resetRetryOnError_();
25431      }
25432
25433      // filter out invalid sources and turn our source into
25434      // an array of source objects
25435      const sources = filterSource(source);
25436
25437      // if a source was passed in then it is invalid because
25438      // it was filtered to a zero length Array. So we have to
25439      // show an error
25440      if (!sources.length) {
25441        this.setTimeout(function () {
25442          this.error({
25443            code: 4,
25444            message: this.options_.notSupportedMessage
25445          });
25446        }, 0);
25447        return;
25448      }
25449
25450      // initial sources
25451      this.changingSrc_ = true;
25452
25453      // Only update the cached source list if we are not retrying a new source after error,
25454      // since in that case we want to include the failed source(s) in the cache
25455      if (!isRetry) {
25456        this.cache_.sources = sources;
25457      }
25458      this.updateSourceCaches_(sources[0]);
25459
25460      // middlewareSource is the source after it has been changed by middleware
25461      setSource(this, sources[0], (middlewareSource, mws) => {
25462        this.middleware_ = mws;
25463
25464        // since sourceSet is async we have to update the cache again after we select a source since
25465        // the source that is selected could be out of order from the cache update above this callback.
25466        if (!isRetry) {
25467          this.cache_.sources = sources;
25468        }
25469        this.updateSourceCaches_(middlewareSource);
25470        const err = this.src_(middlewareSource);
25471        if (err) {
25472          if (sources.length > 1) {
25473            return this.handleSrc_(sources.slice(1));
25474          }
25475          this.changingSrc_ = false;
25476
25477          // We need to wrap this in a timeout to give folks a chance to add error event handlers
25478          this.setTimeout(function () {
25479            this.error({
25480              code: 4,
25481              message: this.options_.notSupportedMessage
25482            });
25483          }, 0);
25484
25485          // we could not find an appropriate tech, but let's still notify the delegate that this is it
25486          // this needs a better comment about why this is needed
25487          this.triggerReady();
25488          return;
25489        }
25490        setTech(mws, this.tech_);
25491      });
25492
25493      // Try another available source if this one fails before playback.
25494      if (sources.length > 1) {
25495        const retry = () => {
25496          // Remove the error modal
25497          this.error(null);
25498          this.handleSrc_(sources.slice(1), true);
25499        };
25500        const stopListeningForErrors = () => {
25501          this.off('error', retry);
25502        };
25503        this.one('error', retry);
25504        this.one('playing', stopListeningForErrors);
25505        this.resetRetryOnError_ = () => {
25506          this.off('error', retry);
25507          this.off('playing', stopListeningForErrors);
25508        };
25509      }
25510    }
25511
25512    /**
25513     * Get or set the video source.
25514     *
25515     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25516     *        A SourceObject, an array of SourceObjects, or a string referencing
25517     *        a URL to a media source. It is _highly recommended_ that an object
25518     *        or array of objects is used here, so that source selection
25519     *        algorithms can take the `type` into account.
25520     *
25521     *        If not provided, this method acts as a getter.
25522     *
25523     * @return {string|undefined}
25524     *         If the `source` argument is missing, returns the current source
25525     *         URL. Otherwise, returns nothing/undefined.
25526     */
25527    src(source) {
25528      return this.handleSrc_(source, false);
25529    }
25530
25531    /**
25532     * Set the source object on the tech, returns a boolean that indicates whether
25533     * there is a tech that can play the source or not
25534     *
25535     * @param {Tech~SourceObject} source
25536     *        The source object to set on the Tech
25537     *
25538     * @return {boolean}
25539     *         - True if there is no Tech to playback this source
25540     *         - False otherwise
25541     *
25542     * @private
25543     */
25544    src_(source) {
25545      const sourceTech = this.selectSource([source]);
25546      if (!sourceTech) {
25547        return true;
25548      }
25549      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
25550        this.changingSrc_ = true;
25551        // load this technology with the chosen source
25552        this.loadTech_(sourceTech.tech, sourceTech.source);
25553        this.tech_.ready(() => {
25554          this.changingSrc_ = false;
25555        });
25556        return false;
25557      }
25558
25559      // wait until the tech is ready to set the source
25560      // and set it synchronously if possible (#2326)
25561      this.ready(function () {
25562        // The setSource tech method was added with source handlers
25563        // so older techs won't support it
25564        // We need to check the direct prototype for the case where subclasses
25565        // of the tech do not support source handlers
25566        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
25567          this.techCall_('setSource', source);
25568        } else {
25569          this.techCall_('src', source.src);
25570        }
25571        this.changingSrc_ = false;
25572      }, true);
25573      return false;
25574    }
25575
25576    /**
25577     * Begin loading the src data.
25578     */
25579    load() {
25580      // Workaround to use the load method with the VHS.
25581      // Does not cover the case when the load method is called directly from the mediaElement.
25582      if (this.tech_ && this.tech_.vhs) {
25583        this.src(this.currentSource());
25584        return;
25585      }
25586      this.techCall_('load');
25587    }
25588
25589    /**
25590     * Reset the player. Loads the first tech in the techOrder,
25591     * removes all the text tracks in the existing `tech`,
25592     * and calls `reset` on the `tech`.
25593     */
25594    reset() {
25595      if (this.paused()) {
25596        this.doReset_();
25597      } else {
25598        const playPromise = this.play();
25599        silencePromise(playPromise.then(() => this.doReset_()));
25600      }
25601    }
25602    doReset_() {
25603      if (this.tech_) {
25604        this.tech_.clearTracks('text');
25605      }
25606      this.removeClass('vjs-playing');
25607      this.addClass('vjs-paused');
25608      this.resetCache_();
25609      this.poster('');
25610      this.loadTech_(this.options_.techOrder[0], null);
25611      this.techCall_('reset');
25612      this.resetControlBarUI_();
25613      this.error(null);
25614      if (this.titleBar) {
25615        this.titleBar.update({
25616          title: undefined,
25617          description: undefined
25618        });
25619      }
25620      if (isEvented(this)) {
25621        this.trigger('playerreset');
25622      }
25623    }
25624
25625    /**
25626     * Reset Control Bar's UI by calling sub-methods that reset
25627     * all of Control Bar's components
25628     */
25629    resetControlBarUI_() {
25630      this.resetProgressBar_();
25631      this.resetPlaybackRate_();
25632      this.resetVolumeBar_();
25633    }
25634
25635    /**
25636     * Reset tech's progress so progress bar is reset in the UI
25637     */
25638    resetProgressBar_() {
25639      this.currentTime(0);
25640      const {
25641        currentTimeDisplay,
25642        durationDisplay,
25643        progressControl,
25644        remainingTimeDisplay
25645      } = this.controlBar || {};
25646      const {
25647        seekBar
25648      } = progressControl || {};
25649      if (currentTimeDisplay) {
25650        currentTimeDisplay.updateContent();
25651      }
25652      if (durationDisplay) {
25653        durationDisplay.updateContent();
25654      }
25655      if (remainingTimeDisplay) {
25656        remainingTimeDisplay.updateContent();
25657      }
25658      if (seekBar) {
25659        seekBar.update();
25660        if (seekBar.loadProgressBar) {
25661          seekBar.loadProgressBar.update();
25662        }
25663      }
25664    }
25665
25666    /**
25667     * Reset Playback ratio
25668     */
25669    resetPlaybackRate_() {
25670      this.playbackRate(this.defaultPlaybackRate());
25671      this.handleTechRateChange_();
25672    }
25673
25674    /**
25675     * Reset Volume bar
25676     */
25677    resetVolumeBar_() {
25678      this.volume(1.0);
25679      this.trigger('volumechange');
25680    }
25681
25682    /**
25683     * Returns all of the current source objects.
25684     *
25685     * @return {Tech~SourceObject[]}
25686     *         The current source objects
25687     */
25688    currentSources() {
25689      const source = this.currentSource();
25690      const sources = [];
25691
25692      // assume `{}` or `{ src }`
25693      if (Object.keys(source).length !== 0) {
25694        sources.push(source);
25695      }
25696      return this.cache_.sources || sources;
25697    }
25698
25699    /**
25700     * Returns the current source object.
25701     *
25702     * @return {Tech~SourceObject}
25703     *         The current source object
25704     */
25705    currentSource() {
25706      return this.cache_.source || {};
25707    }
25708
25709    /**
25710     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
25711     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
25712     *
25713     * @return {string}
25714     *         The current source
25715     */
25716    currentSrc() {
25717      return this.currentSource() && this.currentSource().src || '';
25718    }
25719
25720    /**
25721     * Get the current source type e.g. video/mp4
25722     * This can allow you rebuild the current source object so that you could load the same
25723     * source and tech later
25724     *
25725     * @return {string}
25726     *         The source MIME type
25727     */
25728    currentType() {
25729      return this.currentSource() && this.currentSource().type || '';
25730    }
25731
25732    /**
25733     * Get or set the preload attribute
25734     *
25735     * @param {'none'|'auto'|'metadata'} [value]
25736     *        Preload mode to pass to tech
25737     *
25738     * @return {string|undefined}
25739     *         - The preload attribute value when getting
25740     *         - Nothing when setting
25741     */
25742    preload(value) {
25743      if (value !== undefined) {
25744        this.techCall_('setPreload', value);
25745        this.options_.preload = value;
25746        return;
25747      }
25748      return this.techGet_('preload');
25749    }
25750
25751    /**
25752     * Get or set the autoplay option. When this is a boolean it will
25753     * modify the attribute on the tech. When this is a string the attribute on
25754     * the tech will be removed and `Player` will handle autoplay on loadstarts.
25755     *
25756     * @param {boolean|'play'|'muted'|'any'} [value]
25757     *        - true: autoplay using the browser behavior
25758     *        - false: do not autoplay
25759     *        - 'play': call play() on every loadstart
25760     *        - 'muted': call muted() then play() on every loadstart
25761     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
25762     *        - *: values other than those listed here will be set `autoplay` to true
25763     *
25764     * @return {boolean|string|undefined}
25765     *         - The current value of autoplay when getting
25766     *         - Nothing when setting
25767     */
25768    autoplay(value) {
25769      // getter usage
25770      if (value === undefined) {
25771        return this.options_.autoplay || false;
25772      }
25773      let techAutoplay;
25774
25775      // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
25776      if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
25777        this.options_.autoplay = value;
25778        this.manualAutoplay_(typeof value === 'string' ? value : 'play');
25779        techAutoplay = false;
25780
25781        // any falsy value sets autoplay to false in the browser,
25782        // lets do the same
25783      } else if (!value) {
25784        this.options_.autoplay = false;
25785
25786        // any other value (ie truthy) sets autoplay to true
25787      } else {
25788        this.options_.autoplay = true;
25789      }
25790      techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
25791
25792      // if we don't have a tech then we do not queue up
25793      // a setAutoplay call on tech ready. We do this because the
25794      // autoplay option will be passed in the constructor and we
25795      // do not need to set it twice
25796      if (this.tech_) {
25797        this.techCall_('setAutoplay', techAutoplay);
25798      }
25799    }
25800
25801    /**
25802     * Set or unset the playsinline attribute.
25803     * Playsinline tells the browser that non-fullscreen playback is preferred.
25804     *
25805     * @param {boolean} [value]
25806     *        - true means that we should try to play inline by default
25807     *        - false means that we should use the browser's default playback mode,
25808     *          which in most cases is inline. iOS Safari is a notable exception
25809     *          and plays fullscreen by default.
25810     *
25811     * @return {string|undefined}
25812     *         - the current value of playsinline
25813     *         - Nothing when setting
25814     *
25815     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
25816     */
25817    playsinline(value) {
25818      if (value !== undefined) {
25819        this.techCall_('setPlaysinline', value);
25820        this.options_.playsinline = value;
25821      }
25822      return this.techGet_('playsinline');
25823    }
25824
25825    /**
25826     * Get or set the loop attribute on the video element.
25827     *
25828     * @param {boolean} [value]
25829     *        - true means that we should loop the video
25830     *        - false means that we should not loop the video
25831     *
25832     * @return {boolean|undefined}
25833     *         - The current value of loop when getting
25834     *         - Nothing when setting
25835     */
25836    loop(value) {
25837      if (value !== undefined) {
25838        this.techCall_('setLoop', value);
25839        this.options_.loop = value;
25840        return;
25841      }
25842      return this.techGet_('loop');
25843    }
25844
25845    /**
25846     * Get or set the poster image source url
25847     *
25848     * @fires Player#posterchange
25849     *
25850     * @param {string} [src]
25851     *        Poster image source URL
25852     *
25853     * @return {string|undefined}
25854     *         - The current value of poster when getting
25855     *         - Nothing when setting
25856     */
25857    poster(src) {
25858      if (src === undefined) {
25859        return this.poster_;
25860      }
25861
25862      // The correct way to remove a poster is to set as an empty string
25863      // other falsey values will throw errors
25864      if (!src) {
25865        src = '';
25866      }
25867      if (src === this.poster_) {
25868        return;
25869      }
25870
25871      // update the internal poster variable
25872      this.poster_ = src;
25873
25874      // update the tech's poster
25875      this.techCall_('setPoster', src);
25876      this.isPosterFromTech_ = false;
25877
25878      // alert components that the poster has been set
25879      /**
25880       * This event fires when the poster image is changed on the player.
25881       *
25882       * @event Player#posterchange
25883       * @type {Event}
25884       */
25885      this.trigger('posterchange');
25886    }
25887
25888    /**
25889     * Some techs (e.g. YouTube) can provide a poster source in an
25890     * asynchronous way. We want the poster component to use this
25891     * poster source so that it covers up the tech's controls.
25892     * (YouTube's play button). However we only want to use this
25893     * source if the player user hasn't set a poster through
25894     * the normal APIs.
25895     *
25896     * @fires Player#posterchange
25897     * @listens Tech#posterchange
25898     * @private
25899     */
25900    handleTechPosterChange_() {
25901      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
25902        const newPoster = this.tech_.poster() || '';
25903        if (newPoster !== this.poster_) {
25904          this.poster_ = newPoster;
25905          this.isPosterFromTech_ = true;
25906
25907          // Let components know the poster has changed
25908          this.trigger('posterchange');
25909        }
25910      }
25911    }
25912
25913    /**
25914     * Get or set whether or not the controls are showing.
25915     *
25916     * @fires Player#controlsenabled
25917     *
25918     * @param {boolean} [bool]
25919     *        - true to turn controls on
25920     *        - false to turn controls off
25921     *
25922     * @return {boolean|undefined}
25923     *         - The current value of controls when getting
25924     *         - Nothing when setting
25925     */
25926    controls(bool) {
25927      if (bool === undefined) {
25928        return !!this.controls_;
25929      }
25930      bool = !!bool;
25931
25932      // Don't trigger a change event unless it actually changed
25933      if (this.controls_ === bool) {
25934        return;
25935      }
25936      this.controls_ = bool;
25937      if (this.usingNativeControls()) {
25938        this.techCall_('setControls', bool);
25939      }
25940      if (this.controls_) {
25941        this.removeClass('vjs-controls-disabled');
25942        this.addClass('vjs-controls-enabled');
25943        /**
25944         * @event Player#controlsenabled
25945         * @type {Event}
25946         */
25947        this.trigger('controlsenabled');
25948        if (!this.usingNativeControls()) {
25949          this.addTechControlsListeners_();
25950        }
25951      } else {
25952        this.removeClass('vjs-controls-enabled');
25953        this.addClass('vjs-controls-disabled');
25954        /**
25955         * @event Player#controlsdisabled
25956         * @type {Event}
25957         */
25958        this.trigger('controlsdisabled');
25959        if (!this.usingNativeControls()) {
25960          this.removeTechControlsListeners_();
25961        }
25962      }
25963    }
25964
25965    /**
25966     * Toggle native controls on/off. Native controls are the controls built into
25967     * devices (e.g. default iPhone controls) or other techs
25968     * (e.g. Vimeo Controls)
25969     * **This should only be set by the current tech, because only the tech knows
25970     * if it can support native controls**
25971     *
25972     * @fires Player#usingnativecontrols
25973     * @fires Player#usingcustomcontrols
25974     *
25975     * @param {boolean} [bool]
25976     *        - true to turn native controls on
25977     *        - false to turn native controls off
25978     *
25979     * @return {boolean|undefined}
25980     *         - The current value of native controls when getting
25981     *         - Nothing when setting
25982     */
25983    usingNativeControls(bool) {
25984      if (bool === undefined) {
25985        return !!this.usingNativeControls_;
25986      }
25987      bool = !!bool;
25988
25989      // Don't trigger a change event unless it actually changed
25990      if (this.usingNativeControls_ === bool) {
25991        return;
25992      }
25993      this.usingNativeControls_ = bool;
25994      if (this.usingNativeControls_) {
25995        this.addClass('vjs-using-native-controls');
25996
25997        /**
25998         * player is using the native device controls
25999         *
26000         * @event Player#usingnativecontrols
26001         * @type {Event}
26002         */
26003        this.trigger('usingnativecontrols');
26004      } else {
26005        this.removeClass('vjs-using-native-controls');
26006
26007        /**
26008         * player is using the custom HTML controls
26009         *
26010         * @event Player#usingcustomcontrols
26011         * @type {Event}
26012         */
26013        this.trigger('usingcustomcontrols');
26014      }
26015    }
26016
26017    /**
26018     * Set or get the current MediaError
26019     *
26020     * @fires Player#error
26021     *
26022     * @param  {MediaError|string|number} [err]
26023     *         A MediaError or a string/number to be turned
26024     *         into a MediaError
26025     *
26026     * @return {MediaError|null|undefined}
26027     *         - The current MediaError when getting (or null)
26028     *         - Nothing when setting
26029     */
26030    error(err) {
26031      if (err === undefined) {
26032        return this.error_ || null;
26033      }
26034
26035      // allow hooks to modify error object
26036      hooks('beforeerror').forEach(hookFunction => {
26037        const newErr = hookFunction(this, err);
26038        if (!(isObject
vendor: 3,495 bytes, lines 26038-26147
26038$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
26039          this.log.error('please return a value that MediaError expects in beforeerror hooks');
26040          return;
26041        }
26042        err = newErr;
26043      });
26044
26045      // Suppress the first error message for no compatible source until
26046      // user interaction
26047      if (this.options_.suppressNotSupportedError && err && err.code === 4) {
26048        const triggerSuppressedError = function () {
26049          this.error(err);
26050        };
26051        this.options_.suppressNotSupportedError = false;
26052        this.any(['click', 'touchstart'], triggerSuppressedError);
26053        this.one('loadstart', function () {
26054          this.off(['click', 'touchstart'], triggerSuppressedError);
26055        });
26056        return;
26057      }
26058
26059      // restoring to default
26060      if (err === null) {
26061        this.error_ = null;
26062        this.removeClass('vjs-error');
26063        if (this.errorDisplay) {
26064          this.errorDisplay.close();
26065        }
26066        return;
26067      }
26068      this.error_ = new MediaError(err);
26069
26070      // add the vjs-error classname to the player
26071      this.addClass('vjs-error');
26072
26073      // log the name of the error type and any message
26074      // IE11 logs "[object object]" and required you to expand message to see error object
26075      log$1.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
26076
26077      /**
26078       * @event Player#error
26079       * @type {Event}
26080       */
26081      this.trigger('error');
26082
26083      // notify hooks of the per player error
26084      hooks('error').forEach(hookFunction => hookFunction(this, this.error_));
26085      return;
26086    }
26087
26088    /**
26089     * Report user activity
26090     *
26091     * @param {Object} event
26092     *        Event object
26093     */
26094    reportUserActivity(event) {
26095      this.userActivity_ = true;
26096    }
26097
26098    /**
26099     * Get/set if user is active
26100     *
26101     * @fires Player#useractive
26102     * @fires Player#userinactive
26103     *
26104     * @param {boolean} [bool]
26105     *        - true if the user is active
26106     *        - false if the user is inactive
26107     *
26108     * @return {boolean|undefined}
26109     *         - The current value of userActive when getting
26110     *         - Nothing when setting
26111     */
26112    userActive(bool) {
26113      if (bool === undefined) {
26114        return this.userActive_;
26115      }
26116      bool = !!bool;
26117      if (bool === this.userActive_) {
26118        return;
26119      }
26120      this.userActive_ = bool;
26121      if (this.userActive_) {
26122        this.userActivity_ = true;
26123        this.removeClass('vjs-user-inactive');
26124        this.addClass('vjs-user-active');
26125        /**
26126         * @event Player#useractive
26127         * @type {Event}
26128         */
26129        this.trigger('useractive');
26130        return;
26131      }
26132
26133      // Chrome/Safari/IE have bugs where when you change the cursor it can
26134      // trigger a mousemove event. This causes an issue when you're hiding
26135      // the cursor when the user is inactive, and a mousemove signals user
26136      // activity. Making it impossible to go into inactive mode. Specifically
26137      // this happens in fullscreen when we really need to hide the cursor.
26138      //
26139      // When this gets resolved in ALL browsers it can be removed
26140      // https://code.google.com/p/chromium/issues/detail?id=103041
26141      if (this.tech_) {
26142        this.tech_.one('mousemove', function (e) {
26143          e.stopPropagation();
26144          e.preventDefault();
26145        });
26146      }
26147      this.userActivity_ = false;
vendor: 6,717 bytes, lines 26148-26330
26148      this.removeClass('vjs-user-active');
26149      this.addClass('vjs-user-inactive');
26150      /**
26151       * @event Player#userinactive
26152       * @type {Event}
26153       */
26154      this.trigger('userinactive');
26155    }
26156
26157    /**
26158     * Listen for user activity based on timeout value
26159     *
26160     * @private
26161     */
26162    listenForUserActivity_() {
26163      let mouseInProgress;
26164      let lastMoveX;
26165      let lastMoveY;
26166      const handleActivity = bind_(this, this.reportUserActivity);
26167      const handleMouseMove = function (e) {
26168        // #1068 - Prevent mousemove spamming
26169        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
26170        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
26171          lastMoveX = e.screenX;
26172          lastMoveY = e.screenY;
26173          handleActivity();
26174        }
26175      };
26176      const handleMouseDown = function () {
26177        handleActivity();
26178        // For as long as the they are touching the device or have their mouse down,
26179        // we consider them active even if they're not moving their finger or mouse.
26180        // So we want to continue to update that they are active
26181        this.clearInterval(mouseInProgress);
26182        // Setting userActivity=true now and setting the interval to the same time
26183        // as the activityCheck interval (250) should ensure we never miss the
26184        // next activityCheck
26185        mouseInProgress = this.setInterval(handleActivity, 250);
26186      };
26187      const handleMouseUpAndMouseLeave = function (event) {
26188        handleActivity();
26189        // Stop the interval that maintains activity if the mouse/touch is down
26190        this.clearInterval(mouseInProgress);
26191      };
26192
26193      // Any mouse movement will be considered user activity
26194      this.on('mousedown', handleMouseDown);
26195      this.on('mousemove', handleMouseMove);
26196      this.on('mouseup', handleMouseUpAndMouseLeave);
26197      this.on('mouseleave', handleMouseUpAndMouseLeave);
26198      const controlBar = this.getChild('controlBar');
26199
26200      // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
26201      // controlBar would no longer be hidden by default timeout.
26202      if (controlBar && !IS_IOS && !IS_ANDROID) {
26203        controlBar.on('mouseenter', function (event) {
26204          if (this.player().options_.inactivityTimeout !== 0) {
26205            this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
26206          }
26207          this.player().options_.inactivityTimeout = 0;
26208        });
26209        controlBar.on('mouseleave', function (event) {
26210          this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
26211        });
26212      }
26213
26214      // Listen for keyboard navigation
26215      // Shouldn't need to use inProgress interval because of key repeat
26216      this.on('keydown', handleActivity);
26217      this.on('keyup', handleActivity);
26218
26219      // Run an interval every 250 milliseconds instead of stuffing everything into
26220      // the mousemove/touchmove function itself, to prevent performance degradation.
26221      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
26222      // then gets picked up by this loop
26223      // http://ejohn.org/blog/learning-from-twitter/
26224      let inactivityTimeout;
26225
26226      /** @this Player */
26227      const activityCheck = function () {
26228        // Check to see if mouse/touch activity has happened
26229        if (!this.userActivity_) {
26230          return;
26231        }
26232
26233        // Reset the activity tracker
26234        this.userActivity_ = false;
26235
26236        // If the user state was inactive, set the state to active
26237        this.userActive(true);
26238
26239        // Clear any existing inactivity timeout to start the timer over
26240        this.clearTimeout(inactivityTimeout);
26241        const timeout = this.options_.inactivityTimeout;
26242        if (timeout <= 0) {
26243          return;
26244        }
26245
26246        // In <timeout> milliseconds, if no more activity has occurred the
26247        // user will be considered inactive
26248        inactivityTimeout = this.setTimeout(function () {
26249          // Protect against the case where the inactivityTimeout can trigger just
26250          // before the next user activity is picked up by the activity check loop
26251          // causing a flicker
26252          if (!this.userActivity_) {
26253            this.userActive(false);
26254          }
26255        }, timeout);
26256      };
26257      this.setInterval(activityCheck, 250);
26258    }
26259
26260    /**
26261     * Gets or sets the current playback rate. A playback rate of
26262     * 1.0 represents normal speed and 0.5 would indicate half-speed
26263     * playback, for instance.
26264     *
26265     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
26266     *
26267     * @param {number} [rate]
26268     *       New playback rate to set.
26269     *
26270     * @return {number|undefined}
26271     *         - The current playback rate when getting or 1.0
26272     *         - Nothing when setting
26273     */
26274    playbackRate(rate) {
26275      if (rate !== undefined) {
26276        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
26277        // that is registered above
26278        this.techCall_('setPlaybackRate', rate);
26279        return;
26280      }
26281      if (this.tech_ && this.tech_.featuresPlaybackRate) {
26282        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
26283      }
26284      return 1.0;
26285    }
26286
26287    /**
26288     * Gets or sets the current default playback rate. A default playback rate of
26289     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
26290     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
26291     * not the current playbackRate.
26292     *
26293     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
26294     *
26295     * @param {number} [rate]
26296     *       New default playback rate to set.
26297     *
26298     * @return {number|undefined}
26299     *         - The default playback rate when getting or 1.0
26300     *         - Nothing when setting
26301     */
26302    defaultPlaybackRate(rate) {
26303      if (rate !== undefined) {
26304        return this.techCall_('setDefaultPlaybackRate', rate);
26305      }
26306      if (this.tech_ && this.tech_.featuresPlaybackRate) {
26307        return this.techGet_('defaultPlaybackRate');
26308      }
26309      return 1.0;
26310    }
26311
26312    /**
26313     * Gets or sets the audio flag
26314     *
26315     * @param {boolean} [bool]
26316     *        - true signals that this is an audio player
26317     *        - false signals that this is not an audio player
26318     *
26319     * @return {boolean|undefined}
26320     *         - The current value of isAudio when getting
26321     *         - Nothing when setting
26322     */
26323    isAudio(bool) {
26324      if (bool !== undefined) {
26325        this.isAudio_ = !!bool;
26326        return;
26327      }
26328      return !!this.isAudio_;
26329    }
26330    
26330enableAudioOnlyUI_() {
26331      // Update styling immediately to show the control bar so we can get its height
26332      this.addClass('vjs-audio-only-mode');
26333      const playerChildren = this.children();
26334      const controlBar = this.getChild('ControlBar');
26335      const controlBarHeight = controlBar && controlBar.currentHeight();
26336
26337      // Hide all player components except the control bar. Control bar components
26338      // needed only for video are hidden with CSS
26339      playerChildren.forEach(child => {
26340        if (child === controlBar) {
26341          return;
26342        }
26343        if (child.el_ && !child.hasClass('vjs-hidden')) {
26344          child.hide();
26345          this.audioOnlyCache_.hiddenChildren.push(child);
26346        }
26347      });
26348      this.audioOnlyCache_.playerHeight = this.currentHeight();
26349
26350      // Set the player height the same as the control bar
26351      this.height(controlBarHeight);
26352      this.trigger('audioonlymodechange');
26353    }
26354    disableAudioOnlyUI_() {
26355      this.removeClass('vjs-audio-only-mode');
26356
26357      // Show player components that were previously hidden
vendor: 4,729 bytes, lines 26358-26491
26358      this.audioOnlyCache_.hiddenChildren.forEach(child => child.show());
26359
26360      // Reset player height
26361      this.height(this.audioOnlyCache_.playerHeight);
26362      this.trigger('audioonlymodechange');
26363    }
26364
26365    /**
26366     * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
26367     *
26368     * Setting this to `true` will hide all player components except the control bar,
26369     * as well as control bar components needed only for video.
26370     *
26371     * @param {boolean} [value]
26372     *         The value to set audioOnlyMode to.
26373     *
26374     * @return {Promise|boolean}
26375     *        A Promise is returned when setting the state, and a boolean when getting
26376     *        the present state
26377     */
26378    audioOnlyMode(value) {
26379      if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
26380        return this.audioOnlyMode_;
26381      }
26382      this.audioOnlyMode_ = value;
26383
26384      // Enable Audio Only Mode
26385      if (value) {
26386        const exitPromises = [];
26387
26388        // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
26389        if (this.isInPictureInPicture()) {
26390          exitPromises.push(this.exitPictureInPicture());
26391        }
26392        if (this.isFullscreen()) {
26393          exitPromises.push(this.exitFullscreen());
26394        }
26395        if (this.audioPosterMode()) {
26396          exitPromises.push(this.audioPosterMode(false));
26397        }
26398        return Promise.all(exitPromises).then(() => this.enableAudioOnlyUI_());
26399      }
26400
26401      // Disable Audio Only Mode
26402      return Promise.resolve().then(() => this.disableAudioOnlyUI_());
26403    }
26404    enablePosterModeUI_() {
26405      // Hide the video element and show the poster image to enable posterModeUI
26406      const tech = this.tech_ && this.tech_;
26407      tech.hide();
26408      this.addClass('vjs-audio-poster-mode');
26409      this.trigger('audiopostermodechange');
26410    }
26411    disablePosterModeUI_() {
26412      // Show the video element and hide the poster image to disable posterModeUI
26413      const tech = this.tech_ && this.tech_;
26414      tech.show();
26415      this.removeClass('vjs-audio-poster-mode');
26416      this.trigger('audiopostermodechange');
26417    }
26418
26419    /**
26420     * Get the current audioPosterMode state or set audioPosterMode to true or false
26421     *
26422     * @param {boolean} [value]
26423     *         The value to set audioPosterMode to.
26424     *
26425     * @return {Promise|boolean}
26426     *         A Promise is returned when setting the state, and a boolean when getting
26427     *        the present state
26428     */
26429    audioPosterMode(value) {
26430      if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
26431        return this.audioPosterMode_;
26432      }
26433      this.audioPosterMode_ = value;
26434      if (value) {
26435        if (this.audioOnlyMode()) {
26436          const audioOnlyModePromise = this.audioOnlyMode(false);
26437          return audioOnlyModePromise.then(() => {
26438            // enable audio poster mode after audio only mode is disabled
26439            this.enablePosterModeUI_();
26440          });
26441        }
26442        return Promise.resolve().then(() => {
26443          // enable audio poster mode
26444          this.enablePosterModeUI_();
26445        });
26446      }
26447      return Promise.resolve().then(() => {
26448        // disable audio poster mode
26449        this.disablePosterModeUI_();
26450      });
26451    }
26452
26453    /**
26454     * A helper method for adding a {@link TextTrack} to our
26455     * {@link TextTrackList}.
26456     *
26457     * In addition to the W3C settings we allow adding additional info through options.
26458     *
26459     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
26460     *
26461     * @param {string} [kind]
26462     *        the kind of TextTrack you are adding
26463     *
26464     * @param {string} [label]
26465     *        the label to give the TextTrack label
26466     *
26467     * @param {string} [language]
26468     *        the language to set on the TextTrack
26469     *
26470     * @return {TextTrack|undefined}
26471     *         the TextTrack that was added or undefined
26472     *         if there is no tech
26473     */
26474    addTextTrack(kind, label, language) {
26475      if (this.tech_) {
26476        return this.tech_.addTextTrack(kind, label, language);
26477      }
26478    }
26479
26480    /**
26481     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
26482     *
26483     * @param {Object} options
26484     *        Options to pass to {@link HTMLTrackElement} during creation. See
26485     *        {@link HTMLTrackElement} for object properties that you should use.
26486     *
26487     * @param {boolean} [manualCleanup=false] if set to true, the TextTrack will not be removed
26488     *                                        from the TextTrackList and HtmlTrackElementList
26489     *                                        after a source change
26490     *
26491     * @return { import('./tracks/html-track-element').default 
vendor: 5,649 bytes, lines 26491-26677
26491}
26492     *         the HTMLTrackElement that was created and added
26493     *         to the HtmlTrackElementList and the remote
26494     *         TextTrackList
26495     *
26496     */
26497    addRemoteTextTrack(options, manualCleanup) {
26498      if (this.tech_) {
26499        return this.tech_.addRemoteTextTrack(options, manualCleanup);
26500      }
26501    }
26502
26503    /**
26504     * Remove a remote {@link TextTrack} from the respective
26505     * {@link TextTrackList} and {@link HtmlTrackElementList}.
26506     *
26507     * @param {Object} track
26508     *        Remote {@link TextTrack} to remove
26509     *
26510     * @return {undefined}
26511     *         does not return anything
26512     */
26513    removeRemoteTextTrack(obj = {}) {
26514      let {
26515        track
26516      } = obj;
26517      if (!track) {
26518        track = obj;
26519      }
26520
26521      // destructure the input into an object with a track argument, defaulting to arguments[0]
26522      // default the whole argument to an empty object if nothing was passed in
26523
26524      if (this.tech_) {
26525        return this.tech_.removeRemoteTextTrack(track);
26526      }
26527    }
26528
26529    /**
26530     * Gets available media playback quality metrics as specified by the W3C's Media
26531     * Playback Quality API.
26532     *
26533     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
26534     *
26535     * @return {Object|undefined}
26536     *         An object with supported media playback quality metrics or undefined if there
26537     *         is no tech or the tech does not support it.
26538     */
26539    getVideoPlaybackQuality() {
26540      return this.techGet_('getVideoPlaybackQuality');
26541    }
26542
26543    /**
26544     * Get video width
26545     *
26546     * @return {number}
26547     *         current video width
26548     */
26549    videoWidth() {
26550      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
26551    }
26552
26553    /**
26554     * Get video height
26555     *
26556     * @return {number}
26557     *         current video height
26558     */
26559    videoHeight() {
26560      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
26561    }
26562
26563    /**
26564     * Set or get the player's language code.
26565     *
26566     * Changing the language will trigger
26567     * [languagechange]{@link Player#event:languagechange}
26568     * which Components can use to update control text.
26569     * ClickableComponent will update its control text by default on
26570     * [languagechange]{@link Player#event:languagechange}.
26571     *
26572     * @fires Player#languagechange
26573     *
26574     * @param {string} [code]
26575     *        the language code to set the player to
26576     *
26577     * @return {string|undefined}
26578     *         - The current language code when getting
26579     *         - Nothing when setting
26580     */
26581    language(code) {
26582      if (code === undefined) {
26583        return this.language_;
26584      }
26585      if (this.language_ !== String(code).toLowerCase()) {
26586        this.language_ = String(code).toLowerCase();
26587
26588        // during first init, it's possible some things won't be evented
26589        if (isEvented(this)) {
26590          /**
26591          * fires when the player language change
26592          *
26593          * @event Player#languagechange
26594          * @type {Event}
26595          */
26596          this.trigger('languagechange');
26597        }
26598      }
26599    }
26600
26601    /**
26602     * Get the player's language dictionary
26603     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
26604     * Languages specified directly in the player options have precedence
26605     *
26606     * @return {Array}
26607     *         An array of of supported languages
26608     */
26609    languages() {
26610      return merge$2(Player.prototype.options_.languages, this.languages_);
26611    }
26612
26613    /**
26614     * returns a JavaScript object representing the current track
26615     * information. **DOES not return it as JSON**
26616     *
26617     * @return {Object}
26618     *         Object representing the current of track info
26619     */
26620    toJSON() {
26621      const options = merge$2(this.options_);
26622      const tracks = options.tracks;
26623      options.tracks = [];
26624      for (let i = 0; i < tracks.length; i++) {
26625        let track = tracks[i];
26626
26627        // deep merge tracks and null out player so no circular references
26628        track = merge$2(track);
26629        track.player = undefined;
26630        options.tracks[i] = track;
26631      }
26632      return options;
26633    }
26634
26635    /**
26636     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
26637     * component) that immediately overlays the player with arbitrary
26638     * content and removes itself when closed.
26639     *
26640     * @param {string|Function|Element|Array|null} content
26641     *        Same as {@link ModalDialog#content}'s param of the same name.
26642     *        The most straight-forward usage is to provide a string or DOM
26643     *        element.
26644     *
26645     * @param {Object} [options]
26646     *        Extra options which will be passed on to the {@link ModalDialog}.
26647     *
26648     * @return {ModalDialog}
26649     *         the {@link ModalDialog} that was created
26650     */
26651    createModal(content, options) {
26652      options = options || {};
26653      options.content = content || '';
26654      const modal = new ModalDialog(this, options);
26655      this.addChild(modal);
26656      modal.on('dispose', () => {
26657        this.removeChild(modal);
26658      });
26659      modal.open();
26660      return modal;
26661    }
26662
26663    /**
26664     * Change breakpoint classes when the player resizes.
26665     *
26666     * @private
26667     */
26668    updateCurrentBreakpoint_() {
26669      if (!this.responsive()) {
26670        return;
26671      }
26672      const currentBreakpoint = this.currentBreakpoint();
26673      const currentWidth = this.currentWidth();
26674      for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
26675        const candidateBreakpoint = BREAKPOINT_ORDER[i];
26676        const maxWidth = this.breakpoints_[candidateBreakpoint];
26677        if (currentWidth <= maxWidth) {
vendor: 7,457 bytes, lines 26678-26887
26678          // The current breakpoint did not change, nothing to do.
26679          if (currentBreakpoint === candidateBreakpoint) {
26680            return;
26681          }
26682
26683          // Only remove a class if there is a current breakpoint.
26684          if (currentBreakpoint) {
26685            this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
26686          }
26687          this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
26688          this.breakpoint_ = candidateBreakpoint;
26689          break;
26690        }
26691      }
26692    }
26693
26694    /**
26695     * Removes the current breakpoint.
26696     *
26697     * @private
26698     */
26699    removeCurrentBreakpoint_() {
26700      const className = this.currentBreakpointClass();
26701      this.breakpoint_ = '';
26702      if (className) {
26703        this.removeClass(className);
26704      }
26705    }
26706
26707    /**
26708     * Get or set breakpoints on the player.
26709     *
26710     * Calling this method with an object or `true` will remove any previous
26711     * custom breakpoints and start from the defaults again.
26712     *
26713     * @param  {Object|boolean} [breakpoints]
26714     *         If an object is given, it can be used to provide custom
26715     *         breakpoints. If `true` is given, will set default breakpoints.
26716     *         If this argument is not given, will simply return the current
26717     *         breakpoints.
26718     *
26719     * @param  {number} [breakpoints.tiny]
26720     *         The maximum width for the "vjs-layout-tiny" class.
26721     *
26722     * @param  {number} [breakpoints.xsmall]
26723     *         The maximum width for the "vjs-layout-x-small" class.
26724     *
26725     * @param  {number} [breakpoints.small]
26726     *         The maximum width for the "vjs-layout-small" class.
26727     *
26728     * @param  {number} [breakpoints.medium]
26729     *         The maximum width for the "vjs-layout-medium" class.
26730     *
26731     * @param  {number} [breakpoints.large]
26732     *         The maximum width for the "vjs-layout-large" class.
26733     *
26734     * @param  {number} [breakpoints.xlarge]
26735     *         The maximum width for the "vjs-layout-x-large" class.
26736     *
26737     * @param  {number} [breakpoints.huge]
26738     *         The maximum width for the "vjs-layout-huge" class.
26739     *
26740     * @return {Object}
26741     *         An object mapping breakpoint names to maximum width values.
26742     */
26743    breakpoints(breakpoints) {
26744      // Used as a getter.
26745      if (breakpoints === undefined) {
26746        return Object.assign(this.breakpoints_);
26747      }
26748      this.breakpoint_ = '';
26749      this.breakpoints_ = Object.assign({}, DEFAULT_BREAKPOINTS, breakpoints);
26750
26751      // When breakpoint definitions change, we need to update the currently
26752      // selected breakpoint.
26753      this.updateCurrentBreakpoint_();
26754
26755      // Clone the breakpoints before returning.
26756      return Object.assign(this.breakpoints_);
26757    }
26758
26759    /**
26760     * Get or set a flag indicating whether or not this player should adjust
26761     * its UI based on its dimensions.
26762     *
26763     * @param  {boolean} [value]
26764     *         Should be `true` if the player should adjust its UI based on its
26765     *         dimensions; otherwise, should be `false`.
26766     *
26767     * @return {boolean|undefined}
26768     *         Will be `true` if this player should adjust its UI based on its
26769     *         dimensions; otherwise, will be `false`.
26770     *         Nothing if setting
26771     */
26772    responsive(value) {
26773      // Used as a getter.
26774      if (value === undefined) {
26775        return this.responsive_;
26776      }
26777      value = Boolean(value);
26778      const current = this.responsive_;
26779
26780      // Nothing changed.
26781      if (value === current) {
26782        return;
26783      }
26784
26785      // The value actually changed, set it.
26786      this.responsive_ = value;
26787
26788      // Start listening for breakpoints and set the initial breakpoint if the
26789      // player is now responsive.
26790      if (value) {
26791        this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
26792        this.updateCurrentBreakpoint_();
26793
26794        // Stop listening for breakpoints if the player is no longer responsive.
26795      } else {
26796        this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
26797        this.removeCurrentBreakpoint_();
26798      }
26799      return value;
26800    }
26801
26802    /**
26803     * Get current breakpoint name, if any.
26804     *
26805     * @return {string}
26806     *         If there is currently a breakpoint set, returns a the key from the
26807     *         breakpoints object matching it. Otherwise, returns an empty string.
26808     */
26809    currentBreakpoint() {
26810      return this.breakpoint_;
26811    }
26812
26813    /**
26814     * Get the current breakpoint class name.
26815     *
26816     * @return {string}
26817     *         The matching class name (e.g. `"vjs-layout-tiny"` or
26818     *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
26819     *         there is no current breakpoint.
26820     */
26821    currentBreakpointClass() {
26822      return BREAKPOINT_CLASSES[this.breakpoint_] || '';
26823    }
26824
26825    /**
26826     * An object that describes a single piece of media.
26827     *
26828     * Properties that are not part of this type description will be retained; so,
26829     * this can be viewed as a generic metadata storage mechanism as well.
26830     *
26831     * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
26832     * @typedef  {Object} Player~MediaObject
26833     *
26834     * @property {string} [album]
26835     *           Unused, except if this object is passed to the `MediaSession`
26836     *           API.
26837     *
26838     * @property {string} [artist]
26839     *           Unused, except if this object is passed to the `MediaSession`
26840     *           API.
26841     *
26842     * @property {Object[]} [artwork]
26843     *           Unused, except if this object is passed to the `MediaSession`
26844     *           API. If not specified, will be populated via the `poster`, if
26845     *           available.
26846     *
26847     * @property {string} [poster]
26848     *           URL to an image that will display before playback.
26849     *
26850     * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
26851     *           A single source object, an array of source objects, or a string
26852     *           referencing a URL to a media source. It is _highly recommended_
26853     *           that an object or array of objects is used here, so that source
26854     *           selection algorithms can take the `type` into account.
26855     *
26856     * @property {string} [title]
26857     *           Unused, except if this object is passed to the `MediaSession`
26858     *           API.
26859     *
26860     * @property {Object[]} [textTracks]
26861     *           An array of objects to be used to create text tracks, following
26862     *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
26863     *           For ease of removal, these will be created as "remote" text
26864     *           tracks and set to automatically clean up on source changes.
26865     *
26866     *           These objects may have properties like `src`, `kind`, `label`,
26867     *           and `language`, see {@link Tech#createRemoteTextTrack}.
26868     */
26869
26870    /**
26871     * Populate the player using a {@link Player~MediaObject|MediaObject}.
26872     *
26873     * @param  {Player~MediaObject} media
26874     *         A media object.
26875     *
26876     * @param  {Function} ready
26877     *         A callback to be called when the player is ready.
26878     */
26879    loadMedia(media, ready) {
26880      if (!media || typeof media !== 'object') {
26881        return;
26882      }
26883      const crossOrigin = this.crossOrigin();
26884      this.reset();
26885
26886      // Clone the media object so it cannot be mutated from outside.
26887      this.cache_.media = merge$
vendor: 8,471 bytes, lines 26887-27188
268872(media);
26888      const {
26889        artist,
26890        artwork,
26891        description,
26892        poster,
26893        src,
26894        textTracks,
26895        title
26896      } = this.cache_.media;
26897
26898      // If `artwork` is not given, create it using `poster`.
26899      if (!artwork && poster) {
26900        this.cache_.media.artwork = [{
26901          src: poster,
26902          type: getMimetype(poster)
26903        }];
26904      }
26905      if (crossOrigin) {
26906        this.crossOrigin(crossOrigin);
26907      }
26908      if (src) {
26909        this.src(src);
26910      }
26911      if (poster) {
26912        this.poster(poster);
26913      }
26914      if (Array.isArray(textTracks)) {
26915        textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
26916      }
26917      if (this.titleBar) {
26918        this.titleBar.update({
26919          title,
26920          description: description || artist || ''
26921        });
26922      }
26923      this.ready(ready);
26924    }
26925
26926    /**
26927     * Get a clone of the current {@link Player~MediaObject} for this player.
26928     *
26929     * If the `loadMedia` method has not been used, will attempt to return a
26930     * {@link Player~MediaObject} based on the current state of the player.
26931     *
26932     * @return {Player~MediaObject}
26933     */
26934    getMedia() {
26935      if (!this.cache_.media) {
26936        const poster = this.poster();
26937        const src = this.currentSources();
26938        const textTracks = Array.prototype.map.call(this.remoteTextTracks(), tt => ({
26939          kind: tt.kind,
26940          label: tt.label,
26941          language: tt.language,
26942          src: tt.src
26943        }));
26944        const media = {
26945          src,
26946          textTracks
26947        };
26948        if (poster) {
26949          media.poster = poster;
26950          media.artwork = [{
26951            src: media.poster,
26952            type: getMimetype(media.poster)
26953          }];
26954        }
26955        return media;
26956      }
26957      return merge$2(this.cache_.media);
26958    }
26959
26960    /**
26961     * Gets tag settings
26962     *
26963     * @param {Element} tag
26964     *        The player tag
26965     *
26966     * @return {Object}
26967     *         An object containing all of the settings
26968     *         for a player tag
26969     */
26970    static getTagSettings(tag) {
26971      const baseOptions = {
26972        sources: [],
26973        tracks: []
26974      };
26975      const tagOptions = getAttributes(tag);
26976      const dataSetup = tagOptions['data-setup'];
26977      if (hasClass(tag, 'vjs-fill')) {
26978        tagOptions.fill = true;
26979      }
26980      if (hasClass(tag, 'vjs-fluid')) {
26981        tagOptions.fluid = true;
26982      }
26983
26984      // Check if data-setup attr exists.
26985      if (dataSetup !== null) {
26986        // Parse options JSON
26987        // If empty string, make it a parsable json object.
26988        const [err, data] = tuple(dataSetup || '{}');
26989        if (err) {
26990          log$1.error(err);
26991        }
26992        Object.assign(tagOptions, data);
26993      }
26994      Object.assign(baseOptions, tagOptions);
26995
26996      // Get tag children settings
26997      if (tag.hasChildNodes()) {
26998        const children = tag.childNodes;
26999        for (let i = 0, j = children.length; i < j; i++) {
27000          const child = children[i];
27001          // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
27002          const childName = child.nodeName.toLowerCase();
27003          if (childName === 'source') {
27004            baseOptions.sources.push(getAttributes(child));
27005          } else if (childName === 'track') {
27006            baseOptions.tracks.push(getAttributes(child));
27007          }
27008        }
27009      }
27010      return baseOptions;
27011    }
27012
27013    /**
27014     * Set debug mode to enable/disable logs at info level.
27015     *
27016     * @param {boolean} enabled
27017     * @fires Player#debugon
27018     * @fires Player#debugoff
27019     * @return {boolean|undefined}
27020     */
27021    debug(enabled) {
27022      if (enabled === undefined) {
27023        return this.debugEnabled_;
27024      }
27025      if (enabled) {
27026        this.trigger('debugon');
27027        this.previousLogLevel_ = this.log.level;
27028        this.log.level('debug');
27029        this.debugEnabled_ = true;
27030      } else {
27031        this.trigger('debugoff');
27032        this.log.level(this.previousLogLevel_);
27033        this.previousLogLevel_ = undefined;
27034        this.debugEnabled_ = false;
27035      }
27036    }
27037
27038    /**
27039     * Set or get current playback rates.
27040     * Takes an array and updates the playback rates menu with the new items.
27041     * Pass in an empty array to hide the menu.
27042     * Values other than arrays are ignored.
27043     *
27044     * @fires Player#playbackrateschange
27045     * @param {number[]} newRates
27046     *                   The new rates that the playback rates menu should update to.
27047     *                   An empty array will hide the menu
27048     * @return {number[]} When used as a getter will return the current playback rates
27049     */
27050    playbackRates(newRates) {
27051      if (newRates === undefined) {
27052        return this.cache_.playbackRates;
27053      }
27054
27055      // ignore any value that isn't an array
27056      if (!Array.isArray(newRates)) {
27057        return;
27058      }
27059
27060      // ignore any arrays that don't only contain numbers
27061      if (!newRates.every(rate => typeof rate === 'number')) {
27062        return;
27063      }
27064      this.cache_.playbackRates = newRates;
27065
27066      /**
27067      * fires when the playback rates in a player are changed
27068      *
27069      * @event Player#playbackrateschange
27070      * @type {Event}
27071      */
27072      this.trigger('playbackrateschange');
27073    }
27074  }
27075
27076  /**
27077   * Get the {@link VideoTrackList}
27078   *
27079   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
27080   *
27081   * @return {VideoTrackList}
27082   *         the current video track list
27083   *
27084   * @method Player.prototype.videoTracks
27085   */
27086
27087  /**
27088   * Get the {@link AudioTrackList}
27089   *
27090   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
27091   *
27092   * @return {AudioTrackList}
27093   *         the current audio track list
27094   *
27095   * @method Player.prototype.audioTracks
27096   */
27097
27098  /**
27099   * Get the {@link TextTrackList}
27100   *
27101   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
27102   *
27103   * @return {TextTrackList}
27104   *         the current text track list
27105   *
27106   * @method Player.prototype.textTracks
27107   */
27108
27109  /**
27110   * Get the remote {@link TextTrackList}
27111   *
27112   * @return {TextTrackList}
27113   *         The current remote text track list
27114   *
27115   * @method Player.prototype.remoteTextTracks
27116   */
27117
27118  /**
27119   * Get the remote {@link HtmlTrackElementList} tracks.
27120   *
27121   * @return {HtmlTrackElementList}
27122   *         The current remote text track element list
27123   *
27124   * @method Player.prototype.remoteTextTrackEls
27125   */
27126
27127  ALL.names.forEach(function (name) {
27128    const props = ALL[name];
27129    Player.prototype[props.getterName] = function () {
27130      if (this.tech_) {
27131        return this.tech_[props.getterName]();
27132      }
27133
27134      // if we have not yet loadTech_, we create {video,audio,text}Tracks_
27135      // these will be passed to the tech during loading
27136      this[props.privateName] = this[props.privateName] || new props.ListClass();
27137      return this[props.privateName];
27138    };
27139  });
27140
27141  /**
27142   * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
27143   * sets the `crossOrigin` property on the `<video>` tag to control the CORS
27144   * behavior.
27145   *
27146   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
27147   *
27148   * @param {string} [value]
27149   *        The value to set the `Player`'s crossorigin to. If an argument is
27150   *        given, must be one of `anonymous` or `use-credentials`.
27151   *
27152   * @return {string|undefined}
27153   *         - The current crossorigin value of the `Player` when getting.
27154   *         - undefined when setting
27155   */
27156  Player.prototype.crossorigin = Player.prototype.crossOrigin;
27157
27158  /**
27159   * Global enumeration of players.
27160   *
27161   * The keys are the player IDs and the values are either the {@link Player}
27162   * instance or `null` for disposed players.
27163   *
27164   * @type {Object}
27165   */
27166  Player.players = {};
27167  const navigator = window.navigator;
27168
27169  /*
27170   * Player instance options, surfaced using options
27171   * options = Player.prototype.options_
27172   * Make changes in options, not here.
27173   *
27174   * @type {Object}
27175   * @private
27176   */
27177  Player.prototype.options_ = {
27178    // Default order of fallback technology
27179    techOrder: Tech.defaultTechOrder_,
27180    html5: {},
27181    // enable sourceset by default
27182    enableSourceset: true,
27183    // default inactivity timeout
27184    inactivityTimeout: 2000,
27185    // default playback rates
27186    playbackRates: [],
27187    // Add playback rate selection by adding rates
27188    // 'playbackRates': [0.5, 1, 1.5, 2],
27189    liveui: false,
27190    // Included control sets
27191    children: ['mediaLoader', 'posterImage', 'titleBar', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
27192    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
27193    // locales and their language translations
27194    languages: {},
27195    // Default message to show when a video cannot be played.
27196    notSupportedMessage: 'No compatible source was found for this media.',
27197    normalizeAutoplay: false,
27198    fullscreen: {
27199      options: {
27200        navigationUI: 'hide'
27201      }
27202    },
27203    breakpoints: {},
27204    responsive: false,
27205    audioOnlyMode: false,
27206    audioPosterMode: false,
27207    // Default smooth seeking to false
27208    enableSmoothSeeking: false
27209  };
27210  TECH_EVENTS_RETRIGGER.forEach(function (event) {
27211    Player.prototype[`handleTech${toTitleCase$1(event)}_`] = function () {
27212      return this.trigger(event);
27213    };
27214  });
27215
27216  /**
27217   * Fired when the player has initial duration and dimension information
27218   *
27219   * @event Player#loadedmetadata
27220   * @type {Event}
27221   */
27222
27223  /**
27224   * Fired when the player has downloaded data at the current playback position
27225   *
27226   * @event Player#loadeddata
27227   * @type {Event}
27228   */
27229
27230  /**
27231   * Fired when the current playback position has changed *
27232   * During playback this is fired every 15-250 milliseconds, depending on the
27233   * playback technology in use.
27234   *
27235   * @event Player#timeupdate
27236   * @type {Event}
27237   */
27238
27239  /**
27240   * Fired when the volume changes
27241   *
27242   * @event Player#volumechange
27243   * @type {Event}
27244   */
27245
27246  /**
27247   * Reports whether or not a player has a plugin available.
27248   *
27249   * This does not report whether or not the plugin has ever been initialized
27250   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
27251   *
27252   * @method Player#hasPlugin
27253   * @param  {string}  name
27254   *         The name of a plugin.
27255   *
27256   * @return {boolean}
27257   *         Whether or not this player has the requested plugin available.
27258   */
27259
27260  /**
27261   * Reports whether or not a player is using a plugin by name.
27262   *
27263   * For basic plugins, this only reports whether the plugin has _ever_ been
27264   * initialized on this player.
27265   *
27266   * @method Player#usingPlugin
27267   * @param  {string} name
27268   *         The name of a plugin.
27269   *
27270   * @return {boolean}
27271   *         Whether or not this player is using the requested plugin.
27272   */
27273
27274  Component$1.registerComponent('Player', Player
vendor: 7,826 bytes, lines 27274-27549
27274);
27275
27276  /**
27277   * @file plugin.js
27278   */
27279
27280  /**
27281   * The base plugin name.
27282   *
27283   * @private
27284   * @constant
27285   * @type {string}
27286   */
27287  const BASE_PLUGIN_NAME = 'plugin';
27288
27289  /**
27290   * The key on which a player's active plugins cache is stored.
27291   *
27292   * @private
27293   * @constant
27294   * @type     {string}
27295   */
27296  const PLUGIN_CACHE_KEY = 'activePlugins_';
27297
27298  /**
27299   * Stores registered plugins in a private space.
27300   *
27301   * @private
27302   * @type    {Object}
27303   */
27304  const pluginStorage = {};
27305
27306  /**
27307   * Reports whether or not a plugin has been registered.
27308   *
27309   * @private
27310   * @param   {string} name
27311   *          The name of a plugin.
27312   *
27313   * @return {boolean}
27314   *          Whether or not the plugin has been registered.
27315   */
27316  const pluginExists = name => pluginStorage.hasOwnProperty(name);
27317
27318  /**
27319   * Get a single registered plugin by name.
27320   *
27321   * @private
27322   * @param   {string} name
27323   *          The name of a plugin.
27324   *
27325   * @return {typeof Plugin|Function|undefined}
27326   *          The plugin (or undefined).
27327   */
27328  const getPlugin = name => pluginExists(name) ? pluginStorage[name] : undefined;
27329
27330  /**
27331   * Marks a plugin as "active" on a player.
27332   *
27333   * Also, ensures that the player has an object for tracking active plugins.
27334   *
27335   * @private
27336   * @param   {Player} player
27337   *          A Video.js player instance.
27338   *
27339   * @param   {string} name
27340   *          The name of a plugin.
27341   */
27342  const markPluginAsActive = (player, name) => {
27343    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
27344    player[PLUGIN_CACHE_KEY][name] = true;
27345  };
27346
27347  /**
27348   * Triggers a pair of plugin setup events.
27349   *
27350   * @private
27351   * @param  {Player} player
27352   *         A Video.js player instance.
27353   *
27354   * @param  {PluginEventHash} hash
27355   *         A plugin event hash.
27356   *
27357   * @param  {boolean} [before]
27358   *         If true, prefixes the event name with "before". In other words,
27359   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
27360   */
27361  const triggerSetupEvent = (player, hash, before) => {
27362    const eventName = (before ? 'before' : '') + 'pluginsetup';
27363    player.trigger(eventName, hash);
27364    player.trigger(eventName + ':' + hash.name, hash);
27365  };
27366
27367  /**
27368   * Takes a basic plugin function and returns a wrapper function which marks
27369   * on the player that the plugin has been activated.
27370   *
27371   * @private
27372   * @param   {string} name
27373   *          The name of the plugin.
27374   *
27375   * @param   {Function} plugin
27376   *          The basic plugin.
27377   *
27378   * @return {Function}
27379   *          A wrapper function for the given plugin.
27380   */
27381  const createBasicPlugin = function (name, plugin) {
27382    const basicPluginWrapper = function () {
27383      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
27384      // regardless, but we want the hash to be consistent with the hash provided
27385      // for advanced plugins.
27386      //
27387      // The only potentially counter-intuitive thing here is the `instance` in
27388      // the "pluginsetup" event is the value returned by the `plugin` function.
27389      triggerSetupEvent(this, {
27390        name,
27391        plugin,
27392        instance: null
27393      }, true);
27394      const instance = plugin.apply(this, arguments);
27395      markPluginAsActive(this, name);
27396      triggerSetupEvent(this, {
27397        name,
27398        plugin,
27399        instance
27400      });
27401      return instance;
27402    };
27403    Object.keys(plugin).forEach(function (prop) {
27404      basicPluginWrapper[prop] = plugin[prop];
27405    });
27406    return basicPluginWrapper;
27407  };
27408
27409  /**
27410   * Takes a plugin sub-class and returns a factory function for generating
27411   * instances of it.
27412   *
27413   * This factory function will replace itself with an instance of the requested
27414   * sub-class of Plugin.
27415   *
27416   * @private
27417   * @param   {string} name
27418   *          The name of the plugin.
27419   *
27420   * @param   {Plugin} PluginSubClass
27421   *          The advanced plugin.
27422   *
27423   * @return {Function}
27424   */
27425  const createPluginFactory = (name, PluginSubClass) => {
27426    // Add a `name` property to the plugin prototype so that each plugin can
27427    // refer to itself by name.
27428    PluginSubClass.prototype.name = name;
27429    return function (...args) {
27430      triggerSetupEvent(this, {
27431        name,
27432        plugin: PluginSubClass,
27433        instance: null
27434      }, true);
27435      const instance = new PluginSubClass(...[this, ...args]);
27436
27437      // The plugin is replaced by a function that returns the current instance.
27438      this[name] = () => instance;
27439      triggerSetupEvent(this, instance.getEventHash());
27440      return instance;
27441    };
27442  };
27443
27444  /**
27445   * Parent class for all advanced plugins.
27446   *
27447   * @mixes   module:evented~EventedMixin
27448   * @mixes   module:stateful~StatefulMixin
27449   * @fires   Player#beforepluginsetup
27450   * @fires   Player#beforepluginsetup:$name
27451   * @fires   Player#pluginsetup
27452   * @fires   Player#pluginsetup:$name
27453   * @listens Player#dispose
27454   * @throws  {Error}
27455   *          If attempting to instantiate the base {@link Plugin} class
27456   *          directly instead of via a sub-class.
27457   */
27458  class Plugin {
27459    /**
27460     * Creates an instance of this class.
27461     *
27462     * Sub-classes should call `super` to ensure plugins are properly initialized.
27463     *
27464     * @param {Player} player
27465     *        A Video.js player instance.
27466     */
27467    constructor(player) {
27468      if (this.constructor === Plugin) {
27469        throw new Error('Plugin must be sub-classed; not directly instantiated.');
27470      }
27471      this.player = player;
27472      if (!this.log) {
27473        this.log = this.player.log.createLogger(this.name);
27474      }
27475
27476      // Make this object evented, but remove the added `trigger` method so we
27477      // use the prototype version instead.
27478      evented(this);
27479      delete this.trigger;
27480      stateful(this, this.constructor.defaultState);
27481      markPluginAsActive(player, this.name);
27482
27483      // Auto-bind the dispose method so we can use it as a listener and unbind
27484      // it later easily.
27485      this.dispose = this.dispose.bind(this);
27486
27487      // If the player is disposed, dispose the plugin.
27488      player.on('dispose', this.dispose);
27489    }
27490
27491    /**
27492     * Get the version of the plugin that was set on <pluginName>.VERSION
27493     */
27494    version() {
27495      return this.constructor.VERSION;
27496    }
27497
27498    /**
27499     * Each event triggered by plugins includes a hash of additional data with
27500     * conventional properties.
27501     *
27502     * This returns that object or mutates an existing hash.
27503     *
27504     * @param   {Object} [hash={}]
27505     *          An object to be used as event an event hash.
27506     *
27507     * @return {PluginEventHash}
27508     *          An event hash object with provided properties mixed-in.
27509     */
27510    getEventHash(hash = {}) {
27511      hash.name = this.name;
27512      hash.plugin = this.constructor;
27513      hash.instance = this;
27514      return hash;
27515    }
27516
27517    /**
27518     * Triggers an event on the plugin object and overrides
27519     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
27520     *
27521     * @param   {string|Object} event
27522     *          An event type or an object with a type property.
27523     *
27524     * @param   {Object} [hash={}]
27525     *          Additional data hash to merge with a
27526     *          {@link PluginEventHash|PluginEventHash}.
27527     *
27528     * @return {boolean}
27529     *          Whether or not default was prevented.
27530     */
27531    trigger(event, hash = {}) {
27532      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
27533    }
27534
27535    /**
27536     * Handles "statechanged" events on the plugin. No-op by default, override by
27537     * subclassing.
27538     *
27539     * @abstract
27540     * @param    {Event} e
27541     *           An event object provided by a "statechanged" event.
27542     *
27543     * @param    {Object} e.changes
27544     *           An object describing changes that occurred with the "statechanged"
27545     *           event.
27546     */
27547    handleStateChanged(e) {}
27548
27549    /**
vendor: 8,400 bytes, lines 27550-27828
27550     * Disposes a plugin.
27551     *
27552     * Subclasses can override this if they want, but for the sake of safety,
27553     * it's probably best to subscribe the "dispose" event.
27554     *
27555     * @fires Plugin#dispose
27556     */
27557    dispose() {
27558      const {
27559        name,
27560        player
27561      } = this;
27562
27563      /**
27564       * Signals that a advanced plugin is about to be disposed.
27565       *
27566       * @event Plugin#dispose
27567       * @type  {Event}
27568       */
27569      this.trigger('dispose');
27570      this.off();
27571      player.off('dispose', this.dispose);
27572
27573      // Eliminate any possible sources of leaking memory by clearing up
27574      // references between the player and the plugin instance and nulling out
27575      // the plugin's state and replacing methods with a function that throws.
27576      player[PLUGIN_CACHE_KEY][name] = false;
27577      this.player = this.state = null;
27578
27579      // Finally, replace the plugin name on the player with a new factory
27580      // function, so that the plugin is ready to be set up again.
27581      player[name] = createPluginFactory(name, pluginStorage[name]);
27582    }
27583
27584    /**
27585     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
27586     *
27587     * @param   {string|Function} plugin
27588     *          If a string, matches the name of a plugin. If a function, will be
27589     *          tested directly.
27590     *
27591     * @return {boolean}
27592     *          Whether or not a plugin is a basic plugin.
27593     */
27594    static isBasic(plugin) {
27595      const p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
27596      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
27597    }
27598
27599    /**
27600     * Register a Video.js plugin.
27601     *
27602     * @param   {string} name
27603     *          The name of the plugin to be registered. Must be a string and
27604     *          must not match an existing plugin or a method on the `Player`
27605     *          prototype.
27606     *
27607     * @param   {typeof Plugin|Function} plugin
27608     *          A sub-class of `Plugin` or a function for basic plugins.
27609     *
27610     * @return {typeof Plugin|Function}
27611     *          For advanced plugins, a factory function for that plugin. For
27612     *          basic plugins, a wrapper function that initializes the plugin.
27613     */
27614    static registerPlugin(name, plugin) {
27615      if (typeof name !== 'string') {
27616        throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
27617      }
27618      if (pluginExists(name)) {
27619        log$1.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
27620      } else if (Player.prototype.hasOwnProperty(name)) {
27621        throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
27622      }
27623      if (typeof plugin !== 'function') {
27624        throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
27625      }
27626      pluginStorage[name] = plugin;
27627
27628      // Add a player prototype method for all sub-classed plugins (but not for
27629      // the base Plugin class).
27630      if (name !== BASE_PLUGIN_NAME) {
27631        if (Plugin.isBasic(plugin)) {
27632          Player.prototype[name] = createBasicPlugin(name, plugin);
27633        } else {
27634          Player.prototype[name] = createPluginFactory(name, plugin);
27635        }
27636      }
27637      return plugin;
27638    }
27639
27640    /**
27641     * De-register a Video.js plugin.
27642     *
27643     * @param  {string} name
27644     *         The name of the plugin to be de-registered. Must be a string that
27645     *         matches an existing plugin.
27646     *
27647     * @throws {Error}
27648     *         If an attempt is made to de-register the base plugin.
27649     */
27650    static deregisterPlugin(name) {
27651      if (name === BASE_PLUGIN_NAME) {
27652        throw new Error('Cannot de-register base plugin.');
27653      }
27654      if (pluginExists(name)) {
27655        delete pluginStorage[name];
27656        delete Player.prototype[name];
27657      }
27658    }
27659
27660    /**
27661     * Gets an object containing multiple Video.js plugins.
27662     *
27663     * @param   {Array} [names]
27664     *          If provided, should be an array of plugin names. Defaults to _all_
27665     *          plugin names.
27666     *
27667     * @return {Object|undefined}
27668     *          An object containing plugin(s) associated with their name(s) or
27669     *          `undefined` if no matching plugins exist).
27670     */
27671    static getPlugins(names = Object.keys(pluginStorage)) {
27672      let result;
27673      names.forEach(name => {
27674        const plugin = getPlugin(name);
27675        if (plugin) {
27676          result = result || {};
27677          result[name] = plugin;
27678        }
27679      });
27680      return result;
27681    }
27682
27683    /**
27684     * Gets a plugin's version, if available
27685     *
27686     * @param   {string} name
27687     *          The name of a plugin.
27688     *
27689     * @return {string}
27690     *          The plugin's version or an empty string.
27691     */
27692    static getPluginVersion(name) {
27693      const plugin = getPlugin(name);
27694      return plugin && plugin.VERSION || '';
27695    }
27696  }
27697
27698  /**
27699   * Gets a plugin by name if it exists.
27700   *
27701   * @static
27702   * @method   getPlugin
27703   * @memberOf Plugin
27704   * @param    {string} name
27705   *           The name of a plugin.
27706   *
27707   * @returns  {typeof Plugin|Function|undefined}
27708   *           The plugin (or `undefined`).
27709   */
27710  Plugin.getPlugin = getPlugin;
27711
27712  /**
27713   * The name of the base plugin class as it is registered.
27714   *
27715   * @type {string}
27716   */
27717  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
27718  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
27719
27720  /**
27721   * Documented in player.js
27722   *
27723   * @ignore
27724   */
27725  Player.prototype.usingPlugin = function (name) {
27726    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
27727  };
27728
27729  /**
27730   * Documented in player.js
27731   *
27732   * @ignore
27733   */
27734  Player.prototype.hasPlugin = function (name) {
27735    return !!pluginExists(name);
27736  };
27737
27738  /**
27739   * Signals that a plugin is about to be set up on a player.
27740   *
27741   * @event    Player#beforepluginsetup
27742   * @type     {PluginEventHash}
27743   */
27744
27745  /**
27746   * Signals that a plugin is about to be set up on a player - by name. The name
27747   * is the name of the plugin.
27748   *
27749   * @event    Player#beforepluginsetup:$name
27750   * @type     {PluginEventHash}
27751   */
27752
27753  /**
27754   * Signals that a plugin has just been set up on a player.
27755   *
27756   * @event    Player#pluginsetup
27757   * @type     {PluginEventHash}
27758   */
27759
27760  /**
27761   * Signals that a plugin has just been set up on a player - by name. The name
27762   * is the name of the plugin.
27763   *
27764   * @event    Player#pluginsetup:$name
27765   * @type     {PluginEventHash}
27766   */
27767
27768  /**
27769   * @typedef  {Object} PluginEventHash
27770   *
27771   * @property {string} instance
27772   *           For basic plugins, the return value of the plugin function. For
27773   *           advanced plugins, the plugin instance on which the event is fired.
27774   *
27775   * @property {string} name
27776   *           The name of the plugin.
27777   *
27778   * @property {string} plugin
27779   *           For basic plugins, the plugin function. For advanced plugins, the
27780   *           plugin class/constructor.
27781   */
27782
27783  /**
27784   * @file deprecate.js
27785   * @module deprecate
27786   */
27787
27788  /**
27789   * Decorate a function with a deprecation message the first time it is called.
27790   *
27791   * @param  {string}   message
27792   *         A deprecation message to log the first time the returned function
27793   *         is called.
27794   *
27795   * @param  {Function} fn
27796   *         The function to be deprecated.
27797   *
27798   * @return {Function}
27799   *         A wrapper function that will log a deprecation warning the first
27800   *         time it is called. The return value will be the return value of
27801   *         the wrapped function.
27802   */
27803  function deprecate(message, fn) {
27804    let warned = false;
27805    return function (...args) {
27806      if (!warned) {
27807        log$1.warn(message);
27808      }
27809      warned = true;
27810      return fn.apply(this, args);
27811    };
27812  }
27813
27814  /**
27815   * Internal function used to mark a function as deprecated in the next major
27816   * version with consistent messaging.
27817   *
27818   * @param  {number}   major   The major version where it will be removed
27819   * @param  {string}   oldName The old function name
27820   * @param  {string}   newName The new function name
27821   * @param  {Function} fn      The function to deprecate
27822   * @return {Function}         The decorated function
27823   */
27824  function deprecateForMajor(major, oldName, newName, fn) {
27825    return deprecate(`${oldName} is deprecated and will be removed in ${major}.0; please use ${newName} instead.`, fn);
27826  }
27827
27828  
27828/**
27829   * @file video.js
27830   * @module videojs
27831   */
27832
27833  /**
27834   * Normalize an `id` value by trimming off a leading `#`
27835   *
27836   * @private
27837   * @param   {string} id
27838   *          A string, maybe with a leading `#`.
27839   *
27840   * @return {string}
27841   *          The string, without any leading `#`.
27842   */
27843  const normalizeId = id => id.indexOf('#') === 0 ? id.slice(1) : id;
27844
27845  /**
27846   * A callback that is called when a component is ready. Does not have any
27847   * parameters and any callback value will be ignored. See: {@link Component~ReadyCallback}
27848   *
27849   * @callback ReadyCallback
27850   */
27851
27852  /**
27853   * The `videojs()` function doubles as the main function for users to create a
27854   * {@link Player} instance as well as the main library namespace.
27855   *
27856   * It can also be used as a getter for a pre-existing {@link Player} instance.
27857   * However, we _strongly_ recommend using `videojs.getPlayer()` for this
27858   * purpose because it avoids any potential for unintended initialization.
27859   *
27860   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
27861   * of our JSDoc template, we cannot properly document this as both a function
27862   * and a namespace, so its function signature is documented here.
27863   *
27864   * #### Arguments
27865   * ##### id
27866   * string|Element, **required**
27867   *
27868   * Video element or video element ID.
27869   *
27870   * ##### options
27871   * Object, optional
27872   *
27873   * Options object for providing settings.
27874   * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27875   *
27876   * ##### ready
27877   * {@link Component~ReadyCallback}, optional
27878   *
27879   * A function to be called when the {@link Player} and {@link Tech} are ready.
27880   *
27881   * #### Return Value
27882   *
27883   * The `videojs()` function returns a {@link Player} instance.
27884   *
27885   * @namespace
27886   *
27887   * @borrows AudioTrack as AudioTrack
27888   * @borrows Component.getComponent as getComponent
27889   * @borrows module:events.on as on
27890   * @borrows module:events.one as one
27891   * @borrows module:events.off as off
27892   * @borrows module:events.trigger as trigger
27893   * @borrows EventTarget as EventTarget
27894   * @borrows module:middleware.use as use
27895   * @borrows Player.players as players
27896   * @borrows Plugin.registerPlugin as registerPlugin
27897   * @borrows Plugin.deregisterPlugin as deregisterPlugin
27898   * @borrows Plugin.getPlugins as getPlugins
27899   * @borrows Plugin.getPlugin as getPlugin
27900   * @borrows Plugin.getPluginVersion as getPluginVersion
27901   * @borrows Tech.getTech as getTech
27902   * @borrows Tech.registerTech as registerTech
27903   * @borrows TextTrack as TextTrack
27904   * @borrows VideoTrack as VideoTrack
27905   *
27906   * @param  {string|Element}
vendor: 15,493 bytes, lines 27906-28366
27906 id
27907   *         Video element or video element ID.
27908   *
27909   * @param  {Object} [options]
27910   *         Options object for providing settings.
27911   *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27912   *
27913   * @param  {ReadyCallback} [ready]
27914   *         A function to be called when the {@link Player} and {@link Tech} are
27915   *         ready.
27916   *
27917   * @return {Player}
27918   *         The `videojs()` function returns a {@link Player|Player} instance.
27919   */
27920  function videojs(id, options, ready) {
27921    let player = videojs.getPlayer(id);
27922    if (player) {
27923      if (options) {
27924        log$1.warn(`Player "${id}" is already initialised. Options will not be applied.`);
27925      }
27926      if (ready) {
27927        player.ready(ready);
27928      }
27929      return player;
27930    }
27931    const el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
27932    if (!isEl(el)) {
27933      throw new TypeError('The element or ID supplied is not valid. (videojs)');
27934    }
27935
27936    // document.body.contains(el) will only check if el is contained within that one document.
27937    // This causes problems for elements in iframes.
27938    // Instead, use the element's ownerDocument instead of the global document.
27939    // This will make sure that the element is indeed in the dom of that document.
27940    // Additionally, check that the document in question has a default view.
27941    // If the document is no longer attached to the dom, the defaultView of the document will be null.
27942    // If element is inside Shadow DOM (e.g. is part of a Custom element), ownerDocument.body
27943    // always returns false. Instead, use the Shadow DOM root.
27944    const inShadowDom = 'getRootNode' in el ? el.getRootNode() instanceof window.ShadowRoot : false;
27945    const rootNode = inShadowDom ? el.getRootNode() : el.ownerDocument.body;
27946    if (!el.ownerDocument.defaultView || !rootNode.contains(el)) {
27947      log$1.warn('The element supplied is not included in the DOM');
27948    }
27949    options = options || {};
27950
27951    // Store a copy of the el before modification, if it is to be restored in destroy()
27952    // If div ingest, store the parent div
27953    if (options.restoreEl === true) {
27954      options.restoreEl = (el.parentNode && el.parentNode.hasAttribute('data-vjs-player') ? el.parentNode : el).cloneNode(true);
27955    }
27956    hooks('beforesetup').forEach(hookFunction => {
27957      const opts = hookFunction(el, merge$2(options));
27958      if (!isObject$1(opts) || Array.isArray(opts)) {
27959        log$1.error('please return an object in beforesetup hooks');
27960        return;
27961      }
27962      options = merge$2(options, opts);
27963    });
27964
27965    // We get the current "Player" component here in case an integration has
27966    // replaced it with a custom player.
27967    const PlayerComponent = Component$1.getComponent('Player');
27968    player = new PlayerComponent(el, options, ready);
27969    hooks('setup').forEach(hookFunction => hookFunction(player));
27970    return player;
27971  }
27972  videojs.hooks_ = hooks_;
27973  videojs.hooks = hooks;
27974  videojs.hook = hook;
27975  videojs.hookOnce = hookOnce;
27976  videojs.removeHook = removeHook;
27977
27978  // Add default styles
27979  if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
27980    let style = $('.vjs-styles-defaults');
27981    if (!style) {
27982      style = createStyleElement('vjs-styles-defaults');
27983      const head = $('head');
27984      if (head) {
27985        head.insertBefore(style, head.firstChild);
27986      }
27987      setTextContent(style, `
27988      .video-js {
27989        width: 300px;
27990        height: 150px;
27991      }
27992
27993      .vjs-fluid:not(.vjs-audio-only-mode) {
27994        padding-top: 56.25%
27995      }
27996    `);
27997    }
27998  }
27999
28000  // Run Auto-load players
28001  // You have to wait at least once in case this script is loaded after your
28002  // video in the DOM (weird behavior only with minified version)
28003  autoSetupTimeout(1, videojs);
28004
28005  /**
28006   * Current Video.js version. Follows [semantic versioning](https://semver.org/).
28007   *
28008   * @type {string}
28009   */
28010  videojs.VERSION = version$5;
28011
28012  /**
28013   * The global options object. These are the settings that take effect
28014   * if no overrides are specified when the player is created.
28015   *
28016   * @type {Object}
28017   */
28018  videojs.options = Player.prototype.options_;
28019
28020  /**
28021   * Get an object with the currently created players, keyed by player ID
28022   *
28023   * @return {Object}
28024   *         The created players
28025   */
28026  videojs.getPlayers = () => Player.players;
28027
28028  /**
28029   * Get a single player based on an ID or DOM element.
28030   *
28031   * This is useful if you want to check if an element or ID has an associated
28032   * Video.js player, but not create one if it doesn't.
28033   *
28034   * @param   {string|Element} id
28035   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
28036   *          or a string matching the `id` of such an element.
28037   *
28038   * @return {Player|undefined}
28039   *          A player instance or `undefined` if there is no player instance
28040   *          matching the argument.
28041   */
28042  videojs.getPlayer = id => {
28043    const players = Player.players;
28044    let tag;
28045    if (typeof id === 'string') {
28046      const nId = normalizeId(id);
28047      const player = players[nId];
28048      if (player) {
28049        return player;
28050      }
28051      tag = $('#' + nId);
28052    } else {
28053      tag = id;
28054    }
28055    if (isEl(tag)) {
28056      const {
28057        player,
28058        playerId
28059      } = tag;
28060
28061      // Element may have a `player` property referring to an already created
28062      // player instance. If so, return that.
28063      if (player || players[playerId]) {
28064        return player || players[playerId];
28065      }
28066    }
28067  };
28068
28069  /**
28070   * Returns an array of all current players.
28071   *
28072   * @return {Array}
28073   *         An array of all players. The array will be in the order that
28074   *         `Object.keys` provides, which could potentially vary between
28075   *         JavaScript engines.
28076   *
28077   */
28078  videojs.getAllPlayers = () =>
28079  // Disposed players leave a key with a `null` value, so we need to make sure
28080  // we filter those out.
28081  Object.keys(Player.players).map(k => Player.players[k]).filter(Boolean);
28082  videojs.players = Player.players;
28083  videojs.getComponent = Component$1.getComponent;
28084
28085  /**
28086   * Register a component so it can referred to by name. Used when adding to other
28087   * components, either through addChild `component.addChild('myComponent')` or through
28088   * default children options  `{ children: ['myComponent'] }`.
28089   *
28090   * > NOTE: You could also just initialize the component before adding.
28091   * `component.addChild(new MyComponent());`
28092   *
28093   * @param {string} name
28094   *        The class name of the component
28095   *
28096   * @param {typeof Component} comp
28097   *        The component class
28098   *
28099   * @return {typeof Component}
28100   *         The newly registered component
28101   */
28102  videojs.registerComponent = (name, comp) => {
28103    if (Tech.isTech(comp)) {
28104      log$1.warn(`The ${name} tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)`);
28105    }
28106    return Component$1.registerComponent.call(Component$1, name, comp);
28107  };
28108  videojs.getTech = Tech.getTech;
28109  videojs.registerTech = Tech.registerTech;
28110  videojs.use = use;
28111
28112  /**
28113   * An object that can be returned by a middleware to signify
28114   * that the middleware is being terminated.
28115   *
28116   * @type {object}
28117   * @property {object} middleware.TERMINATOR
28118   */
28119  Object.defineProperty(videojs, 'middleware', {
28120    value: {},
28121    writeable: false,
28122    enumerable: true
28123  });
28124  Object.defineProperty(videojs.middleware, 'TERMINATOR', {
28125    value: TERMINATOR,
28126    writeable: false,
28127    enumerable: true
28128  });
28129
28130  /**
28131   * A reference to the {@link module:browser|browser utility module} as an object.
28132   *
28133   * @type {Object}
28134   * @see  {@link module:browser|browser}
28135   */
28136  videojs.browser = browser;
28137
28138  /**
28139   * A reference to the {@link module:obj|obj utility module} as an object.
28140   *
28141   * @type {Object}
28142   * @see  {@link module:obj|obj}
28143   */
28144  videojs.obj = Obj;
28145
28146  /**
28147   * Deprecated reference to the {@link module:obj.merge|merge function}
28148   *
28149   * @type {Function}
28150   * @see {@link module:obj.merge|merge}
28151   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.merge instead.
28152   */
28153  videojs.mergeOptions = deprecateForMajor(9, 'videojs.mergeOptions', 'videojs.obj.merge', merge$2);
28154
28155  /**
28156   * Deprecated reference to the {@link module:obj.defineLazyProperty|defineLazyProperty function}
28157   *
28158   * @type {Function}
28159   * @see {@link module:obj.defineLazyProperty|defineLazyProperty}
28160   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.defineLazyProperty instead.
28161   */
28162  videojs.defineLazyProperty = deprecateForMajor(9, 'videojs.defineLazyProperty', 'videojs.obj.defineLazyProperty', defineLazyProperty);
28163
28164  /**
28165   * Deprecated reference to the {@link module:fn.bind_|fn.bind_ function}
28166   *
28167   * @type {Function}
28168   * @see {@link module:fn.bind_|fn.bind_}
28169   * @deprecated Deprecated and will be removed in 9.0. Please use native Function.prototype.bind instead.
28170   */
28171  videojs.bind = deprecateForMajor(9, 'videojs.bind', 'native Function.prototype.bind', bind_);
28172  videojs.registerPlugin = Plugin.registerPlugin;
28173  videojs.deregisterPlugin = Plugin.deregisterPlugin;
28174
28175  /**
28176   * Deprecated method to register a plugin with Video.js
28177   *
28178   * @deprecated Deprecated and will be removed in 9.0. Use videojs.registerPlugin() instead.
28179   *
28180   * @param {string} name
28181   *        The plugin name
28182  *
28183   * @param {typeof Plugin|Function} plugin
28184   *         The plugin sub-class or function
28185   *
28186   * @return {typeof Plugin|Function}
28187   */
28188  videojs.plugin = (name, plugin) => {
28189    log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
28190    return Plugin.registerPlugin(name, plugin);
28191  };
28192  videojs.getPlugins = Plugin.getPlugins;
28193  videojs.getPlugin = Plugin.getPlugin;
28194  videojs.getPluginVersion = Plugin.getPluginVersion;
28195
28196  /**
28197   * Adding languages so that they're available to all players.
28198   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
28199   *
28200   * @param {string} code
28201   *        The language code or dictionary property
28202   *
28203   * @param {Object} data
28204   *        The data values to be translated
28205   *
28206   * @return {Object}
28207   *         The resulting language dictionary object
28208   */
28209  videojs.addLanguage = function (code, data) {
28210    code = ('' + code).toLowerCase();
28211    videojs.options.languages = merge$2(videojs.options.languages, {
28212      [code]: data
28213    });
28214    return videojs.options.languages[code];
28215  };
28216
28217  /**
28218   * A reference to the {@link module:log|log utility module} as an object.
28219   *
28220   * @type {Function}
28221   * @see  {@link module:log|log}
28222   */
28223  videojs.log = log$1;
28224  videojs.createLogger = createLogger;
28225
28226  /**
28227   * A reference to the {@link module:time|time utility module} as an object.
28228   *
28229   * @type {Object}
28230   * @see {@link module:time|time}
28231   */
28232  videojs.time = Time;
28233
28234  /**
28235   * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28236   *
28237   * @type {Function}
28238   * @see {@link module:time.createTimeRanges|createTimeRanges}
28239   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28240   */
28241  videojs.createTimeRange = deprecateForMajor(9, 'videojs.createTimeRange', 'videojs.time.createTimeRanges', createTimeRanges$1);
28242
28243  /**
28244   * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28245   *
28246   * @type {Function}
28247   * @see {@link module:time.createTimeRanges|createTimeRanges}
28248   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28249   */
28250  videojs.createTimeRanges = deprecateForMajor(9, 'videojs.createTimeRanges', 'videojs.time.createTimeRanges', createTimeRanges$1);
28251
28252  /**
28253   * Deprecated reference to the {@link module:time.formatTime|formatTime function}
28254   *
28255   * @type {Function}
28256   * @see {@link module:time.formatTime|formatTime}
28257   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.format instead.
28258   */
28259  videojs.formatTime = deprecateForMajor(9, 'videojs.formatTime', 'videojs.time.formatTime', formatTime);
28260
28261  /**
28262   * Deprecated reference to the {@link module:time.setFormatTime|setFormatTime function}
28263   *
28264   * @type {Function}
28265   * @see {@link module:time.setFormatTime|setFormatTime}
28266   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.setFormat instead.
28267   */
28268  videojs.setFormatTime = deprecateForMajor(9, 'videojs.setFormatTime', 'videojs.time.setFormatTime', setFormatTime);
28269
28270  /**
28271   * Deprecated reference to the {@link module:time.resetFormatTime|resetFormatTime function}
28272   *
28273   * @type {Function}
28274   * @see {@link module:time.resetFormatTime|resetFormatTime}
28275   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.resetFormat instead.
28276   */
28277  videojs.resetFormatTime = deprecateForMajor(9, 'videojs.resetFormatTime', 'videojs.time.resetFormatTime', resetFormatTime);
28278
28279  /**
28280   * Deprecated reference to the {@link module:url.parseUrl|Url.parseUrl function}
28281   *
28282   * @type {Function}
28283   * @see {@link module:url.parseUrl|parseUrl}
28284   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.parseUrl instead.
28285   */
28286  videojs.parseUrl = deprecateForMajor(9, 'videojs.parseUrl', 'videojs.url.parseUrl', parseUrl);
28287
28288  /**
28289   * Deprecated reference to the {@link module:url.isCrossOrigin|Url.isCrossOrigin function}
28290   *
28291   * @type {Function}
28292   * @see {@link module:url.isCrossOrigin|isCrossOrigin}
28293   * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.isCrossOrigin instead.
28294   */
28295  videojs.isCrossOrigin = deprecateForMajor(9, 'videojs.isCrossOrigin', 'videojs.url.isCrossOrigin', isCrossOrigin);
28296  videojs.EventTarget = EventTarget$2;
28297  videojs.any = any;
28298  videojs.on = on;
28299  videojs.one = one;
28300  videojs.off = off;
28301  videojs.trigger = trigger;
28302
28303  /**
28304   * A cross-browser XMLHttpRequest wrapper.
28305   *
28306   * @function
28307   * @param    {Object} options
28308   *           Settings for the request.
28309   *
28310   * @return   {XMLHttpRequest|XDomainRequest}
28311   *           The request object.
28312   *
28313   * @see      https://github.com/Raynos/xhr
28314   */
28315  videojs.xhr = lib;
28316  videojs.TextTrack = TextTrack;
28317  videojs.AudioTrack = AudioTrack;
28318  videojs.VideoTrack = VideoTrack;
28319  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(k => {
28320    videojs[k] = function () {
28321      log$1.warn(`videojs.${k}() is deprecated; use videojs.dom.${k}() instead`);
28322      return Dom[k].apply(null, arguments);
28323    };
28324  });
28325  videojs.computedStyle = deprecateForMajor(9, 'videojs.computedStyle', 'videojs.dom.computedStyle', computedStyle);
28326
28327  /**
28328   * A reference to the {@link module:dom|DOM utility module} as an object.
28329   *
28330   * @type {Object}
28331   * @see {@link module:dom|dom}
28332   */
28333  videojs.dom = Dom;
28334
28335  /**
28336   * A reference to the {@link module:fn|fn utility module} as an object.
28337   *
28338   * @type {Object}
28339   * @see {@link module:fn|fn}
28340   */
28341  videojs.fn = Fn;
28342
28343  /**
28344   * A reference to the {@link module:num|num utility module} as an object.
28345   *
28346   * @type {Object}
28347   * @see {@link module:num|num}
28348   */
28349  videojs.num = Num;
28350
28351  /**
28352   * A reference to the {@link module:str|str utility module} as an object.
28353   *
28354   * @type {Object}
28355   * @see {@link module:str|str}
28356   */
28357  videojs.str = Str;
28358
28359  /**
28360   * A reference to the {@link module:url|URL utility module} as an object.
28361   *
28362   * @type {Object}
28363   * @see {@link module:url|url}
28364   */
28365  videojs.url = Url;
28366
vendor: 10,142 bytes, lines 28367-28651
28367  createCommonjsModule(function (module, exports) {
28368    /*! @name videojs-contrib-quality-levels @version 4.0.0 @license Apache-2.0 */
28369    (function (global, factory) {
28370      module.exports = factory(videojs) ;
28371    })(commonjsGlobal, function (videojs) {
28372
28373      function _interopDefaultLegacy(e) {
28374        return e && typeof e === 'object' && 'default' in e ? e : {
28375          'default': e
28376        };
28377      }
28378      var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
28379
28380      /**
28381       * A single QualityLevel.
28382       *
28383       * interface QualityLevel {
28384       *   readonly attribute DOMString id;
28385       *            attribute DOMString label;
28386       *   readonly attribute long width;
28387       *   readonly attribute long height;
28388       *   readonly attribute long bitrate;
28389       *            attribute boolean enabled;
28390       * };
28391       *
28392       * @class QualityLevel
28393       */
28394      class QualityLevel {
28395        /**
28396         * Creates a QualityLevel
28397         *
28398         * @param {Representation|Object} representation The representation of the quality level
28399         * @param {string}   representation.id        Unique id of the QualityLevel
28400         * @param {number=}  representation.width     Resolution width of the QualityLevel
28401         * @param {number=}  representation.height    Resolution height of the QualityLevel
28402         * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28403         * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28404         * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28405         */
28406        constructor(representation) {
28407          let level = this; // eslint-disable-line
28408
28409          level.id = representation.id;
28410          level.label = level.id;
28411          level.width = representation.width;
28412          level.height = representation.height;
28413          level.bitrate = representation.bandwidth;
28414          level.frameRate = representation.frameRate;
28415          level.enabled_ = representation.enabled;
28416          Object.defineProperty(level, 'enabled', {
28417            /**
28418             * Get whether the QualityLevel is enabled.
28419             *
28420             * @return {boolean} True if the QualityLevel is enabled.
28421             */
28422            get() {
28423              return level.enabled_();
28424            },
28425            /**
28426             * Enable or disable the QualityLevel.
28427             *
28428             * @param {boolean} enable true to enable QualityLevel, false to disable.
28429             */
28430            set(enable) {
28431              level.enabled_(enable);
28432            }
28433          });
28434          return level;
28435        }
28436      }
28437
28438      /**
28439       * A list of QualityLevels.
28440       *
28441       * interface QualityLevelList : EventTarget {
28442       *   getter QualityLevel (unsigned long index);
28443       *   readonly attribute unsigned long length;
28444       *   readonly attribute long selectedIndex;
28445       *
28446       *   void addQualityLevel(QualityLevel qualityLevel)
28447       *   void removeQualityLevel(QualityLevel remove)
28448       *   QualityLevel? getQualityLevelById(DOMString id);
28449       *
28450       *   attribute EventHandler onchange;
28451       *   attribute EventHandler onaddqualitylevel;
28452       *   attribute EventHandler onremovequalitylevel;
28453       * };
28454       *
28455       * @extends videojs.EventTarget
28456       * @class QualityLevelList
28457       */
28458
28459      class QualityLevelList extends videojs__default['default'].EventTarget {
28460        /**
28461         * Creates a QualityLevelList.
28462         */
28463        constructor() {
28464          super();
28465          let list = this; // eslint-disable-line
28466
28467          list.levels_ = [];
28468          list.selectedIndex_ = -1;
28469          /**
28470           * Get the index of the currently selected QualityLevel.
28471           *
28472           * @returns {number} The index of the selected QualityLevel. -1 if none selected.
28473           * @readonly
28474           */
28475
28476          Object.defineProperty(list, 'selectedIndex', {
28477            get() {
28478              return list.selectedIndex_;
28479            }
28480          });
28481          /**
28482           * Get the length of the list of QualityLevels.
28483           *
28484           * @returns {number} The length of the list.
28485           * @readonly
28486           */
28487
28488          Object.defineProperty(list, 'length', {
28489            get() {
28490              return list.levels_.length;
28491            }
28492          });
28493          list[Symbol.iterator] = () => list.levels_.values();
28494          return list;
28495        }
28496        /**
28497         * Adds a quality level to the list.
28498         *
28499         * @param {Representation|Object} representation The representation of the quality level
28500         * @param {string}   representation.id        Unique id of the QualityLevel
28501         * @param {number=}  representation.width     Resolution width of the QualityLevel
28502         * @param {number=}  representation.height    Resolution height of the QualityLevel
28503         * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28504         * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28505         * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28506         * @return {QualityLevel} the QualityLevel added to the list
28507         * @method addQualityLevel
28508         */
28509
28510        addQualityLevel(representation) {
28511          let qualityLevel = this.getQualityLevelById(representation.id); // Do not add duplicate quality levels
28512
28513          if (qualityLevel) {
28514            return qualityLevel;
28515          }
28516          const index = this.levels_.length;
28517          qualityLevel = new QualityLevel(representation);
28518          if (!('' + index in this)) {
28519            Object.defineProperty(this, index, {
28520              get() {
28521                return this.levels_[index];
28522              }
28523            });
28524          }
28525          this.levels_.push(qualityLevel);
28526          this.trigger({
28527            qualityLevel,
28528            type: 'addqualitylevel'
28529          });
28530          return qualityLevel;
28531        }
28532        /**
28533         * Removes a quality level from the list.
28534         *
28535         * @param {QualityLevel} qualityLevel The QualityLevel to remove from the list.
28536         * @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
28537         * @method removeQualityLevel
28538         */
28539
28540        removeQualityLevel(qualityLevel) {
28541          let removed = null;
28542          for (let i = 0, l = this.length; i < l; i++) {
28543            if (this[i] === qualityLevel) {
28544              removed = this.levels_.splice(i, 1)[0];
28545              if (this.selectedIndex_ === i) {
28546                this.selectedIndex_ = -1;
28547              } else if (this.selectedIndex_ > i) {
28548                this.selectedIndex_--;
28549              }
28550              break;
28551            }
28552          }
28553          if (removed) {
28554            this.trigger({
28555              qualityLevel,
28556              type: 'removequalitylevel'
28557            });
28558          }
28559          return removed;
28560        }
28561        /**
28562         * Searches for a QualityLevel with the given id.
28563         *
28564         * @param {string} id The id of the QualityLevel to find.
28565         * @return {QualityLevel|null} The QualityLevel with id, or null if not found.
28566         * @method getQualityLevelById
28567         */
28568
28569        getQualityLevelById(id) {
28570          for (let i = 0, l = this.length; i < l; i++) {
28571            const level = this[i];
28572            if (level.id === id) {
28573              return level;
28574            }
28575          }
28576          return null;
28577        }
28578        /**
28579         * Resets the list of QualityLevels to empty
28580         *
28581         * @method dispose
28582         */
28583
28584        dispose() {
28585          this.selectedIndex_ = -1;
28586          this.levels_.length = 0;
28587        }
28588      }
28589      /**
28590       * change - The selected QualityLevel has changed.
28591       * addqualitylevel - A QualityLevel has been added to the QualityLevelList.
28592       * removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
28593       */
28594
28595      QualityLevelList.prototype.allowedEvents_ = {
28596        change: 'change',
28597        addqualitylevel: 'addqualitylevel',
28598        removequalitylevel: 'removequalitylevel'
28599      }; // emulate attribute EventHandler support to allow for feature detection
28600
28601      for (const event in QualityLevelList.prototype.allowedEvents_) {
28602        QualityLevelList.prototype['on' + event] = null;
28603      }
28604      var version = "4.0.0";
28605
28606      /**
28607       * Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
28608       * event handlers.
28609       *
28610       * @param {Player} player Player object.
28611       * @param {Object} options Plugin options object.
28612       * @return {QualityLevelList} a list of QualityLevels
28613       */
28614
28615      const initPlugin = function (player, options) {
28616        const originalPluginFn = player.qualityLevels;
28617        const qualityLevelList = new QualityLevelList();
28618        const disposeHandler = function () {
28619          qualityLevelList.dispose();
28620          player.qualityLevels = originalPluginFn;
28621          player.off('dispose', disposeHandler);
28622        };
28623        player.on('dispose', disposeHandler);
28624        player.qualityLevels = () => qualityLevelList;
28625        player.qualityLevels.VERSION = version;
28626        return qualityLevelList;
28627      };
28628      /**
28629       * A video.js plugin.
28630       *
28631       * In the plugin function, the value of `this` is a video.js `Player`
28632       * instance. You cannot rely on the player being in a "ready" state here,
28633       * depending on how the plugin is invoked. This may or may not be important
28634       * to you; if not, remove the wait for "ready"!
28635       *
28636       * @param {Object} options Plugin options object
28637       * @return {QualityLevelList} a list of QualityLevels
28638       */
28639
28640      const qualityLevels = function (options) {
28641        return initPlugin(this, videojs__default['default'].obj.merge({}, options));
28642      }; // Register the plugin with video.js.
28643
28644      videojs__default['default'].registerPlugin('qualityLevels', qualityLevels); // Include the version number.
28645
28646      qualityLevels.VERSION = version;
28647      return qualityLevels;
28648    });
28649  });
28650
28651  var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,625 bytes, lines 28651-28741
28651) {
28652    // see https://tools.ietf.org/html/rfc1808
28653
28654    (function (root) {
28655      var URL_REGEX = /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/;
28656      var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/;
28657      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
28658      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
28659      var URLToolkit = {
28660        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
28661        // E.g
28662        // With opts.alwaysNormalize = false (default, spec compliant)
28663        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
28664        // With opts.alwaysNormalize = true (not spec compliant)
28665        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
28666        buildAbsoluteURL: function (baseURL, relativeURL, opts) {
28667          opts = opts || {};
28668          // remove any remaining space and CRLF
28669          baseURL = baseURL.trim();
28670          relativeURL = relativeURL.trim();
28671          if (!relativeURL) {
28672            // 2a) If the embedded URL is entirely empty, it inherits the
28673            // entire base URL (i.e., is set equal to the base URL)
28674            // and we are done.
28675            if (!opts.alwaysNormalize) {
28676              return baseURL;
28677            }
28678            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
28679            if (!basePartsForNormalise) {
28680              throw new Error('Error trying to parse base URL.');
28681            }
28682            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
28683            return URLToolkit.buildURLFromParts(basePartsForNormalise);
28684          }
28685          var relativeParts = URLToolkit.parseURL(relativeURL);
28686          if (!relativeParts) {
28687            throw new Error('Error trying to parse relative URL.');
28688          }
28689          if (relativeParts.scheme) {
28690            // 2b) If the embedded URL starts with a scheme name, it is
28691            // interpreted as an absolute URL and we are done.
28692            if (!opts.alwaysNormalize) {
28693              return relativeURL;
28694            }
28695            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
28696            return URLToolkit.buildURLFromParts(relativeParts);
28697          }
28698          var baseParts = URLToolkit.parseURL(baseURL);
28699          if (!baseParts) {
28700            throw new Error('Error trying to parse base URL.');
28701          }
28702          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
28703            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
28704            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
28705            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
28706            baseParts.netLoc = pathParts[1];
28707            baseParts.path = pathParts[2];
28708          }
28709          if (baseParts.netLoc && !baseParts.path) {
28710            baseParts.path = '/';
28711          }
28712          var builtParts = {
28713            // 2c) Otherwise, the embedded URL inherits the scheme of
28714            // the base URL.
28715            scheme: baseParts.scheme,
28716            netLoc: relativeParts.netLoc,
28717            path: null,
28718            params: relativeParts.params,
28719            query: relativeParts.query,
28720            fragment: relativeParts.fragment
28721          };
28722          if (!relativeParts.netLoc) {
28723            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
28724            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
28725            // (if any) of the base URL.
28726            builtParts.netLoc = baseParts.netLoc;
28727            // 4) If the embedded URL path is preceded by a slash "/", the
28728            // path is not relative and we skip to Step 7.
28729            if (relativeParts.path[0] !== '/') {
28730              if (!relativeParts.path) {
28731                // 5) If the embedded URL path is empty (and not preceded by a
28732                // slash), then the embedded URL inherits the base URL path
28733                builtParts.path = baseParts.path;
28734                // 5a) if the embedded URL's <params> is non-empty, we skip to
28735                // step 7; otherwise, it inherits the <params> of the base
28736                // URL (if any) and
28737                if (!relativeParts.params) {
28738                  builtParts.params = baseParts.params;
28739                  // 5b) if the embedded URL's <query> is non-empty, we skip to
28740                  // step 7; otherwise, it inherits the <query> of the base
28741                  // URL (if any) and we skip to step 7.
vendor: 5,376 bytes, lines 28742-28880
28742                  if (!relativeParts.query) {
28743                    builtParts.query = baseParts.query;
28744                  }
28745                }
28746              } else {
28747                // 6) The last segment of the base URL's path (anything
28748                // following the rightmost slash "/", or the entire path if no
28749                // slash is present) is removed and the embedded URL's path is
28750                // appended in its place.
28751                var baseURLPath = baseParts.path;
28752                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
28753                builtParts.path = URLToolkit.normalizePath(newPath);
28754              }
28755            }
28756          }
28757          if (builtParts.path === null) {
28758            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
28759          }
28760          return URLToolkit.buildURLFromParts(builtParts);
28761        },
28762        parseURL: function (url) {
28763          var parts = URL_REGEX.exec(url);
28764          if (!parts) {
28765            return null;
28766          }
28767          return {
28768            scheme: parts[1] || '',
28769            netLoc: parts[2] || '',
28770            path: parts[3] || '',
28771            params: parts[4] || '',
28772            query: parts[5] || '',
28773            fragment: parts[6] || ''
28774          };
28775        },
28776        normalizePath: function (path) {
28777          // The following operations are
28778          // then applied, in order, to the new path:
28779          // 6a) All occurrences of "./", where "." is a complete path
28780          // segment, are removed.
28781          // 6b) If the path ends with "." as a complete path segment,
28782          // that "." is removed.
28783          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
28784          // 6c) All occurrences of "<segment>/../", where <segment> is a
28785          // complete path segment not equal to "..", are removed.
28786          // Removal of these path segments is performed iteratively,
28787          // removing the leftmost matching pattern on each iteration,
28788          // until no matching pattern remains.
28789          // 6d) If the path ends with "<segment>/..", where <segment> is a
28790          // complete path segment not equal to "..", that
28791          // "<segment>/.." is removed.
28792          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {}
28793          return path.split('').reverse().join('');
28794        },
28795        buildURLFromParts: function (parts) {
28796          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
28797        }
28798      };
28799      module.exports = URLToolkit;
28800    })();
28801  });
28802
28803  var DEFAULT_LOCATION = 'http://example.com';
28804  var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
28805    // return early if we don't need to resolve
28806    if (/^[a-z]+:/i.test(relativeUrl)) {
28807      return relativeUrl;
28808    } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
28809
28810    if (/^data:/.test(baseUrl)) {
28811      baseUrl = window.location && window.location.href || '';
28812    } // IE11 supports URL but not the URL constructor
28813    // feature detect the behavior we want
28814
28815    var nativeURL = typeof window.URL === 'function';
28816    var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
28817    // and if baseUrl isn't an absolute url
28818
28819    var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
28820
28821    if (nativeURL) {
28822      baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
28823    } else if (!/\/\//i.test(baseUrl)) {
28824      baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
28825    }
28826    if (nativeURL) {
28827      var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
28828      // and if we're location-less, remove the location
28829      // otherwise, return the url unmodified
28830
28831      if (removeLocation) {
28832        return newUrl.href.slice(DEFAULT_LOCATION.length);
28833      } else if (protocolLess) {
28834        return newUrl.href.slice(newUrl.protocol.length);
28835      }
28836      return newUrl.href;
28837    }
28838    return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
28839  };
28840
28841  /**
28842   * @file stream.js
28843   */
28844
28845  /**
28846   * A lightweight readable stream implemention that handles event dispatching.
28847   *
28848   * @class Stream
28849   */
28850  var Stream = /*#__PURE__*/function () {
28851    function Stream() {
28852      this.listeners = {};
28853    }
28854    /**
28855     * Add a listener for a specified event type.
28856     *
28857     * @param {string} type the event name
28858     * @param {Function} listener the callback to be invoked when an event of
28859     * the specified type occurs
28860     */
28861
28862    var _proto = Stream.prototype;
28863    _proto.on = function on(type, listener) {
28864      if (!this.listeners[type]) {
28865        this.listeners[type] = [];
28866      }
28867      this.listeners[type].push(listener);
28868    }
28869    /**
28870     * Remove a listener for a specified event type.
28871     *
28872     * @param {string} type the event name
28873     * @param {Function} listener  a function previously registered for this
28874     * type of event through `on`
28875     * @return {boolean} if we could turn it off or not
28876     */;
28877
28878    _proto.off = function off(type, listener) {
28879      if (!this.listeners[type]) {
28880        return false;
vendor: 36,599 bytes, lines 28881-29924
28881      }
28882      var index = this.listeners[type].indexOf(listener); // TODO: which is better?
28883      // In Video.js we slice listener functions
28884      // on trigger so that it does not mess up the order
28885      // while we loop through.
28886      //
28887      // Here we slice on off so that the loop in trigger
28888      // can continue using it's old reference to loop without
28889      // messing up the order.
28890
28891      this.listeners[type] = this.listeners[type].slice(0);
28892      this.listeners[type].splice(index, 1);
28893      return index > -1;
28894    }
28895    /**
28896     * Trigger an event of the specified type on this stream. Any additional
28897     * arguments to this function are passed as parameters to event listeners.
28898     *
28899     * @param {string} type the event name
28900     */;
28901
28902    _proto.trigger = function trigger(type) {
28903      var callbacks = this.listeners[type];
28904      if (!callbacks) {
28905        return;
28906      } // Slicing the arguments on every invocation of this method
28907      // can add a significant amount of overhead. Avoid the
28908      // intermediate object creation for the common case of a
28909      // single callback argument
28910
28911      if (arguments.length === 2) {
28912        var length = callbacks.length;
28913        for (var i = 0; i < length; ++i) {
28914          callbacks[i].call(this, arguments[1]);
28915        }
28916      } else {
28917        var args = Array.prototype.slice.call(arguments, 1);
28918        var _length = callbacks.length;
28919        for (var _i = 0; _i < _length; ++_i) {
28920          callbacks[_i].apply(this, args);
28921        }
28922      }
28923    }
28924    /**
28925     * Destroys the stream and cleans up.
28926     */;
28927
28928    _proto.dispose = function dispose() {
28929      this.listeners = {};
28930    }
28931    /**
28932     * Forwards all `data` events on this stream to the destination stream. The
28933     * destination stream should provide a method `push` to receive the data
28934     * events as they arrive.
28935     *
28936     * @param {Stream} destination the stream that will receive all `data` events
28937     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
28938     */;
28939
28940    _proto.pipe = function pipe(destination) {
28941      this.on('data', function (data) {
28942        destination.push(data);
28943      });
28944    };
28945    return Stream;
28946  }();
28947
28948  var atob$1 = function atob(s) {
28949    return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
28950  };
28951  function decodeB64ToUint8Array$1(b64Text) {
28952    var decodedString = atob$1(b64Text);
28953    var array = new Uint8Array(decodedString.length);
28954    for (var i = 0; i < decodedString.length; i++) {
28955      array[i] = decodedString.charCodeAt(i);
28956    }
28957    return array;
28958  }
28959
28960  /*! @name m3u8-parser @version 7.1.0 @license Apache-2.0 */
28961
28962  /**
28963   * @file m3u8/line-stream.js
28964   */
28965  /**
28966   * A stream that buffers string input and generates a `data` event for each
28967   * line.
28968   *
28969   * @class LineStream
28970   * @extends Stream
28971   */
28972
28973  class LineStream extends Stream {
28974    constructor() {
28975      super();
28976      this.buffer = '';
28977    }
28978    /**
28979     * Add new data to be parsed.
28980     *
28981     * @param {string} data the text to process
28982     */
28983
28984    push(data) {
28985      let nextNewline;
28986      this.buffer += data;
28987      nextNewline = this.buffer.indexOf('\n');
28988      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
28989        this.trigger('data', this.buffer.substring(0, nextNewline));
28990        this.buffer = this.buffer.substring(nextNewline + 1);
28991      }
28992    }
28993  }
28994  const TAB = String.fromCharCode(0x09);
28995  const parseByterange = function (byterangeString) {
28996    // optionally match and capture 0+ digits before `@`
28997    // optionally match and capture 0+ digits after `@`
28998    const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
28999    const result = {};
29000    if (match[1]) {
29001      result.length = parseInt(match[1], 10);
29002    }
29003    if (match[2]) {
29004      result.offset = parseInt(match[2], 10);
29005    }
29006    return result;
29007  };
29008  /**
29009   * "forgiving" attribute list psuedo-grammar:
29010   * attributes -> keyvalue (',' keyvalue)*
29011   * keyvalue   -> key '=' value
29012   * key        -> [^=]*
29013   * value      -> '"' [^"]* '"' | [^,]*
29014   */
29015
29016  const attributeSeparator = function () {
29017    const key = '[^=]*';
29018    const value = '"[^"]*"|[^,]*';
29019    const keyvalue = '(?:' + key + ')=(?:' + value + ')';
29020    return new RegExp('(?:^|,)(' + keyvalue + ')');
29021  };
29022  /**
29023   * Parse attributes from a line given the separator
29024   *
29025   * @param {string} attributes the attribute line to parse
29026   */
29027
29028  const parseAttributes$1 = function (attributes) {
29029    const result = {};
29030    if (!attributes) {
29031      return result;
29032    } // split the string using attributes as the separator
29033
29034    const attrs = attributes.split(attributeSeparator());
29035    let i = attrs.length;
29036    let attr;
29037    while (i--) {
29038      // filter out unmatched portions of the string
29039      if (attrs[i] === '') {
29040        continue;
29041      } // split the key and value
29042
29043      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
29044
29045      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
29046      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
29047      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
29048      result[attr[0]] = attr[1];
29049    }
29050    return result;
29051  };
29052  /**
29053   * A line-level M3U8 parser event stream. It expects to receive input one
29054   * line at a time and performs a context-free parse of its contents. A stream
29055   * interpretation of a manifest can be useful if the manifest is expected to
29056   * be too large to fit comfortably into memory or the entirety of the input
29057   * is not immediately available. Otherwise, it's probably much easier to work
29058   * with a regular `Parser` object.
29059   *
29060   * Produces `data` events with an object that captures the parser's
29061   * interpretation of the input. That object has a property `tag` that is one
29062   * of `uri`, `comment`, or `tag`. URIs only have a single additional
29063   * property, `line`, which captures the entirety of the input without
29064   * interpretation. Comments similarly have a single additional property
29065   * `text` which is the input without the leading `#`.
29066   *
29067   * Tags always have a property `tagType` which is the lower-cased version of
29068   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
29069   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
29070   * tags are given the tag type `unknown` and a single additional property
29071   * `data` with the remainder of the input.
29072   *
29073   * @class ParseStream
29074   * @extends Stream
29075   */
29076
29077  class ParseStream extends Stream {
29078    constructor() {
29079      super();
29080      this.customParsers = [];
29081      this.tagMappers = [];
29082    }
29083    /**
29084     * Parses an additional line of input.
29085     *
29086     * @param {string} line a single line of an M3U8 file to parse
29087     */
29088
29089    push(line) {
29090      let match;
29091      let event; // strip whitespace
29092
29093      line = line.trim();
29094      if (line.length === 0) {
29095        // ignore empty lines
29096        return;
29097      } // URIs
29098
29099      if (line[0] !== '#') {
29100        this.trigger('data', {
29101          type: 'uri',
29102          uri: line
29103        });
29104        return;
29105      } // map tags
29106
29107      const newLines = this.tagMappers.reduce((acc, mapper) => {
29108        const mappedLine = mapper(line); // skip if unchanged
29109
29110        if (mappedLine === line) {
29111          return acc;
29112        }
29113        return acc.concat([mappedLine]);
29114      }, [line]);
29115      newLines.forEach(newLine => {
29116        for (let i = 0; i < this.customParsers.length; i++) {
29117          if (this.customParsers[i].call(this, newLine)) {
29118            return;
29119          }
29120        } // Comments
29121
29122        if (newLine.indexOf('#EXT') !== 0) {
29123          this.trigger('data', {
29124            type: 'comment',
29125            text: newLine.slice(1)
29126          });
29127          return;
29128        } // strip off any carriage returns here so the regex matching
29129        // doesn't have to account for them.
29130
29131        newLine = newLine.replace('\r', ''); // Tags
29132
29133        match = /^#EXTM3U/.exec(newLine);
29134        if (match) {
29135          this.trigger('data', {
29136            type: 'tag',
29137            tagType: 'm3u'
29138          });
29139          return;
29140        }
29141        match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
29142        if (match) {
29143          event = {
29144            type: 'tag',
29145            tagType: 'inf'
29146          };
29147          if (match[1]) {
29148            event.duration = parseFloat(match[1]);
29149          }
29150          if (match[2]) {
29151            event.title = match[2];
29152          }
29153          this.trigger('data', event);
29154          return;
29155        }
29156        match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
29157        if (match) {
29158          event = {
29159            type: 'tag',
29160            tagType: 'targetduration'
29161          };
29162          if (match[1]) {
29163            event.duration = parseInt(match[1], 10);
29164          }
29165          this.trigger('data', event);
29166          return;
29167        }
29168        match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
29169        if (match) {
29170          event = {
29171            type: 'tag',
29172            tagType: 'version'
29173          };
29174          if (match[1]) {
29175            event.version = parseInt(match[1], 10);
29176          }
29177          this.trigger('data', event);
29178          return;
29179        }
29180        match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
29181        if (match) {
29182          event = {
29183            type: 'tag',
29184            tagType: 'media-sequence'
29185          };
29186          if (match[1]) {
29187            event.number = parseInt(match[1], 10);
29188          }
29189          this.trigger('data', event);
29190          return;
29191        }
29192        match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
29193        if (match) {
29194          event = {
29195            type: 'tag',
29196            tagType: 'discontinuity-sequence'
29197          };
29198          if (match[1]) {
29199            event.number = parseInt(match[1], 10);
29200          }
29201          this.trigger('data', event);
29202          return;
29203        }
29204        match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
29205        if (match) {
29206          event = {
29207            type: 'tag',
29208            tagType: 'playlist-type'
29209          };
29210          if (match[1]) {
29211            event.playlistType = match[1];
29212          }
29213          this.trigger('data', event);
29214          return;
29215        }
29216        match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
29217        if (match) {
29218          event = _extends$1(parseByterange(match[1]), {
29219            type: 'tag',
29220            tagType: 'byterange'
29221          });
29222          this.trigger('data', event);
29223          return;
29224        }
29225        match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
29226        if (match) {
29227          event = {
29228            type: 'tag',
29229            tagType: 'allow-cache'
29230          };
29231          if (match[1]) {
29232            event.allowed = !/NO/.test(match[1]);
29233          }
29234          this.trigger('data', event);
29235          return;
29236        }
29237        match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
29238        if (match) {
29239          event = {
29240            type: 'tag',
29241            tagType: 'map'
29242          };
29243          if (match[1]) {
29244            const attributes = parseAttributes$1(match[1]);
29245            if (attributes.URI) {
29246              event.uri = attributes.URI;
29247            }
29248            if (attributes.BYTERANGE) {
29249              event.byterange = parseByterange(attributes.BYTERANGE);
29250            }
29251          }
29252          this.trigger('data', event);
29253          return;
29254        }
29255        match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
29256        if (match) {
29257          event = {
29258            type: 'tag',
29259            tagType: 'stream-inf'
29260          };
29261          if (match[1]) {
29262            event.attributes = parseAttributes$1(match[1]);
29263            if (event.attributes.RESOLUTION) {
29264              const split = event.attributes.RESOLUTION.split('x');
29265              const resolution = {};
29266              if (split[0]) {
29267                resolution.width = parseInt(split[0], 10);
29268              }
29269              if (split[1]) {
29270                resolution.height = parseInt(split[1], 10);
29271              }
29272              event.attributes.RESOLUTION = resolution;
29273            }
29274            if (event.attributes.BANDWIDTH) {
29275              event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
29276            }
29277            if (event.attributes['FRAME-RATE']) {
29278              event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
29279            }
29280            if (event.attributes['PROGRAM-ID']) {
29281              event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
29282            }
29283          }
29284          this.trigger('data', event);
29285          return;
29286        }
29287        match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
29288        if (match) {
29289          event = {
29290            type: 'tag',
29291            tagType: 'media'
29292          };
29293          if (match[1]) {
29294            event.attributes = parseAttributes$1(match[1]);
29295          }
29296          this.trigger('data', event);
29297          return;
29298        }
29299        match = /^#EXT-X-ENDLIST/.exec(newLine);
29300        if (match) {
29301          this.trigger('data', {
29302            type: 'tag',
29303            tagType: 'endlist'
29304          });
29305          return;
29306        }
29307        match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
29308        if (match) {
29309          this.trigger('data', {
29310            type: 'tag',
29311            tagType: 'discontinuity'
29312          });
29313          return;
29314        }
29315        match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
29316        if (match) {
29317          event = {
29318            type: 'tag',
29319            tagType: 'program-date-time'
29320          };
29321          if (match[1]) {
29322            event.dateTimeString = match[1];
29323            event.dateTimeObject = new Date(match[1]);
29324          }
29325          this.trigger('data', event);
29326          return;
29327        }
29328        match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
29329        if (match) {
29330          event = {
29331            type: 'tag',
29332            tagType: 'key'
29333          };
29334          if (match[1]) {
29335            event.attributes = parseAttributes$1(match[1]); // parse the IV string into a Uint32Array
29336
29337            if (event.attributes.IV) {
29338              if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
29339                event.attributes.IV = event.attributes.IV.substring(2);
29340              }
29341              event.attributes.IV = event.attributes.IV.match(/.{8}/g);
29342              event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
29343              event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
29344              event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
29345              event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
29346              event.attributes.IV = new Uint32Array(event.attributes.IV);
29347            }
29348          }
29349          this.trigger('data', event);
29350          return;
29351        }
29352        match = /^#EXT-X-START:(.*)$/.exec(newLine);
29353        if (match) {
29354          event = {
29355            type: 'tag',
29356            tagType: 'start'
29357          };
29358          if (match[1]) {
29359            event.attributes = parseAttributes$1(match[1]);
29360            event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
29361            event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
29362          }
29363          this.trigger('data', event);
29364          return;
29365        }
29366        match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
29367        if (match) {
29368          event = {
29369            type: 'tag',
29370            tagType: 'cue-out-cont'
29371          };
29372          if (match[1]) {
29373            event.data = match[1];
29374          } else {
29375            event.data = '';
29376          }
29377          this.trigger('data', event);
29378          return;
29379        }
29380        match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
29381        if (match) {
29382          event = {
29383            type: 'tag',
29384            tagType: 'cue-out'
29385          };
29386          if (match[1]) {
29387            event.data = match[1];
29388          } else {
29389            event.data = '';
29390          }
29391          this.trigger('data', event);
29392          return;
29393        }
29394        match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
29395        if (match) {
29396          event = {
29397            type: 'tag',
29398            tagType: 'cue-in'
29399          };
29400          if (match[1]) {
29401            event.data = match[1];
29402          } else {
29403            event.data = '';
29404          }
29405          this.trigger('data', event);
29406          return;
29407        }
29408        match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
29409        if (match && match[1]) {
29410          event = {
29411            type: 'tag',
29412            tagType: 'skip'
29413          };
29414          event.attributes = parseAttributes$1(match[1]);
29415          if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
29416            event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
29417          }
29418          if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
29419            event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
29420          }
29421          this.trigger('data', event);
29422          return;
29423        }
29424        match = /^#EXT-X-PART:(.*)$/.exec(newLine);
29425        if (match && match[1]) {
29426          event = {
29427            type: 'tag',
29428            tagType: 'part'
29429          };
29430          event.attributes = parseAttributes$1(match[1]);
29431          ['DURATION'].forEach(function (key) {
29432            if (event.attributes.hasOwnProperty(key)) {
29433              event.attributes[key] = parseFloat(event.attributes[key]);
29434            }
29435          });
29436          ['INDEPENDENT', 'GAP'].forEach(function (key) {
29437            if (event.attributes.hasOwnProperty(key)) {
29438              event.attributes[key] = /YES/.test(event.attributes[key]);
29439            }
29440          });
29441          if (event.attributes.hasOwnProperty('BYTERANGE')) {
29442            event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
29443          }
29444          this.trigger('data', event);
29445          return;
29446        }
29447        match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
29448        if (match && match[1]) {
29449          event = {
29450            type: 'tag',
29451            tagType: 'server-control'
29452          };
29453          event.attributes = parseAttributes$1(match[1]);
29454          ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
29455            if (event.attributes.hasOwnProperty(key)) {
29456              event.attributes[key] = parseFloat(event.attributes[key]);
29457            }
29458          });
29459          ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
29460            if (event.attributes.hasOwnProperty(key)) {
29461              event.attributes[key] = /YES/.test(event.attributes[key]);
29462            }
29463          });
29464          this.trigger('data', event);
29465          return;
29466        }
29467        match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
29468        if (match && match[1]) {
29469          event = {
29470            type: 'tag',
29471            tagType: 'part-inf'
29472          };
29473          event.attributes = parseAttributes$1(match[1]);
29474          ['PART-TARGET'].forEach(function (key) {
29475            if (event.attributes.hasOwnProperty(key)) {
29476              event.attributes[key] = parseFloat(event.attributes[key]);
29477            }
29478          });
29479          this.trigger('data', event);
29480          return;
29481        }
29482        match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
29483        if (match && match[1]) {
29484          event = {
29485            type: 'tag',
29486            tagType: 'preload-hint'
29487          };
29488          event.attributes = parseAttributes$1(match[1]);
29489          ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
29490            if (event.attributes.hasOwnProperty(key)) {
29491              event.attributes[key] = parseInt(event.attributes[key], 10);
29492              const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
29493              event.attributes.byterange = event.attributes.byterange || {};
29494              event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
29495
29496              delete event.attributes[key];
29497            }
29498          });
29499          this.trigger('data', event);
29500          return;
29501        }
29502        match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
29503        if (match && match[1]) {
29504          event = {
29505            type: 'tag',
29506            tagType: 'rendition-report'
29507          };
29508          event.attributes = parseAttributes$1(match[1]);
29509          ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
29510            if (event.attributes.hasOwnProperty(key)) {
29511              event.attributes[key] = parseInt(event.attributes[key], 10);
29512            }
29513          });
29514          this.trigger('data', event);
29515          return;
29516        }
29517        match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
29518        if (match && match[1]) {
29519          event = {
29520            type: 'tag',
29521            tagType: 'daterange'
29522          };
29523          event.attributes = parseAttributes$1(match[1]);
29524          ['ID', 'CLASS'].forEach(function (key) {
29525            if (event.attributes.hasOwnProperty(key)) {
29526              event.attributes[key] = String(event.attributes[key]);
29527            }
29528          });
29529          ['START-DATE', 'END-DATE'].forEach(function (key) {
29530            if (event.attributes.hasOwnProperty(key)) {
29531              event.attributes[key] = new Date(event.attributes[key]);
29532            }
29533          });
29534          ['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
29535            if (event.attributes.hasOwnProperty(key)) {
29536              event.attributes[key] = parseFloat(event.attributes[key]);
29537            }
29538          });
29539          ['END-ON-NEXT'].forEach(function (key) {
29540            if (event.attributes.hasOwnProperty(key)) {
29541              event.attributes[key] = /YES/i.test(event.attributes[key]);
29542            }
29543          });
29544          ['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
29545            if (event.attributes.hasOwnProperty(key)) {
29546              event.attributes[key] = event.attributes[key].toString(16);
29547            }
29548          });
29549          const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
29550          for (const key in event.attributes) {
29551            if (!clientAttributePattern.test(key)) {
29552              continue;
29553            }
29554            const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
29555            const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
29556            event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
29557          }
29558          this.trigger('data', event);
29559          return;
29560        }
29561        match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
29562        if (match) {
29563          this.trigger('data', {
29564            type: 'tag',
29565            tagType: 'independent-segments'
29566          });
29567          return;
29568        }
29569        match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
29570        if (match) {
29571          event = {
29572            type: 'tag',
29573            tagType: 'content-steering'
29574          };
29575          event.attributes = parseAttributes$1(match[1]);
29576          this.trigger('data', event);
29577          return;
29578        } // unknown tag type
29579
29580        this.trigger('data', {
29581          type: 'tag',
29582          data: newLine.slice(4)
29583        });
29584      });
29585    }
29586    /**
29587     * Add a parser for custom headers
29588     *
29589     * @param {Object}   options              a map of options for the added parser
29590     * @param {RegExp}   options.expression   a regular expression to match the custom header
29591     * @param {string}   options.customType   the custom type to register to the output
29592     * @param {Function} [options.dataParser] function to parse the line into an object
29593     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
29594     */
29595
29596    addParser({
29597      expression,
29598      customType,
29599      dataParser,
29600      segment
29601    }) {
29602      if (typeof dataParser !== 'function') {
29603        dataParser = line => line;
29604      }
29605      this.customParsers.push(line => {
29606        const match = expression.exec(line);
29607        if (match) {
29608          this.trigger('data', {
29609            type: 'custom',
29610            data: dataParser(line),
29611            customType,
29612            segment
29613          });
29614          return true;
29615        }
29616      });
29617    }
29618    /**
29619     * Add a custom header mapper
29620     *
29621     * @param {Object}   options
29622     * @param {RegExp}   options.expression   a regular expression to match the custom header
29623     * @param {Function} options.map          function to translate tag into a different tag
29624     */
29625
29626    addTagMapper({
29627      expression,
29628      map
29629    }) {
29630      const mapFn = line => {
29631        if (expression.test(line)) {
29632          return map(line);
29633        }
29634        return line;
29635      };
29636      this.tagMappers.push(mapFn);
29637    }
29638  }
29639  const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
29640  const camelCaseKeys = function (attributes) {
29641    const result = {};
29642    Object.keys(attributes).forEach(function (key) {
29643      result[camelCase(key)] = attributes[key];
29644    });
29645    return result;
29646  }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
29647  // we need this helper because defaults are based upon targetDuration and
29648  // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
29649  // target durations are set.
29650
29651  const setHoldBack = function (manifest) {
29652    const {
29653      serverControl,
29654      targetDuration,
29655      partTargetDuration
29656    } = manifest;
29657    if (!serverControl) {
29658      return;
29659    }
29660    const tag = '#EXT-X-SERVER-CONTROL';
29661    const hb = 'holdBack';
29662    const phb = 'partHoldBack';
29663    const minTargetDuration = targetDuration && targetDuration * 3;
29664    const minPartDuration = partTargetDuration && partTargetDuration * 2;
29665    if (targetDuration && !serverControl.hasOwnProperty(hb)) {
29666      serverControl[hb] = minTargetDuration;
29667      this.trigger('info', {
29668        message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
29669      });
29670    }
29671    if (minTargetDuration && serverControl[hb] < minTargetDuration) {
29672      this.trigger('warn', {
29673        message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
29674      });
29675      serverControl[hb] = minTargetDuration;
29676    } // default no part hold back to part target duration * 3
29677
29678    if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
29679      serverControl[phb] = partTargetDuration * 3;
29680      this.trigger('info', {
29681        message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
29682      });
29683    } // if part hold back is too small default it to part target duration * 2
29684
29685    if (partTargetDuration && serverControl[phb] < minPartDuration) {
29686      this.trigger('warn', {
29687        message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
29688      });
29689      serverControl[phb] = minPartDuration;
29690    }
29691  };
29692  /**
29693   * A parser for M3U8 files. The current interpretation of the input is
29694   * exposed as a property `manifest` on parser objects. It's just two lines to
29695   * create and parse a manifest once you have the contents available as a string:
29696   *
29697   * ```js
29698   * var parser = new m3u8.Parser();
29699   * parser.push(xhr.responseText);
29700   * ```
29701   *
29702   * New input can later be applied to update the manifest object by calling
29703   * `push` again.
29704   *
29705   * The parser attempts to create a usable manifest object even if the
29706   * underlying input is somewhat nonsensical. It emits `info` and `warning`
29707   * events during the parse if it encounters input that seems invalid or
29708   * requires some property of the manifest object to be defaulted.
29709   *
29710   * @class Parser
29711   * @extends Stream
29712   */
29713
29714  class Parser extends Stream {
29715    constructor() {
29716      super();
29717      this.lineStream = new LineStream();
29718      this.parseStream = new ParseStream();
29719      this.lineStream.pipe(this.parseStream);
29720      this.lastProgramDateTime = null;
29721      /* eslint-disable consistent-this */
29722
29723      const self = this;
29724      /* eslint-enable consistent-this */
29725
29726      const uris = [];
29727      let currentUri = {}; // if specified, the active EXT-X-MAP definition
29728
29729      let currentMap; // if specified, the active decryption key
29730
29731      let key;
29732      let hasParts = false;
29733      const noop = function () {};
29734      const defaultMediaGroups = {
29735        'AUDIO': {},
29736        'VIDEO': {},
29737        'CLOSED-CAPTIONS': {},
29738        'SUBTITLES': {}
29739      }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
29740      // used in MPDs with Widevine encrypted streams.
29741
29742      const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
29743
29744      let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
29745
29746      this.manifest = {
29747        allowCache: true,
29748        discontinuityStarts: [],
29749        dateRanges: [],
29750        segments: []
29751      }; // keep track of the last seen segment's byte range end, as segments are not required
29752      // to provide the offset, in which case it defaults to the next byte after the
29753      // previous segment
29754
29755      let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
29756
29757      let lastPartByterangeEnd = 0;
29758      const dateRangeTags = {};
29759      this.on('end', () => {
29760        // only add preloadSegment if we don't yet have a uri for it.
29761        // and we actually have parts/preloadHints
29762        if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
29763          return;
29764        }
29765        if (!currentUri.map && currentMap) {
29766          currentUri.map = currentMap;
29767        }
29768        if (!currentUri.key && key) {
29769          currentUri.key = key;
29770        }
29771        if (!currentUri.timeline && typeof currentTimeline === 'number') {
29772          currentUri.timeline = currentTimeline;
29773        }
29774        this.manifest.preloadSegment = currentUri;
29775      }); // update the manifest with the m3u8 entry from the parse stream
29776
29777      this.parseStream.on('data', function (entry) {
29778        let mediaGroup;
29779        let rendition;
29780        ({
29781          tag() {
29782            // switch based on the tag type
29783            (({
29784              version() {
29785                if (entry.version) {
29786                  this.manifest.version = entry.version;
29787                }
29788              },
29789              'allow-cache'() {
29790                this.manifest.allowCache = entry.allowed;
29791                if (!('allowed' in entry)) {
29792                  this.trigger('info', {
29793                    message: 'defaulting allowCache to YES'
29794                  });
29795                  this.manifest.allowCache = true;
29796                }
29797              },
29798              byterange() {
29799                const byterange = {};
29800                if ('length' in entry) {
29801                  currentUri.byterange = byterange;
29802                  byterange.length = entry.length;
29803                  if (!('offset' in entry)) {
29804                    /*
29805                     * From the latest spec (as of this writing):
29806                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
29807                     *
29808                     * Same text since EXT-X-BYTERANGE's introduction in draft 7:
29809                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
29810                     *
29811                     * "If o [offset] is not present, the sub-range begins at the next byte
29812                     * following the sub-range of the previous media segment."
29813                     */
29814                    entry.offset = lastByterangeEnd;
29815                  }
29816                }
29817                if ('offset' in entry) {
29818                  currentUri.byterange = byterange;
29819                  byterange.offset = entry.offset;
29820                }
29821                lastByterangeEnd = byterange.offset + byterange.length;
29822              },
29823              endlist() {
29824                this.manifest.endList = true;
29825              },
29826              inf() {
29827                if (!('mediaSequence' in this.manifest)) {
29828                  this.manifest.mediaSequence = 0;
29829                  this.trigger('info', {
29830                    message: 'defaulting media sequence to zero'
29831                  });
29832                }
29833                if (!('discontinuitySequence' in this.manifest)) {
29834                  this.manifest.discontinuitySequence = 0;
29835                  this.trigger('info', {
29836                    message: 'defaulting discontinuity sequence to zero'
29837                  });
29838                }
29839                if (entry.title) {
29840                  currentUri.title = entry.title;
29841                }
29842                if (entry.duration > 0) {
29843                  currentUri.duration = entry.duration;
29844                }
29845                if (entry.duration === 0) {
29846                  currentUri.duration = 0.01;
29847                  this.trigger('info', {
29848                    message: 'updating zero segment duration to a small value'
29849                  });
29850                }
29851                this.manifest.segments = uris;
29852              },
29853              key() {
29854                if (!entry.attributes) {
29855                  this.trigger('warn', {
29856                    message: 'ignoring key declaration without attribute list'
29857                  });
29858                  return;
29859                } // clear the active encryption key
29860
29861                if (entry.attributes.METHOD === 'NONE') {
29862                  key = null;
29863                  return;
29864                }
29865                if (!entry.attributes.URI) {
29866                  this.trigger('warn', {
29867                    message: 'ignoring key declaration without URI'
29868                  });
29869                  return;
29870                }
29871                if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
29872                  this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
29873
29874                  this.manifest.contentProtection['com.apple.fps.1_0'] = {
29875                    attributes: entry.attributes
29876                  };
29877                  return;
29878                }
29879                if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
29880                  this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
29881
29882                  this.manifest.contentProtection['com.microsoft.playready'] = {
29883                    uri: entry.attributes.URI
29884                  };
29885                  return;
29886                } // check if the content is encrypted for Widevine
29887                // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
29888
29889                if (entry.attributes.KEYFORMAT === widevineUuid) {
29890                  const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
29891                  if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
29892                    this.trigger('warn', {
29893                      message: 'invalid key method provided for Widevine'
29894                    });
29895                    return;
29896                  }
29897                  if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
29898                    this.trigger('warn', {
29899                      message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
29900                    });
29901                  }
29902                  if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
29903                    this.trigger('warn', {
29904                      message: 'invalid key URI provided for Widevine'
29905                    });
29906                    return;
29907                  }
29908                  if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
29909                    this.trigger('warn', {
29910                      message: 'invalid key ID provided for Widevine'
29911                    });
29912                    return;
29913                  } // if Widevine key attributes are valid, store them as `contentProtection`
29914                  // on the manifest to emulate Widevine tag structure in a DASH mpd
29915
29916                  this.manifest.contentProtection = this.manifest.contentProtection || {};
29917                  this.manifest.contentProtection['com.widevine.alpha'] = {
29918                    attributes: {
29919                      schemeIdUri: entry.attributes.KEYFORMAT,
29920                      // remove '0x' from the key id string
29921                      keyId: entry.attributes.KEYID.substring(2)
29922                    },
29923                    // decode the base64-encoded PSSH box
29924                    pssh: decodeB64ToUint8Array$1
vendor: 16,332 bytes, lines 29924-30254
29924(entry.attributes.URI.split(',')[1])
29925                  };
29926                  return;
29927                }
29928                if (!entry.attributes.METHOD) {
29929                  this.trigger('warn', {
29930                    message: 'defaulting key method to AES-128'
29931                  });
29932                } // setup an encryption key for upcoming segments
29933
29934                key = {
29935                  method: entry.attributes.METHOD || 'AES-128',
29936                  uri: entry.attributes.URI
29937                };
29938                if (typeof entry.attributes.IV !== 'undefined') {
29939                  key.iv = entry.attributes.IV;
29940                }
29941              },
29942              'media-sequence'() {
29943                if (!isFinite(entry.number)) {
29944                  this.trigger('warn', {
29945                    message: 'ignoring invalid media sequence: ' + entry.number
29946                  });
29947                  return;
29948                }
29949                this.manifest.mediaSequence = entry.number;
29950              },
29951              'discontinuity-sequence'() {
29952                if (!isFinite(entry.number)) {
29953                  this.trigger('warn', {
29954                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
29955                  });
29956                  return;
29957                }
29958                this.manifest.discontinuitySequence = entry.number;
29959                currentTimeline = entry.number;
29960              },
29961              'playlist-type'() {
29962                if (!/VOD|EVENT/.test(entry.playlistType)) {
29963                  this.trigger('warn', {
29964                    message: 'ignoring unknown playlist type: ' + entry.playlist
29965                  });
29966                  return;
29967                }
29968                this.manifest.playlistType = entry.playlistType;
29969              },
29970              map() {
29971                currentMap = {};
29972                if (entry.uri) {
29973                  currentMap.uri = entry.uri;
29974                }
29975                if (entry.byterange) {
29976                  currentMap.byterange = entry.byterange;
29977                }
29978                if (key) {
29979                  currentMap.key = key;
29980                }
29981              },
29982              'stream-inf'() {
29983                this.manifest.playlists = uris;
29984                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
29985                if (!entry.attributes) {
29986                  this.trigger('warn', {
29987                    message: 'ignoring empty stream-inf attributes'
29988                  });
29989                  return;
29990                }
29991                if (!currentUri.attributes) {
29992                  currentUri.attributes = {};
29993                }
29994                _extends$1(currentUri.attributes, entry.attributes);
29995              },
29996              media() {
29997                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
29998                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
29999                  this.trigger('warn', {
30000                    message: 'ignoring incomplete or missing media group'
30001                  });
30002                  return;
30003                } // find the media group, creating defaults as necessary
30004
30005                const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
30006                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
30007                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
30008
30009                rendition = {
30010                  default: /yes/i.test(entry.attributes.DEFAULT)
30011                };
30012                if (rendition.default) {
30013                  rendition.autoselect = true;
30014                } else {
30015                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
30016                }
30017                if (entry.attributes.LANGUAGE) {
30018                  rendition.language = entry.attributes.LANGUAGE;
30019                }
30020                if (entry.attributes.URI) {
30021                  rendition.uri = entry.attributes.URI;
30022                }
30023                if (entry.attributes['INSTREAM-ID']) {
30024                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
30025                }
30026                if (entry.attributes.CHARACTERISTICS) {
30027                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
30028                }
30029                if (entry.attributes.FORCED) {
30030                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
30031                } // insert the new rendition
30032
30033                mediaGroup[entry.attributes.NAME] = rendition;
30034              },
30035              discontinuity() {
30036                currentTimeline += 1;
30037                currentUri.discontinuity = true;
30038                this.manifest.discontinuityStarts.push(uris.length);
30039              },
30040              'program-date-time'() {
30041                if (typeof this.manifest.dateTimeString === 'undefined') {
30042                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
30043                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
30044                  // to the manifest object
30045                  // TODO: Consider removing this in future major version
30046                  this.manifest.dateTimeString = entry.dateTimeString;
30047                  this.manifest.dateTimeObject = entry.dateTimeObject;
30048                }
30049                currentUri.dateTimeString = entry.dateTimeString;
30050                currentUri.dateTimeObject = entry.dateTimeObject;
30051                const {
30052                  lastProgramDateTime
30053                } = this;
30054                this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
30055                // Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
30056
30057                if (lastProgramDateTime === null) {
30058                  // Extrapolate Program Date Time backward
30059                  // Since it is first program date time occurrence we're assuming that
30060                  // all this.manifest.segments have no program date time info
30061                  this.manifest.segments.reduceRight((programDateTime, segment) => {
30062                    segment.programDateTime = programDateTime - segment.duration * 1000;
30063                    return segment.programDateTime;
30064                  }, this.lastProgramDateTime);
30065                }
30066              },
30067              targetduration() {
30068                if (!isFinite(entry.duration) || entry.duration < 0) {
30069                  this.trigger('warn', {
30070                    message: 'ignoring invalid target duration: ' + entry.duration
30071                  });
30072                  return;
30073                }
30074                this.manifest.targetDuration = entry.duration;
30075                setHoldBack.call(this, this.manifest);
30076              },
30077              start() {
30078                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
30079                  this.trigger('warn', {
30080                    message: 'ignoring start declaration without appropriate attribute list'
30081                  });
30082                  return;
30083                }
30084                this.manifest.start = {
30085                  timeOffset: entry.attributes['TIME-OFFSET'],
30086                  precise: entry.attributes.PRECISE
30087                };
30088              },
30089              'cue-out'() {
30090                currentUri.cueOut = entry.data;
30091              },
30092              'cue-out-cont'() {
30093                currentUri.cueOutCont = entry.data;
30094              },
30095              'cue-in'() {
30096                currentUri.cueIn = entry.data;
30097              },
30098              'skip'() {
30099                this.manifest.skip = camelCaseKeys(entry.attributes);
30100                this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
30101              },
30102              'part'() {
30103                hasParts = true; // parts are always specifed before a segment
30104
30105                const segmentIndex = this.manifest.segments.length;
30106                const part = camelCaseKeys(entry.attributes);
30107                currentUri.parts = currentUri.parts || [];
30108                currentUri.parts.push(part);
30109                if (part.byterange) {
30110                  if (!part.byterange.hasOwnProperty('offset')) {
30111                    part.byterange.offset = lastPartByterangeEnd;
30112                  }
30113                  lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
30114                }
30115                const partIndex = currentUri.parts.length - 1;
30116                this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
30117                if (this.manifest.renditionReports) {
30118                  this.manifest.renditionReports.forEach((r, i) => {
30119                    if (!r.hasOwnProperty('lastPart')) {
30120                      this.trigger('warn', {
30121                        message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
30122                      });
30123                    }
30124                  });
30125                }
30126              },
30127              'server-control'() {
30128                const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
30129                if (!attrs.hasOwnProperty('canBlockReload')) {
30130                  attrs.canBlockReload = false;
30131                  this.trigger('info', {
30132                    message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
30133                  });
30134                }
30135                setHoldBack.call(this, this.manifest);
30136                if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
30137                  this.trigger('warn', {
30138                    message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
30139                  });
30140                }
30141              },
30142              'preload-hint'() {
30143                // parts are always specifed before a segment
30144                const segmentIndex = this.manifest.segments.length;
30145                const hint = camelCaseKeys(entry.attributes);
30146                const isPart = hint.type && hint.type === 'PART';
30147                currentUri.preloadHints = currentUri.preloadHints || [];
30148                currentUri.preloadHints.push(hint);
30149                if (hint.byterange) {
30150                  if (!hint.byterange.hasOwnProperty('offset')) {
30151                    // use last part byterange end or zero if not a part.
30152                    hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
30153                    if (isPart) {
30154                      lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
30155                    }
30156                  }
30157                }
30158                const index = currentUri.preloadHints.length - 1;
30159                this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
30160                if (!hint.type) {
30161                  return;
30162                } // search through all preload hints except for the current one for
30163                // a duplicate type.
30164
30165                for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
30166                  const otherHint = currentUri.preloadHints[i];
30167                  if (!otherHint.type) {
30168                    continue;
30169                  }
30170                  if (otherHint.type === hint.type) {
30171                    this.trigger('warn', {
30172                      message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
30173                    });
30174                  }
30175                }
30176              },
30177              'rendition-report'() {
30178                const report = camelCaseKeys(entry.attributes);
30179                this.manifest.renditionReports = this.manifest.renditionReports || [];
30180                this.manifest.renditionReports.push(report);
30181                const index = this.manifest.renditionReports.length - 1;
30182                const required = ['LAST-MSN', 'URI'];
30183                if (hasParts) {
30184                  required.push('LAST-PART');
30185                }
30186                this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
30187              },
30188              'part-inf'() {
30189                this.manifest.partInf = camelCaseKeys(entry.attributes);
30190                this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
30191                if (this.manifest.partInf.partTarget) {
30192                  this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
30193                }
30194                setHoldBack.call(this, this.manifest);
30195              },
30196              'daterange'() {
30197                this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
30198                const index = this.manifest.dateRanges.length - 1;
30199                this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
30200                const dateRange = this.manifest.dateRanges[index];
30201                if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
30202                  this.trigger('warn', {
30203                    message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
30204                  });
30205                }
30206                if (dateRange.duration && dateRange.duration < 0) {
30207                  this.trigger('warn', {
30208                    message: 'EXT-X-DATERANGE DURATION must not be negative'
30209                  });
30210                }
30211                if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
30212                  this.trigger('warn', {
30213                    message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
30214                  });
30215                }
30216                const endOnNextYes = !!dateRange.endOnNext;
30217                if (endOnNextYes && !dateRange.class) {
30218                  this.trigger('warn', {
30219                    message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
30220                  });
30221                }
30222                if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
30223                  this.trigger('warn', {
30224                    message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
30225                  });
30226                }
30227                if (dateRange.duration && dateRange.endDate) {
30228                  const startDate = dateRange.startDate;
30229                  const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
30230                  this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
30231                }
30232                if (!dateRangeTags[dateRange.id]) {
30233                  dateRangeTags[dateRange.id] = dateRange;
30234                } else {
30235                  for (const attribute in dateRangeTags[dateRange.id]) {
30236                    if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
30237                      this.trigger('warn', {
30238                        message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
30239                      });
30240                      break;
30241                    }
30242                  } // if tags with the same ID do not have conflicting attributes, merge them
30243
30244                  const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
30245                  this.manifest.dateRanges[dateRangeWithSameId] = _extends$1(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
30246                  dateRangeTags[dateRange.id] = _extends$1(dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
30247
30248                  this.manifest.dateRanges.pop();
30249                }
30250              },
30251              'independent-segments'() {
30252                this.manifest.independentSegments = true;
30253              },
30254              '
vendor: 6,772 bytes, lines 30254-30438
30254content-steering'() {
30255                this.manifest.contentSteering = camelCaseKeys(entry.attributes);
30256                this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
30257              }
30258            })[entry.tagType] || noop).call(self);
30259          },
30260          uri() {
30261            currentUri.uri = entry.uri;
30262            uris.push(currentUri); // if no explicit duration was declared, use the target duration
30263
30264            if (this.manifest.targetDuration && !('duration' in currentUri)) {
30265              this.trigger('warn', {
30266                message: 'defaulting segment duration to the target duration'
30267              });
30268              currentUri.duration = this.manifest.targetDuration;
30269            } // annotate with encryption information, if necessary
30270
30271            if (key) {
30272              currentUri.key = key;
30273            }
30274            currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
30275
30276            if (currentMap) {
30277              currentUri.map = currentMap;
30278            } // reset the last byterange end as it needs to be 0 between parts
30279
30280            lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
30281
30282            if (this.lastProgramDateTime !== null) {
30283              currentUri.programDateTime = this.lastProgramDateTime;
30284              this.lastProgramDateTime += currentUri.duration * 1000;
30285            } // prepare for the next URI
30286
30287            currentUri = {};
30288          },
30289          comment() {// comments are not important for playback
30290          },
30291          custom() {
30292            // if this is segment-level data attach the output to the segment
30293            if (entry.segment) {
30294              currentUri.custom = currentUri.custom || {};
30295              currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
30296            } else {
30297              this.manifest.custom = this.manifest.custom || {};
30298              this.manifest.custom[entry.customType] = entry.data;
30299            }
30300          }
30301        })[entry.type].call(self);
30302      });
30303    }
30304    warnOnMissingAttributes_(identifier, attributes, required) {
30305      const missing = [];
30306      required.forEach(function (key) {
30307        if (!attributes.hasOwnProperty(key)) {
30308          missing.push(key);
30309        }
30310      });
30311      if (missing.length) {
30312        this.trigger('warn', {
30313          message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
30314        });
30315      }
30316    }
30317    /**
30318     * Parse the input string and update the manifest object.
30319     *
30320     * @param {string} chunk a potentially incomplete portion of the manifest
30321     */
30322
30323    push(chunk) {
30324      this.lineStream.push(chunk);
30325    }
30326    /**
30327     * Flush any remaining input. This can be handy if the last line of an M3U8
30328     * manifest did not contain a trailing newline but the file has been
30329     * completely received.
30330     */
30331
30332    end() {
30333      // flush any buffered input
30334      this.lineStream.push('\n');
30335      if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
30336        this.trigger('warn', {
30337          message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
30338        });
30339      }
30340      this.lastProgramDateTime = null;
30341      this.trigger('end');
30342    }
30343    /**
30344     * Add an additional parser for non-standard tags
30345     *
30346     * @param {Object}   options              a map of options for the added parser
30347     * @param {RegExp}   options.expression   a regular expression to match the custom header
30348     * @param {string}   options.customType   the custom type to register to the output
30349     * @param {Function} [options.dataParser] function to parse the line into an object
30350     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
30351     */
30352
30353    addParser(options) {
30354      this.parseStream.addParser(options);
30355    }
30356    /**
30357     * Add a custom header mapper
30358     *
30359     * @param {Object}   options
30360     * @param {RegExp}   options.expression   a regular expression to match the custom header
30361     * @param {Function} options.map          function to translate tag into a different tag
30362     */
30363
30364    addTagMapper(options) {
30365      this.parseStream.addTagMapper(options);
30366    }
30367  }
30368
30369  var regexs = {
30370    // to determine mime types
30371    mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
30372    webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
30373    ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
30374    // to determine if a codec is audio or video
30375    video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
30376    audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
30377    text: /^(stpp.ttml.im1t)/,
30378    // mux.js support regex
30379    muxerVideo: /^(avc0?1)/,
30380    muxerAudio: /^(mp4a)/,
30381    // match nothing as muxer does not support text right now.
30382    // there cannot never be a character before the start of a string
30383    // so this matches nothing.
30384    muxerText: /a^/
30385  };
30386  var mediaTypes = ['video', 'audio', 'text'];
30387  var upperMediaTypes = ['Video', 'Audio', 'Text'];
30388  /**
30389   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
30390   * `avc1.<hhhhhh>`
30391   *
30392   * @param {string} codec
30393   *        Codec string to translate
30394   * @return {string}
30395   *         The translated codec string
30396   */
30397
30398  var translateLegacyCodec = function translateLegacyCodec(codec) {
30399    if (!codec) {
30400      return codec;
30401    }
30402    return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
30403      var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
30404      var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
30405      return 'avc1.' + profileHex + '00' + avcLevelHex;
30406    });
30407  };
30408  /**
30409   * @typedef {Object} ParsedCodecInfo
30410   * @property {number} codecCount
30411   *           Number of codecs parsed
30412   * @property {string} [videoCodec]
30413   *           Parsed video codec (if found)
30414   * @property {string} [videoObjectTypeIndicator]
30415   *           Video object type indicator (if found)
30416   * @property {string|null} audioProfile
30417   *           Audio profile
30418   */
30419
30420  /**
30421   * Parses a codec string to retrieve the number of codecs specified, the video codec and
30422   * object type indicator, and the audio profile.
30423   *
30424   * @param {string} [codecString]
30425   *        The codec string to parse
30426   * @return {ParsedCodecInfo}
30427   *         Parsed codec info
30428   */
30429
30430  var parseCodecs = function parseCodecs(codecString) {
30431    if (codecString === void 0) {
30432      codecString = '';
30433    }
30434    var codecs = codecString.split(',');
30435    var result = [];
30436    codecs.forEach(function (codec) {
30437      codec = codec.trim();
30438      var codecType;
vendor: 3,347 bytes, lines 30439-30545
30439      mediaTypes.forEach(function (name) {
30440        var match = regexs[name].exec(codec.toLowerCase());
30441        if (!match || match.length <= 1) {
30442          return;
30443        }
30444        codecType = name; // maintain codec case
30445
30446        var type = codec.substring(0, match[1].length);
30447        var details = codec.replace(type, '');
30448        result.push({
30449          type: type,
30450          details: details,
30451          mediaType: name
30452        });
30453      });
30454      if (!codecType) {
30455        result.push({
30456          type: codec,
30457          details: '',
30458          mediaType: 'unknown'
30459        });
30460      }
30461    });
30462    return result;
30463  };
30464  /**
30465   * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
30466   * a default alternate audio playlist for the provided audio group.
30467   *
30468   * @param {Object} master
30469   *        The master playlist
30470   * @param {string} audioGroupId
30471   *        ID of the audio group for which to find the default codec info
30472   * @return {ParsedCodecInfo}
30473   *         Parsed codec info
30474   */
30475
30476  var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
30477    if (!master.mediaGroups.AUDIO || !audioGroupId) {
30478      return null;
30479    }
30480    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
30481    if (!audioGroup) {
30482      return null;
30483    }
30484    for (var name in audioGroup) {
30485      var audioType = audioGroup[name];
30486      if (audioType.default && audioType.playlists) {
30487        // codec should be the same for all playlists within the audio type
30488        return parseCodecs(audioType.playlists[0].attributes.CODECS);
30489      }
30490    }
30491    return null;
30492  };
30493  var isAudioCodec = function isAudioCodec(codec) {
30494    if (codec === void 0) {
30495      codec = '';
30496    }
30497    return regexs.audio.test(codec.trim().toLowerCase());
30498  };
30499  var isTextCodec = function isTextCodec(codec) {
30500    if (codec === void 0) {
30501      codec = '';
30502    }
30503    return regexs.text.test(codec.trim().toLowerCase());
30504  };
30505  var getMimeForCodec = function getMimeForCodec(codecString) {
30506    if (!codecString || typeof codecString !== 'string') {
30507      return;
30508    }
30509    var codecs = codecString.toLowerCase().split(',').map(function (c) {
30510      return translateLegacyCodec(c.trim());
30511    }); // default to video type
30512
30513    var type = 'video'; // only change to audio type if the only codec we have is
30514    // audio
30515
30516    if (codecs.length === 1 && isAudioCodec(codecs[0])) {
30517      type = 'audio';
30518    } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
30519      // text uses application/<container> for now
30520      type = 'application';
30521    } // default the container to mp4
30522
30523    var container = 'mp4'; // every codec must be able to go into the container
30524    // for that container to be the correct one
30525
30526    if (codecs.every(function (c) {
30527      return regexs.mp4.test(c);
30528    })) {
30529      container = 'mp4';
30530    } else if (codecs.every(function (c) {
30531      return regexs.webm.test(c);
30532    })) {
30533      container = 'webm';
30534    } else if (codecs.every(function (c) {
30535      return regexs.ogg.test(c);
30536    })) {
30537      container = 'ogg';
30538    }
30539    return type + "/" + container + ";codecs=\"" + codecString + "\"";
30540  };
30541  var browserSupportsCodec = function browserSupportsCodec(codecString) {
30542    if (codecString === void 0) {
30543      codecString = '';
30544    }
30545    return window.MediaSource && window.MediaSource.isTypeSupported && window.
vendor: 8,931 bytes, lines 30545-30800
30545MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
30546  };
30547  var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
30548    if (codecString === void 0) {
30549      codecString = '';
30550    }
30551    return codecString.toLowerCase().split(',').every(function (codec) {
30552      codec = codec.trim(); // any match is supported.
30553
30554      for (var i = 0; i < upperMediaTypes.length; i++) {
30555        var type = upperMediaTypes[i];
30556        if (regexs["muxer" + type].test(codec)) {
30557          return true;
30558        }
30559      }
30560      return false;
30561    });
30562  };
30563  var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
30564  var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
30565
30566  var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
30567  var DASH_REGEX = /^application\/dash\+xml/i;
30568  /**
30569   * Returns a string that describes the type of source based on a video source object's
30570   * media type.
30571   *
30572   * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
30573   *
30574   * @param {string} type
30575   *        Video source object media type
30576   * @return {('hls'|'dash'|'vhs-json'|null)}
30577   *         VHS source type string
30578   */
30579
30580  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
30581    if (MPEGURL_REGEX.test(type)) {
30582      return 'hls';
30583    }
30584    if (DASH_REGEX.test(type)) {
30585      return 'dash';
30586    } // Denotes the special case of a manifest object passed to http-streaming instead of a
30587    // source URL.
30588    //
30589    // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
30590    //
30591    // In this case, vnd stands for vendor, video.js for the organization, VHS for this
30592    // project, and the +json suffix identifies the structure of the media type.
30593
30594    if (type === 'application/vnd.videojs.vhs+json') {
30595      return 'vhs-json';
30596    }
30597    return null;
30598  };
30599
30600  // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
30601  // we used to do this with log2 but BigInt does not support builtin math
30602  // Math.ceil(log2(x));
30603
30604  var countBits = function countBits(x) {
30605    return x.toString(2).length;
30606  }; // count the number of whole bytes it would take to represent a number
30607
30608  var countBytes = function countBytes(x) {
30609    return Math.ceil(countBits(x) / 8);
30610  };
30611  var isArrayBufferView = function isArrayBufferView(obj) {
30612    if (ArrayBuffer.isView === 'function') {
30613      return ArrayBuffer.isView(obj);
30614    }
30615    return obj && obj.buffer instanceof ArrayBuffer;
30616  };
30617  var isTypedArray = function isTypedArray(obj) {
30618    return isArrayBufferView(obj);
30619  };
30620  var toUint8 = function toUint8(bytes) {
30621    if (bytes instanceof Uint8Array) {
30622      return bytes;
30623    }
30624    if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
30625      // any non-number or NaN leads to empty uint8array
30626      // eslint-disable-next-line
30627      if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
30628        bytes = 0;
30629      } else {
30630        bytes = [bytes];
30631      }
30632    }
30633    return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
30634  };
30635  var BigInt = window.BigInt || Number;
30636  var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
30637  (function () {
30638    var a = new Uint16Array([0xFFCC]);
30639    var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
30640    if (b[0] === 0xFF) {
30641      return 'big';
30642    }
30643    if (b[0] === 0xCC) {
30644      return 'little';
30645    }
30646    return 'unknown';
30647  })();
30648  var bytesToNumber = function bytesToNumber(bytes, _temp) {
30649    var _ref = _temp === void 0 ? {} : _temp,
30650      _ref$signed = _ref.signed,
30651      signed = _ref$signed === void 0 ? false : _ref$signed,
30652      _ref$le = _ref.le,
30653      le = _ref$le === void 0 ? false : _ref$le;
30654    bytes = toUint8(bytes);
30655    var fn = le ? 'reduce' : 'reduceRight';
30656    var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
30657    var number = obj.call(bytes, function (total, byte, i) {
30658      var exponent = le ? i : Math.abs(i + 1 - bytes.length);
30659      return total + BigInt(byte) * BYTE_TABLE[exponent];
30660    }, BigInt(0));
30661    if (signed) {
30662      var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
30663      number = BigInt(number);
30664      if (number > max) {
30665        number -= max;
30666        number -= max;
30667        number -= BigInt(2);
30668      }
30669    }
30670    return Number(number);
30671  };
30672  var numberToBytes = function numberToBytes(number, _temp2) {
30673    var _ref2 = _temp2 === void 0 ? {} : _temp2,
30674      _ref2$le = _ref2.le,
30675      le = _ref2$le === void 0 ? false : _ref2$le;
30676
30677    // eslint-disable-next-line
30678    if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
30679      number = 0;
30680    }
30681    number = BigInt(number);
30682    var byteCount = countBytes(number);
30683    var bytes = new Uint8Array(new ArrayBuffer(byteCount));
30684    for (var i = 0; i < byteCount; i++) {
30685      var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
30686      bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
30687      if (number < 0) {
30688        bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
30689        bytes[byteIndex] -= i === 0 ? 1 : 2;
30690      }
30691    }
30692    return bytes;
30693  };
30694  var stringToBytes = function stringToBytes(string, stringIsBytes) {
30695    if (typeof string !== 'string' && string && typeof string.toString === 'function') {
30696      string = string.toString();
30697    }
30698    if (typeof string !== 'string') {
30699      return new Uint8Array();
30700    } // If the string already is bytes, we don't have to do this
30701    // otherwise we do this so that we split multi length characters
30702    // into individual bytes
30703
30704    if (!stringIsBytes) {
30705      string = unescape(encodeURIComponent(string));
30706    }
30707    var view = new Uint8Array(string.length);
30708    for (var i = 0; i < string.length; i++) {
30709      view[i] = string.charCodeAt(i);
30710    }
30711    return view;
30712  };
30713  var concatTypedArrays = function concatTypedArrays() {
30714    for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
30715      buffers[_key] = arguments[_key];
30716    }
30717    buffers = buffers.filter(function (b) {
30718      return b && (b.byteLength || b.length) && typeof b !== 'string';
30719    });
30720    if (buffers.length <= 1) {
30721      // for 0 length we will return empty uint8
30722      // for 1 length we return the first uint8
30723      return toUint8(buffers[0]);
30724    }
30725    var totalLen = buffers.reduce(function (total, buf, i) {
30726      return total + (buf.byteLength || buf.length);
30727    }, 0);
30728    var tempBuffer = new Uint8Array(totalLen);
30729    var offset = 0;
30730    buffers.forEach(function (buf) {
30731      buf = toUint8(buf);
30732      tempBuffer.set(buf, offset);
30733      offset += buf.byteLength;
30734    });
30735    return tempBuffer;
30736  };
30737  /**
30738   * Check if the bytes "b" are contained within bytes "a".
30739   *
30740   * @param {Uint8Array|Array} a
30741   *        Bytes to check in
30742   *
30743   * @param {Uint8Array|Array} b
30744   *        Bytes to check for
30745   *
30746   * @param {Object} options
30747   *        options
30748   *
30749   * @param {Array|Uint8Array} [offset=0]
30750   *        offset to use when looking at bytes in a
30751   *
30752   * @param {Array|Uint8Array} [mask=[]]
30753   *        mask to use on bytes before comparison.
30754   *
30755   * @return {boolean}
30756   *         If all bytes in b are inside of a, taking into account
30757   *         bit masks.
30758   */
30759
30760  var bytesMatch = function bytesMatch(a, b, _temp3) {
30761    var _ref3 = _temp3 === void 0 ? {} : _temp3,
30762      _ref3$offset = _ref3.offset,
30763      offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
30764      _ref3$mask = _ref3.mask,
30765      mask = _ref3$mask === void 0 ? [] : _ref3$mask;
30766    a = toUint8(a);
30767    b = toUint8(b); // ie 11 does not support uint8 every
30768
30769    var fn = b.every ? b.every : Array.prototype.every;
30770    return b.length && a.length - offset >= b.length &&
30771    // ie 11 doesn't support every on uin8
30772    fn.call(b, function (bByte, i) {
30773      var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
30774      return bByte === aByte;
30775    });
30776  };
30777
30778  /**
30779   * Loops through all supported media groups in master and calls the provided
30780   * callback for each group
30781   *
30782   * @param {Object} master
30783   *        The parsed master manifest object
30784   * @param {string[]} groups
30785   *        The media groups to call the callback for
30786   * @param {Function} callback
30787   *        Callback to call for each media group
30788   */
30789  var forEachMediaGroup$1 = function forEachMediaGroup(master, groups, callback) {
30790    groups.forEach(function (mediaType) {
30791      for (var groupKey in master.mediaGroups[mediaType]) {
30792        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
30793          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
30794          callback(mediaProperties, mediaType, groupKey, labelKey);
30795        }
30796      }
30797    });
30798  };
30799
30800  
30800var atob = function atob(s) {
30801    return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
30802  };
30803  function decodeB64ToUint8Array(b64Text) {
30804    var decodedString = atob(b64Text);
30805    var array = new Uint8Array(decodedString.length);
30806    for (var i = 0; i < decodedString.length; i++) {
30807      array[i] = decodedString.charCodeAt(i);
30808    }
30809    return array;
30810  }
vendor: 6,685 bytes, lines 30810-30997
30810
30811
30812  /**
30813   * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
30814   *
30815   * Works with anything that has a `length` property and index access properties, including NodeList.
30816   *
30817   * @template {unknown} T
30818   * @param {Array<T> | ({length:number, [number]: T})} list
30819   * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
30820   * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
30821   * 				allows injecting a custom implementation in tests
30822   * @returns {T | undefined}
30823   *
30824   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
30825   * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
30826   */
30827  function find$1(list, predicate, ac) {
30828    if (ac === undefined) {
30829      ac = Array.prototype;
30830    }
30831    if (list && typeof ac.find === 'function') {
30832      return ac.find.call(list, predicate);
30833    }
30834    for (var i = 0; i < list.length; i++) {
30835      if (Object.prototype.hasOwnProperty.call(list, i)) {
30836        var item = list[i];
30837        if (predicate.call(undefined, item, i, list)) {
30838          return item;
30839        }
30840      }
30841    }
30842  }
30843
30844  /**
30845   * "Shallow freezes" an object to render it immutable.
30846   * Uses `Object.freeze` if available,
30847   * otherwise the immutability is only in the type.
30848   *
30849   * Is used to create "enum like" objects.
30850   *
30851   * @template T
30852   * @param {T} object the object to freeze
30853   * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
30854   * 				allows to inject custom object constructor for tests
30855   * @returns {Readonly<T>}
30856   *
30857   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
30858   */
30859  function freeze(object, oc) {
30860    if (oc === undefined) {
30861      oc = Object;
30862    }
30863    return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object;
30864  }
30865
30866  /**
30867   * Since we can not rely on `Object.assign` we provide a simplified version
30868   * that is sufficient for our needs.
30869   *
30870   * @param {Object} target
30871   * @param {Object | null | undefined} source
30872   *
30873   * @returns {Object} target
30874   * @throws TypeError if target is not an object
30875   *
30876   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
30877   * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
30878   */
30879  function assign(target, source) {
30880    if (target === null || typeof target !== 'object') {
30881      throw new TypeError('target is not an object');
30882    }
30883    for (var key in source) {
30884      if (Object.prototype.hasOwnProperty.call(source, key)) {
30885        target[key] = source[key];
30886      }
30887    }
30888    return target;
30889  }
30890
30891  /**
30892   * All mime types that are allowed as input to `DOMParser.parseFromString`
30893   *
30894   * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
30895   * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
30896   * @see DOMParser.prototype.parseFromString
30897   */
30898  var MIME_TYPE = freeze({
30899    /**
30900     * `text/html`, the only mime type that triggers treating an XML document as HTML.
30901     *
30902     * @see DOMParser.SupportedType.isHTML
30903     * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
30904     * @see https://en.wikipedia.org/wiki/HTML Wikipedia
30905     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
30906     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
30907     */
30908    HTML: 'text/html',
30909    /**
30910     * Helper method to check a mime type if it indicates an HTML document
30911     *
30912     * @param {string} [value]
30913     * @returns {boolean}
30914     *
30915     * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
30916     * @see https://en.wikipedia.org/wiki/HTML Wikipedia
30917     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
30918     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring 	 */
30919    isHTML: function (value) {
30920      return value === MIME_TYPE.HTML;
30921    },
30922    /**
30923     * `application/xml`, the standard mime type for XML documents.
30924     *
30925     * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
30926     * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
30927     * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
30928     */
30929    XML_APPLICATION: 'application/xml',
30930    /**
30931     * `text/html`, an alias for `application/xml`.
30932     *
30933     * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
30934     * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
30935     * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
30936     */
30937    XML_TEXT: 'text/xml',
30938    /**
30939     * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
30940     * but is parsed as an XML document.
30941     *
30942     * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
30943     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
30944     * @see https://en.wikipedia.org/wiki/XHTML Wikipedia
30945     */
30946    XML_XHTML_APPLICATION: 'application/xhtml+xml',
30947    /**
30948     * `image/svg+xml`,
30949     *
30950     * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
30951     * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
30952     * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
30953     */
30954    XML_SVG_IMAGE: 'image/svg+xml'
30955  });
30956
30957  /**
30958   * Namespaces that are used in this code base.
30959   *
30960   * @see http://www.w3.org/TR/REC-xml-names
30961   */
30962  var NAMESPACE$3 = freeze({
30963    /**
30964     * The XHTML namespace.
30965     *
30966     * @see http://www.w3.org/1999/xhtml
30967     */
30968    HTML: 'http://www.w3.org/1999/xhtml',
30969    /**
30970     * Checks if `uri` equals `NAMESPACE.HTML`.
30971     *
30972     * @param {string} [uri]
30973     *
30974     * @see NAMESPACE.HTML
30975     */
30976    isHTML: function (uri) {
30977      return uri === NAMESPACE$3.HTML;
30978    },
30979    /**
30980     * The SVG namespace.
30981     *
30982     * @see http://www.w3.org/2000/svg
30983     */
30984    SVG: 'http://www.w3.org/2000/svg',
30985    /**
30986     * The `xml:` namespace.
30987     *
30988     * @see http://www.w3.org/XML/1998/namespace
30989     */
30990    XML: 'http://www.w3.org/XML/1998/namespace',
30991    /**
30992     * The `xmlns:` namespace
30993     *
30994     * @see https://www.w3.org/2000/xmlns/
30995     */
30996    XMLNS: 'http://www.w3.org/2000/xmlns/'
30997  });
vendor: 15,318 bytes, lines 30998-31423
30998  var assign_1 = assign;
30999  var find_1 = find$1;
31000  var freeze_1 = freeze;
31001  var MIME_TYPE_1 = MIME_TYPE;
31002  var NAMESPACE_1 = NAMESPACE$3;
31003  var conventions = {
31004    assign: assign_1,
31005    find: find_1,
31006    freeze: freeze_1,
31007    MIME_TYPE: MIME_TYPE_1,
31008    NAMESPACE: NAMESPACE_1
31009  };
31010
31011  var find = conventions.find;
31012  var NAMESPACE$2 = conventions.NAMESPACE;
31013
31014  /**
31015   * A prerequisite for `[].filter`, to drop elements that are empty
31016   * @param {string} input
31017   * @returns {boolean}
31018   */
31019  function notEmptyString(input) {
31020    return input !== '';
31021  }
31022  /**
31023   * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace
31024   * @see https://infra.spec.whatwg.org/#ascii-whitespace
31025   *
31026   * @param {string} input
31027   * @returns {string[]} (can be empty)
31028   */
31029  function splitOnASCIIWhitespace(input) {
31030    // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE
31031    return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [];
31032  }
31033
31034  /**
31035   * Adds element as a key to current if it is not already present.
31036   *
31037   * @param {Record<string, boolean | undefined>} current
31038   * @param {string} element
31039   * @returns {Record<string, boolean | undefined>}
31040   */
31041  function orderedSetReducer(current, element) {
31042    if (!current.hasOwnProperty(element)) {
31043      current[element] = true;
31044    }
31045    return current;
31046  }
31047
31048  /**
31049   * @see https://infra.spec.whatwg.org/#ordered-set
31050   * @param {string} input
31051   * @returns {string[]}
31052   */
31053  function toOrderedSet(input) {
31054    if (!input) return [];
31055    var list = splitOnASCIIWhitespace(input);
31056    return Object.keys(list.reduce(orderedSetReducer, {}));
31057  }
31058
31059  /**
31060   * Uses `list.indexOf` to implement something like `Array.prototype.includes`,
31061   * which we can not rely on being available.
31062   *
31063   * @param {any[]} list
31064   * @returns {function(any): boolean}
31065   */
31066  function arrayIncludes(list) {
31067    return function (element) {
31068      return list && list.indexOf(element) !== -1;
31069    };
31070  }
31071  function copy(src, dest) {
31072    for (var p in src) {
31073      if (Object.prototype.hasOwnProperty.call(src, p)) {
31074        dest[p] = src[p];
31075      }
31076    }
31077  }
31078
31079  /**
31080  ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
31081  ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
31082   */
31083  function _extends(Class, Super) {
31084    var pt = Class.prototype;
31085    if (!(pt instanceof Super)) {
31086      function t() {}
31087      t.prototype = Super.prototype;
31088      t = new t();
31089      copy(pt, t);
31090      Class.prototype = pt = t;
31091    }
31092    if (pt.constructor != Class) {
31093      if (typeof Class != 'function') {
31094        console.error("unknown Class:" + Class);
31095      }
31096      pt.constructor = Class;
31097    }
31098  }
31099
31100  // Node Types
31101  var NodeType = {};
31102  var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1;
31103  var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2;
31104  var TEXT_NODE = NodeType.TEXT_NODE = 3;
31105  var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4;
31106  var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5;
31107  var ENTITY_NODE = NodeType.ENTITY_NODE = 6;
31108  var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
31109  var COMMENT_NODE = NodeType.COMMENT_NODE = 8;
31110  var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9;
31111  var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10;
31112  var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11;
31113  var NOTATION_NODE = NodeType.NOTATION_NODE = 12;
31114
31115  // ExceptionCode
31116  var ExceptionCode = {};
31117  var ExceptionMessage = {};
31118  ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1);
31119  ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2);
31120  var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3);
31121  ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4);
31122  ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5);
31123  ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6);
31124  ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7);
31125  var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8);
31126  ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9);
31127  var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10);
31128  //level2
31129  ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11);
31130  ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12);
31131  ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13);
31132  ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14);
31133  ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15);
31134
31135  /**
31136   * DOM Level 2
31137   * Object DOMException
31138   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
31139   * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
31140   */
31141  function DOMException(code, message) {
31142    if (message instanceof Error) {
31143      var error = message;
31144    } else {
31145      error = this;
31146      Error.call(this, ExceptionMessage[code]);
31147      this.message = ExceptionMessage[code];
31148      if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
31149    }
31150    error.code = code;
31151    if (message) this.message = this.message + ": " + message;
31152    return error;
31153  }
31154  DOMException.prototype = Error.prototype;
31155  copy(ExceptionCode, DOMException);
31156
31157  /**
31158   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
31159   * 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.
31160   * The items in the NodeList are accessible via an integral index, starting from 0.
31161   */
31162  function NodeList() {}
31163  NodeList.prototype = {
31164    /**
31165     * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
31166     * @standard level1
31167     */
31168    length: 0,
31169    /**
31170     * 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.
31171     * @standard level1
31172     * @param index  unsigned long
31173     *   Index into the collection.
31174     * @return Node
31175     * 	The node at the indexth position in the NodeList, or null if that is not a valid index.
31176     */
31177    item: function (index) {
31178      return index >= 0 && index < this.length ? this[index] : null;
31179    },
31180    toString: function (isHTML, nodeFilter) {
31181      for (var buf = [], i = 0; i < this.length; i++) {
31182        serializeToString(this[i], buf, isHTML, nodeFilter);
31183      }
31184      return buf.join('');
31185    },
31186    /**
31187     * @private
31188     * @param {function (Node):boolean} predicate
31189     * @returns {Node[]}
31190     */
31191    filter: function (predicate) {
31192      return Array.prototype.filter.call(this, predicate);
31193    },
31194    /**
31195     * @private
31196     * @param {Node} item
31197     * @returns {number}
31198     */
31199    indexOf: function (item) {
31200      return Array.prototype.indexOf.call(this, item);
31201    }
31202  };
31203  function LiveNodeList(node, refresh) {
31204    this._node = node;
31205    this._refresh = refresh;
31206    _updateLiveList(this);
31207  }
31208  function _updateLiveList(list) {
31209    var inc = list._node._inc || list._node.ownerDocument._inc;
31210    if (list._inc !== inc) {
31211      var ls = list._refresh(list._node);
31212      __set__(list, 'length', ls.length);
31213      if (!list.$$length || ls.length < list.$$length) {
31214        for (var i = ls.length; (i in list); i++) {
31215          if (Object.prototype.hasOwnProperty.call(list, i)) {
31216            delete list[i];
31217          }
31218        }
31219      }
31220      copy(ls, list);
31221      list._inc = inc;
31222    }
31223  }
31224  LiveNodeList.prototype.item = function (i) {
31225    _updateLiveList(this);
31226    return this[i] || null;
31227  };
31228  _extends(LiveNodeList, NodeList);
31229
31230  /**
31231   * Objects implementing the NamedNodeMap interface are used
31232   * to represent collections of nodes that can be accessed by name.
31233   * Note that NamedNodeMap does not inherit from NodeList;
31234   * NamedNodeMaps are not maintained in any particular order.
31235   * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
31236   * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
31237   * and does not imply that the DOM specifies an order to these Nodes.
31238   * NamedNodeMap objects in the DOM are live.
31239   * used for attributes or DocumentType entities
31240   */
31241  function NamedNodeMap() {}
31242  function _findNodeIndex(list, node) {
31243    var i = list.length;
31244    while (i--) {
31245      if (list[i] === node) {
31246        return i;
31247      }
31248    }
31249  }
31250  function _addNamedNode(el, list, newAttr, oldAttr) {
31251    if (oldAttr) {
31252      list[_findNodeIndex(list, oldAttr)] = newAttr;
31253    } else {
31254      list[list.length++] = newAttr;
31255    }
31256    if (el) {
31257      newAttr.ownerElement = el;
31258      var doc = el.ownerDocument;
31259      if (doc) {
31260        oldAttr && _onRemoveAttribute(doc, el, oldAttr);
31261        _onAddAttribute(doc, el, newAttr);
31262      }
31263    }
31264  }
31265  function _removeNamedNode(el, list, attr) {
31266    //console.log('remove attr:'+attr)
31267    var i = _findNodeIndex(list, attr);
31268    if (i >= 0) {
31269      var lastIndex = list.length - 1;
31270      while (i < lastIndex) {
31271        list[i] = list[++i];
31272      }
31273      list.length = lastIndex;
31274      if (el) {
31275        var doc = el.ownerDocument;
31276        if (doc) {
31277          _onRemoveAttribute(doc, el, attr);
31278          attr.ownerElement = null;
31279        }
31280      }
31281    } else {
31282      throw new DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
31283    }
31284  }
31285  NamedNodeMap.prototype = {
31286    length: 0,
31287    item: NodeList.prototype.item,
31288    getNamedItem: function (key) {
31289      //		if(key.indexOf(':')>0 || key == 'xmlns'){
31290      //			return null;
31291      //		}
31292      //console.log()
31293      var i = this.length;
31294      while (i--) {
31295        var attr = this[i];
31296        //console.log(attr.nodeName,key)
31297        if (attr.nodeName == key) {
31298          return attr;
31299        }
31300      }
31301    },
31302    setNamedItem: function (attr) {
31303      var el = attr.ownerElement;
31304      if (el && el != this._ownerElement) {
31305        throw new DOMException(INUSE_ATTRIBUTE_ERR);
31306      }
31307      var oldAttr = this.getNamedItem(attr.nodeName);
31308      _addNamedNode(this._ownerElement, this, attr, oldAttr);
31309      return oldAttr;
31310    },
31311    /* returns Node */
31312    setNamedItemNS: function (attr) {
31313      // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
31314      var el = attr.ownerElement,
31315        oldAttr;
31316      if (el && el != this._ownerElement) {
31317        throw new DOMException(INUSE_ATTRIBUTE_ERR);
31318      }
31319      oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
31320      _addNamedNode(this._ownerElement, this, attr, oldAttr);
31321      return oldAttr;
31322    },
31323    /* returns Node */
31324    removeNamedItem: function (key) {
31325      var attr = this.getNamedItem(key);
31326      _removeNamedNode(this._ownerElement, this, attr);
31327      return attr;
31328    },
31329    // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
31330
31331    //for level2
31332    removeNamedItemNS: function (namespaceURI, localName) {
31333      var attr = this.getNamedItemNS(namespaceURI, localName);
31334      _removeNamedNode(this._ownerElement, this, attr);
31335      return attr;
31336    },
31337    getNamedItemNS: function (namespaceURI, localName) {
31338      var i = this.length;
31339      while (i--) {
31340        var node = this[i];
31341        if (node.localName == localName && node.namespaceURI == namespaceURI) {
31342          return node;
31343        }
31344      }
31345      return null;
31346    }
31347  };
31348
31349  /**
31350   * The DOMImplementation interface represents an object providing methods
31351   * which are not dependent on any particular document.
31352   * Such an object is returned by the `Document.implementation` property.
31353   *
31354   * __The individual methods describe the differences compared to the specs.__
31355   *
31356   * @constructor
31357   *
31358   * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
31359   * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
31360   * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
31361   * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
31362   * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
31363   */
31364  function DOMImplementation$1() {}
31365  DOMImplementation$1.prototype = {
31366    /**
31367     * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
31368     * The different implementations fairly diverged in what kind of features were reported.
31369     * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
31370     *
31371     * @deprecated It is deprecated and modern browsers return true in all cases.
31372     *
31373     * @param {string} feature
31374     * @param {string} [version]
31375     * @returns {boolean} always true
31376     *
31377     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
31378     * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
31379     * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
31380     */
31381    hasFeature: function (feature, version) {
31382      return true;
31383    },
31384    /**
31385     * Creates an XML Document object of the specified type with its document element.
31386     *
31387     * __It behaves slightly different from the description in the living standard__:
31388     * - There is no interface/class `XMLDocument`, it returns a `Document` instance.
31389     * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
31390     * - this implementation is not validating names or qualified names
31391     *   (when parsing XML strings, the SAX parser takes care of that)
31392     *
31393     * @param {string|null} namespaceURI
31394     * @param {string} qualifiedName
31395     * @param {DocumentType=null} doctype
31396     * @returns {Document}
31397     *
31398     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
31399     * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
31400     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument  DOM Level 2 Core
31401     *
31402     * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
31403     * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
31404     * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
31405     */
31406    createDocument: function (namespaceURI, qualifiedName, doctype) {
31407      var doc = new Document();
31408      doc.implementation = this;
31409      doc.childNodes = new NodeList();
31410      doc.doctype = doctype || null;
31411      if (doctype) {
31412        doc.appendChild(doctype);
31413      }
31414      if (qualifiedName) {
31415        var root = doc.createElementNS(namespaceURI, qualifiedName);
31416        doc.appendChild(root);
31417      }
31418      return doc;
31419    },
31420    /**
31421     * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
31422     *
31423     * __This behavior is slightly different from the in the specs__:
vendor: 5,159 bytes, lines 31424-31567
31424     * - this implementation is not validating names or qualified names
31425     *   (when parsing XML strings, the SAX parser takes care of that)
31426     *
31427     * @param {string} qualifiedName
31428     * @param {string} [publicId]
31429     * @param {string} [systemId]
31430     * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
31431     * 				  or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
31432     *
31433     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
31434     * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
31435     * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
31436     *
31437     * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
31438     * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
31439     * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
31440     */
31441    createDocumentType: function (qualifiedName, publicId, systemId) {
31442      var node = new DocumentType();
31443      node.name = qualifiedName;
31444      node.nodeName = qualifiedName;
31445      node.publicId = publicId || '';
31446      node.systemId = systemId || '';
31447      return node;
31448    }
31449  };
31450
31451  /**
31452   * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
31453   */
31454
31455  function Node() {}
31456  Node.prototype = {
31457    firstChild: null,
31458    lastChild: null,
31459    previousSibling: null,
31460    nextSibling: null,
31461    attributes: null,
31462    parentNode: null,
31463    childNodes: null,
31464    ownerDocument: null,
31465    nodeValue: null,
31466    namespaceURI: null,
31467    prefix: null,
31468    localName: null,
31469    // Modified in DOM Level 2:
31470    insertBefore: function (newChild, refChild) {
31471      //raises
31472      return _insertBefore(this, newChild, refChild);
31473    },
31474    replaceChild: function (newChild, oldChild) {
31475      //raises
31476      _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
31477      if (oldChild) {
31478        this.removeChild(oldChild);
31479      }
31480    },
31481    removeChild: function (oldChild) {
31482      return _removeChild(this, oldChild);
31483    },
31484    appendChild: function (newChild) {
31485      return this.insertBefore(newChild, null);
31486    },
31487    hasChildNodes: function () {
31488      return this.firstChild != null;
31489    },
31490    cloneNode: function (deep) {
31491      return cloneNode(this.ownerDocument || this, this, deep);
31492    },
31493    // Modified in DOM Level 2:
31494    normalize: function () {
31495      var child = this.firstChild;
31496      while (child) {
31497        var next = child.nextSibling;
31498        if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) {
31499          this.removeChild(next);
31500          child.appendData(next.data);
31501        } else {
31502          child.normalize();
31503          child = next;
31504        }
31505      }
31506    },
31507    // Introduced in DOM Level 2:
31508    isSupported: function (feature, version) {
31509      return this.ownerDocument.implementation.hasFeature(feature, version);
31510    },
31511    // Introduced in DOM Level 2:
31512    hasAttributes: function () {
31513      return this.attributes.length > 0;
31514    },
31515    /**
31516     * Look up the prefix associated to the given namespace URI, starting from this node.
31517     * **The default namespace declarations are ignored by this method.**
31518     * See Namespace Prefix Lookup for details on the algorithm used by this method.
31519     *
31520     * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._
31521     *
31522     * @param {string | null} namespaceURI
31523     * @returns {string | null}
31524     * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix
31525     * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
31526     * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix
31527     * @see https://github.com/xmldom/xmldom/issues/322
31528     */
31529    lookupPrefix: function (namespaceURI) {
31530      var el = this;
31531      while (el) {
31532        var map = el._nsMap;
31533        //console.dir(map)
31534        if (map) {
31535          for (var n in map) {
31536            if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) {
31537              return n;
31538            }
31539          }
31540        }
31541        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
31542      }
31543      return null;
31544    },
31545    // Introduced in DOM Level 3:
31546    lookupNamespaceURI: function (prefix) {
31547      var el = this;
31548      while (el) {
31549        var map = el._nsMap;
31550        //console.dir(map)
31551        if (map) {
31552          if (Object.prototype.hasOwnProperty.call(map, prefix)) {
31553            return map[prefix];
31554          }
31555        }
31556        el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
31557      }
31558      return null;
31559    },
31560    // Introduced in DOM Level 3:
31561    isDefaultNamespace: function (namespaceURI) {
31562      var prefix = this.lookupPrefix(namespaceURI);
31563      return prefix == null;
31564    }
31565  };
31566  function _xmlEncoder(c) {
31567    return c == '<' && '&lt;' || c == '>' && '&gt;' || c == '&' && '&amp;' || c == '"' && '&quot;
vendor: 4,793 bytes, lines 31567-31729
31567' || '&#' + c.charCodeAt() + ';';
31568  }
31569  copy(NodeType, Node);
31570  copy(NodeType, Node.prototype);
31571
31572  /**
31573   * @param callback return true for continue,false for break
31574   * @return boolean true: break visit;
31575   */
31576  function _visitNode(node, callback) {
31577    if (callback(node)) {
31578      return true;
31579    }
31580    if (node = node.firstChild) {
31581      do {
31582        if (_visitNode(node, callback)) {
31583          return true;
31584        }
31585      } while (node = node.nextSibling);
31586    }
31587  }
31588  function Document() {
31589    this.ownerDocument = this;
31590  }
31591  function _onAddAttribute(doc, el, newAttr) {
31592    doc && doc._inc++;
31593    var ns = newAttr.namespaceURI;
31594    if (ns === NAMESPACE$2.XMLNS) {
31595      //update namespace
31596      el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value;
31597    }
31598  }
31599  function _onRemoveAttribute(doc, el, newAttr, remove) {
31600    doc && doc._inc++;
31601    var ns = newAttr.namespaceURI;
31602    if (ns === NAMESPACE$2.XMLNS) {
31603      //update namespace
31604      delete el._nsMap[newAttr.prefix ? newAttr.localName : ''];
31605    }
31606  }
31607
31608  /**
31609   * Updates `el.childNodes`, updating the indexed items and it's `length`.
31610   * Passing `newChild` means it will be appended.
31611   * Otherwise it's assumed that an item has been removed,
31612   * and `el.firstNode` and it's `.nextSibling` are used
31613   * to walk the current list of child nodes.
31614   *
31615   * @param {Document} doc
31616   * @param {Node} el
31617   * @param {Node} [newChild]
31618   * @private
31619   */
31620  function _onUpdateChild(doc, el, newChild) {
31621    if (doc && doc._inc) {
31622      doc._inc++;
31623      //update childNodes
31624      var cs = el.childNodes;
31625      if (newChild) {
31626        cs[cs.length++] = newChild;
31627      } else {
31628        var child = el.firstChild;
31629        var i = 0;
31630        while (child) {
31631          cs[i++] = child;
31632          child = child.nextSibling;
31633        }
31634        cs.length = i;
31635        delete cs[cs.length];
31636      }
31637    }
31638  }
31639
31640  /**
31641   * Removes the connections between `parentNode` and `child`
31642   * and any existing `child.previousSibling` or `child.nextSibling`.
31643   *
31644   * @see https://github.com/xmldom/xmldom/issues/135
31645   * @see https://github.com/xmldom/xmldom/issues/145
31646   *
31647   * @param {Node} parentNode
31648   * @param {Node} child
31649   * @returns {Node} the child that was removed.
31650   * @private
31651   */
31652  function _removeChild(parentNode, child) {
31653    var previous = child.previousSibling;
31654    var next = child.nextSibling;
31655    if (previous) {
31656      previous.nextSibling = next;
31657    } else {
31658      parentNode.firstChild = next;
31659    }
31660    if (next) {
31661      next.previousSibling = previous;
31662    } else {
31663      parentNode.lastChild = previous;
31664    }
31665    child.parentNode = null;
31666    child.previousSibling = null;
31667    child.nextSibling = null;
31668    _onUpdateChild(parentNode.ownerDocument, parentNode);
31669    return child;
31670  }
31671
31672  /**
31673   * Returns `true` if `node` can be a parent for insertion.
31674   * @param {Node} node
31675   * @returns {boolean}
31676   */
31677  function hasValidParentNodeType(node) {
31678    return node && (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE);
31679  }
31680
31681  /**
31682   * Returns `true` if `node` can be inserted according to it's `nodeType`.
31683   * @param {Node} node
31684   * @returns {boolean}
31685   */
31686  function hasInsertableNodeType(node) {
31687    return node && (isElementNode(node) || isTextNode(node) || isDocTypeNode(node) || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.COMMENT_NODE || node.nodeType === Node.PROCESSING_INSTRUCTION_NODE);
31688  }
31689
31690  /**
31691   * Returns true if `node` is a DOCTYPE node
31692   * @param {Node} node
31693   * @returns {boolean}
31694   */
31695  function isDocTypeNode(node) {
31696    return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
31697  }
31698
31699  /**
31700   * Returns true if the node is an element
31701   * @param {Node} node
31702   * @returns {boolean}
31703   */
31704  function isElementNode(node) {
31705    return node && node.nodeType === Node.ELEMENT_NODE;
31706  }
31707  /**
31708   * Returns true if `node` is a text node
31709   * @param {Node} node
31710   * @returns {boolean}
31711   */
31712  function isTextNode(node) {
31713    return node && node.nodeType === Node.TEXT_NODE;
31714  }
31715
31716  /**
31717   * Check if en element node can be inserted before `child`, or at the end if child is falsy,
31718   * according to the presence and position of a doctype node on the same level.
31719   *
31720   * @param {Document} doc The document node
31721   * @param {Node} child the node that would become the nextSibling if the element would be inserted
31722   * @returns {boolean} `true` if an element can be inserted before child
31723   * @private
31724   * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31725   */
31726  function isElementInsertionPossible(doc, child) {
31727    var parentChildNodes = doc.childNodes || [];
31728    if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
31729      return false;
vendor: 13,855 bytes, lines 31730-32054
31730    }
31731    var docTypeNode = find(parentChildNodes, isDocTypeNode);
31732    return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
31733  }
31734
31735  /**
31736   * Check if en element node can be inserted before `child`, or at the end if child is falsy,
31737   * according to the presence and position of a doctype node on the same level.
31738   *
31739   * @param {Node} doc The document node
31740   * @param {Node} child the node that would become the nextSibling if the element would be inserted
31741   * @returns {boolean} `true` if an element can be inserted before child
31742   * @private
31743   * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31744   */
31745  function isElementReplacementPossible(doc, child) {
31746    var parentChildNodes = doc.childNodes || [];
31747    function hasElementChildThatIsNotChild(node) {
31748      return isElementNode(node) && node !== child;
31749    }
31750    if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
31751      return false;
31752    }
31753    var docTypeNode = find(parentChildNodes, isDocTypeNode);
31754    return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
31755  }
31756
31757  /**
31758   * @private
31759   * Steps 1-5 of the checks before inserting and before replacing a child are the same.
31760   *
31761   * @param {Node} parent the parent node to insert `node` into
31762   * @param {Node} node the node to insert
31763   * @param {Node=} child the node that should become the `nextSibling` of `node`
31764   * @returns {Node}
31765   * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31766   * @throws DOMException if `child` is provided but is not a child of `parent`.
31767   * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31768   * @see https://dom.spec.whatwg.org/#concept-node-replace
31769   */
31770  function assertPreInsertionValidity1to5(parent, node, child) {
31771    // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
31772    if (!hasValidParentNodeType(parent)) {
31773      throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
31774    }
31775    // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
31776    // not implemented!
31777    // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
31778    if (child && child.parentNode !== parent) {
31779      throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
31780    }
31781    if (
31782    // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
31783    !hasInsertableNodeType(node) ||
31784    // 5. If either `node` is a Text node and `parent` is a document,
31785    // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
31786    // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
31787    // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
31788    isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) {
31789      throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType);
31790    }
31791  }
31792
31793  /**
31794   * @private
31795   * Step 6 of the checks before inserting and before replacing a child are different.
31796   *
31797   * @param {Document} parent the parent node to insert `node` into
31798   * @param {Node} node the node to insert
31799   * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
31800   * @returns {Node}
31801   * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31802   * @throws DOMException if `child` is provided but is not a child of `parent`.
31803   * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31804   * @see https://dom.spec.whatwg.org/#concept-node-replace
31805   */
31806  function assertPreInsertionValidityInDocument(parent, node, child) {
31807    var parentChildNodes = parent.childNodes || [];
31808    var nodeChildNodes = node.childNodes || [];
31809
31810    // DocumentFragment
31811    if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
31812      var nodeChildElements = nodeChildNodes.filter(isElementNode);
31813      // If node has more than one element child or has a Text node child.
31814      if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
31815        throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
31816      }
31817      // Otherwise, if `node` has one element child and either `parent` has an element child,
31818      // `child` is a doctype, or `child` is non-null and a doctype is following `child`.
31819      if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
31820        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
31821      }
31822    }
31823    // Element
31824    if (isElementNode(node)) {
31825      // `parent` has an element child, `child` is a doctype,
31826      // or `child` is non-null and a doctype is following `child`.
31827      if (!isElementInsertionPossible(parent, child)) {
31828        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
31829      }
31830    }
31831    // DocumentType
31832    if (isDocTypeNode(node)) {
31833      // `parent` has a doctype child,
31834      if (find(parentChildNodes, isDocTypeNode)) {
31835        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
31836      }
31837      var parentElementChild = find(parentChildNodes, isElementNode);
31838      // `child` is non-null and an element is preceding `child`,
31839      if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
31840        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
31841      }
31842      // or `child` is null and `parent` has an element child.
31843      if (!child && parentElementChild) {
31844        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
31845      }
31846    }
31847  }
31848
31849  /**
31850   * @private
31851   * Step 6 of the checks before inserting and before replacing a child are different.
31852   *
31853   * @param {Document} parent the parent node to insert `node` into
31854   * @param {Node} node the node to insert
31855   * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
31856   * @returns {Node}
31857   * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31858   * @throws DOMException if `child` is provided but is not a child of `parent`.
31859   * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31860   * @see https://dom.spec.whatwg.org/#concept-node-replace
31861   */
31862  function assertPreReplacementValidityInDocument(parent, node, child) {
31863    var parentChildNodes = parent.childNodes || [];
31864    var nodeChildNodes = node.childNodes || [];
31865
31866    // DocumentFragment
31867    if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
31868      var nodeChildElements = nodeChildNodes.filter(isElementNode);
31869      // If `node` has more than one element child or has a Text node child.
31870      if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
31871        throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
31872      }
31873      // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
31874      if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
31875        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
31876      }
31877    }
31878    // Element
31879    if (isElementNode(node)) {
31880      // `parent` has an element child that is not `child` or a doctype is following `child`.
31881      if (!isElementReplacementPossible(parent, child)) {
31882        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
31883      }
31884    }
31885    // DocumentType
31886    if (isDocTypeNode(node)) {
31887      function hasDoctypeChildThatIsNotChild(node) {
31888        return isDocTypeNode(node) && node !== child;
31889      }
31890
31891      // `parent` has a doctype child that is not `child`,
31892      if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
31893        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
31894      }
31895      var parentElementChild = find(parentChildNodes, isElementNode);
31896      // or an element is preceding `child`.
31897      if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
31898        throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
31899      }
31900    }
31901  }
31902
31903  /**
31904   * @private
31905   * @param {Node} parent the parent node to insert `node` into
31906   * @param {Node} node the node to insert
31907   * @param {Node=} child the node that should become the `nextSibling` of `node`
31908   * @returns {Node}
31909   * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31910   * @throws DOMException if `child` is provided but is not a child of `parent`.
31911   * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31912   */
31913  function _insertBefore(parent, node, child, _inDocumentAssertion) {
31914    // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
31915    assertPreInsertionValidity1to5(parent, node, child);
31916
31917    // If parent is a document, and any of the statements below, switched on the interface node implements,
31918    // are true, then throw a "HierarchyRequestError" DOMException.
31919    if (parent.nodeType === Node.DOCUMENT_NODE) {
31920      (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
31921    }
31922    var cp = node.parentNode;
31923    if (cp) {
31924      cp.removeChild(node); //remove and update
31925    }
31926
31927    if (node.nodeType === DOCUMENT_FRAGMENT_NODE) {
31928      var newFirst = node.firstChild;
31929      if (newFirst == null) {
31930        return node;
31931      }
31932      var newLast = node.lastChild;
31933    } else {
31934      newFirst = newLast = node;
31935    }
31936    var pre = child ? child.previousSibling : parent.lastChild;
31937    newFirst.previousSibling = pre;
31938    newLast.nextSibling = child;
31939    if (pre) {
31940      pre.nextSibling = newFirst;
31941    } else {
31942      parent.firstChild = newFirst;
31943    }
31944    if (child == null) {
31945      parent.lastChild = newLast;
31946    } else {
31947      child.previousSibling = newLast;
31948    }
31949    do {
31950      newFirst.parentNode = parent;
31951    } while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
31952    _onUpdateChild(parent.ownerDocument || parent, parent);
31953    //console.log(parent.lastChild.nextSibling == null)
31954    if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
31955      node.firstChild = node.lastChild = null;
31956    }
31957    return node;
31958  }
31959
31960  /**
31961   * Appends `newChild` to `parentNode`.
31962   * If `newChild` is already connected to a `parentNode` it is first removed from it.
31963   *
31964   * @see https://github.com/xmldom/xmldom/issues/135
31965   * @see https://github.com/xmldom/xmldom/issues/145
31966   * @param {Node} parentNode
31967   * @param {Node} newChild
31968   * @returns {Node}
31969   * @private
31970   */
31971  function _appendSingleChild(parentNode, newChild) {
31972    if (newChild.parentNode) {
31973      newChild.parentNode.removeChild(newChild);
31974    }
31975    newChild.parentNode = parentNode;
31976    newChild.previousSibling = parentNode.lastChild;
31977    newChild.nextSibling = null;
31978    if (newChild.previousSibling) {
31979      newChild.previousSibling.nextSibling = newChild;
31980    } else {
31981      parentNode.firstChild = newChild;
31982    }
31983    parentNode.lastChild = newChild;
31984    _onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
31985    return newChild;
31986  }
31987  Document.prototype = {
31988    //implementation : null,
31989    nodeName: '#document',
31990    nodeType: DOCUMENT_NODE,
31991    /**
31992     * The DocumentType node of the document.
31993     *
31994     * @readonly
31995     * @type DocumentType
31996     */
31997    doctype: null,
31998    documentElement: null,
31999    _inc: 1,
32000    insertBefore: function (newChild, refChild) {
32001      //raises
32002      if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
32003        var child = newChild.firstChild;
32004        while (child) {
32005          var next = child.nextSibling;
32006          this.insertBefore(child, refChild);
32007          child = next;
32008        }
32009        return newChild;
32010      }
32011      _insertBefore(this, newChild, refChild);
32012      newChild.ownerDocument = this;
32013      if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
32014        this.documentElement = newChild;
32015      }
32016      return newChild;
32017    },
32018    removeChild: function (oldChild) {
32019      if (this.documentElement == oldChild) {
32020        this.documentElement = null;
32021      }
32022      return _removeChild(this, oldChild);
32023    },
32024    replaceChild: function (newChild, oldChild) {
32025      //raises
32026      _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
32027      newChild.ownerDocument = this;
32028      if (oldChild) {
32029        this.removeChild(oldChild);
32030      }
32031      if (isElementNode(newChild)) {
32032        this.documentElement = newChild;
32033      }
32034    },
32035    // Introduced in DOM Level 2:
32036    importNode: function (importedNode, deep) {
32037      return importNode(this, importedNode, deep);
32038    },
32039    // Introduced in DOM Level 2:
32040    getElementById: function (id) {
32041      var rtv = null;
32042      _visitNode(this.documentElement, function (node) {
32043        if (node.nodeType == ELEMENT_NODE) {
32044          if (node.getAttribute('id') == id) {
32045            rtv = node;
32046            return true;
32047          }
32048        }
32049      });
32050      return rtv;
32051    },
32052    /**
32053     * The `getElementsByClassName` method of `Document` interface returns an array-like object
32054     * of all child elements which have **all** of the given class 
vendor: 8,940 bytes, lines 32054-32306
32054name(s).
32055     *
32056     * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters.
32057     *
32058     *
32059     * Warning: This is a live LiveNodeList.
32060     * Changes in the DOM will reflect in the array as the changes occur.
32061     * If an element selected by this array no longer qualifies for the selector,
32062     * it will automatically be removed. Be aware of this for iteration purposes.
32063     *
32064     * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace
32065     *
32066     * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
32067     * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
32068     */
32069    getElementsByClassName: function (classNames) {
32070      var classNamesSet = toOrderedSet(classNames);
32071      return new LiveNodeList(this, function (base) {
32072        var ls = [];
32073        if (classNamesSet.length > 0) {
32074          _visitNode(base.documentElement, function (node) {
32075            if (node !== base && node.nodeType === ELEMENT_NODE) {
32076              var nodeClassNames = node.getAttribute('class');
32077              // can be null if the attribute does not exist
32078              if (nodeClassNames) {
32079                // before splitting and iterating just compare them for the most common case
32080                var matches = classNames === nodeClassNames;
32081                if (!matches) {
32082                  var nodeClassNamesSet = toOrderedSet(nodeClassNames);
32083                  matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet));
32084                }
32085                if (matches) {
32086                  ls.push(node);
32087                }
32088              }
32089            }
32090          });
32091        }
32092        return ls;
32093      });
32094    },
32095    //document factory method:
32096    createElement: function (tagName) {
32097      var node = new Element();
32098      node.ownerDocument = this;
32099      node.nodeName = tagName;
32100      node.tagName = tagName;
32101      node.localName = tagName;
32102      node.childNodes = new NodeList();
32103      var attrs = node.attributes = new NamedNodeMap();
32104      attrs._ownerElement = node;
32105      return node;
32106    },
32107    createDocumentFragment: function () {
32108      var node = new DocumentFragment();
32109      node.ownerDocument = this;
32110      node.childNodes = new NodeList();
32111      return node;
32112    },
32113    createTextNode: function (data) {
32114      var node = new Text();
32115      node.ownerDocument = this;
32116      node.appendData(data);
32117      return node;
32118    },
32119    createComment: function (data) {
32120      var node = new Comment();
32121      node.ownerDocument = this;
32122      node.appendData(data);
32123      return node;
32124    },
32125    createCDATASection: function (data) {
32126      var node = new CDATASection();
32127      node.ownerDocument = this;
32128      node.appendData(data);
32129      return node;
32130    },
32131    createProcessingInstruction: function (target, data) {
32132      var node = new ProcessingInstruction();
32133      node.ownerDocument = this;
32134      node.tagName = node.nodeName = node.target = target;
32135      node.nodeValue = node.data = data;
32136      return node;
32137    },
32138    createAttribute: function (name) {
32139      var node = new Attr();
32140      node.ownerDocument = this;
32141      node.name = name;
32142      node.nodeName = name;
32143      node.localName = name;
32144      node.specified = true;
32145      return node;
32146    },
32147    createEntityReference: function (name) {
32148      var node = new EntityReference();
32149      node.ownerDocument = this;
32150      node.nodeName = name;
32151      return node;
32152    },
32153    // Introduced in DOM Level 2:
32154    createElementNS: function (namespaceURI, qualifiedName) {
32155      var node = new Element();
32156      var pl = qualifiedName.split(':');
32157      var attrs = node.attributes = new NamedNodeMap();
32158      node.childNodes = new NodeList();
32159      node.ownerDocument = this;
32160      node.nodeName = qualifiedName;
32161      node.tagName = qualifiedName;
32162      node.namespaceURI = namespaceURI;
32163      if (pl.length == 2) {
32164        node.prefix = pl[0];
32165        node.localName = pl[1];
32166      } else {
32167        //el.prefix = null;
32168        node.localName = qualifiedName;
32169      }
32170      attrs._ownerElement = node;
32171      return node;
32172    },
32173    // Introduced in DOM Level 2:
32174    createAttributeNS: function (namespaceURI, qualifiedName) {
32175      var node = new Attr();
32176      var pl = qualifiedName.split(':');
32177      node.ownerDocument = this;
32178      node.nodeName = qualifiedName;
32179      node.name = qualifiedName;
32180      node.namespaceURI = namespaceURI;
32181      node.specified = true;
32182      if (pl.length == 2) {
32183        node.prefix = pl[0];
32184        node.localName = pl[1];
32185      } else {
32186        //el.prefix = null;
32187        node.localName = qualifiedName;
32188      }
32189      return node;
32190    }
32191  };
32192  _extends(Document, Node);
32193  function Element() {
32194    this._nsMap = {};
32195  }
32196  Element.prototype = {
32197    nodeType: ELEMENT_NODE,
32198    hasAttribute: function (name) {
32199      return this.getAttributeNode(name) != null;
32200    },
32201    getAttribute: function (name) {
32202      var attr = this.getAttributeNode(name);
32203      return attr && attr.value || '';
32204    },
32205    getAttributeNode: function (name) {
32206      return this.attributes.getNamedItem(name);
32207    },
32208    setAttribute: function (name, value) {
32209      var attr = this.ownerDocument.createAttribute(name);
32210      attr.value = attr.nodeValue = "" + value;
32211      this.setAttributeNode(attr);
32212    },
32213    removeAttribute: function (name) {
32214      var attr = this.getAttributeNode(name);
32215      attr && this.removeAttributeNode(attr);
32216    },
32217    //four real opeartion method
32218    appendChild: function (newChild) {
32219      if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
32220        return this.insertBefore(newChild, null);
32221      } else {
32222        return _appendSingleChild(this, newChild);
32223      }
32224    },
32225    setAttributeNode: function (newAttr) {
32226      return this.attributes.setNamedItem(newAttr);
32227    },
32228    setAttributeNodeNS: function (newAttr) {
32229      return this.attributes.setNamedItemNS(newAttr);
32230    },
32231    removeAttributeNode: function (oldAttr) {
32232      //console.log(this == oldAttr.ownerElement)
32233      return this.attributes.removeNamedItem(oldAttr.nodeName);
32234    },
32235    //get real attribute name,and remove it by removeAttributeNode
32236    removeAttributeNS: function (namespaceURI, localName) {
32237      var old = this.getAttributeNodeNS(namespaceURI, localName);
32238      old && this.removeAttributeNode(old);
32239    },
32240    hasAttributeNS: function (namespaceURI, localName) {
32241      return this.getAttributeNodeNS(namespaceURI, localName) != null;
32242    },
32243    getAttributeNS: function (namespaceURI, localName) {
32244      var attr = this.getAttributeNodeNS(namespaceURI, localName);
32245      return attr && attr.value || '';
32246    },
32247    setAttributeNS: function (namespaceURI, qualifiedName, value) {
32248      var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
32249      attr.value = attr.nodeValue = "" + value;
32250      this.setAttributeNode(attr);
32251    },
32252    getAttributeNodeNS: function (namespaceURI, localName) {
32253      return this.attributes.getNamedItemNS(namespaceURI, localName);
32254    },
32255    getElementsByTagName: function (tagName) {
32256      return new LiveNodeList(this, function (base) {
32257        var ls = [];
32258        _visitNode(base, function (node) {
32259          if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) {
32260            ls.push(node);
32261          }
32262        });
32263        return ls;
32264      });
32265    },
32266    getElementsByTagNameNS: function (namespaceURI, localName) {
32267      return new LiveNodeList(this, function (base) {
32268        var ls = [];
32269        _visitNode(base, function (node) {
32270          if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) {
32271            ls.push(node);
32272          }
32273        });
32274        return ls;
32275      });
32276    }
32277  };
32278  Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
32279  Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
32280  _extends(Element, Node);
32281  function Attr() {}
32282  Attr.prototype.nodeType = ATTRIBUTE_NODE;
32283  _extends(Attr, Node);
32284  function CharacterData() {}
32285  CharacterData.prototype = {
32286    data: '',
32287    substringData: function (offset, count) {
32288      return this.data.substring(offset, offset + count);
32289    },
32290    appendData: function (text) {
32291      text = this.data + text;
32292      this.nodeValue = this.data = text;
32293      this.length = text.length;
32294    },
32295    insertData: function (offset, text) {
32296      this.replaceData(offset, 0, text);
32297    },
32298    appendChild: function (newChild) {
32299      throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]);
32300    },
32301    deleteData: function (offset, count) {
32302      this.replaceData(offset, count, "");
32303    },
32304    replaceData: function (offset, count, text) {
32305      var start = this.data.substring(0, offset);
32306      var end = this.data.substring(offset + count);
vendor: 4,539 bytes, lines 32307-32428
32307      text = start + text + end;
32308      this.nodeValue = this.data = text;
32309      this.length = text.length;
32310    }
32311  };
32312  _extends(CharacterData, Node);
32313  function Text() {}
32314  Text.prototype = {
32315    nodeName: "#text",
32316    nodeType: TEXT_NODE,
32317    splitText: function (offset) {
32318      var text = this.data;
32319      var newText = text.substring(offset);
32320      text = text.substring(0, offset);
32321      this.data = this.nodeValue = text;
32322      this.length = text.length;
32323      var newNode = this.ownerDocument.createTextNode(newText);
32324      if (this.parentNode) {
32325        this.parentNode.insertBefore(newNode, this.nextSibling);
32326      }
32327      return newNode;
32328    }
32329  };
32330  _extends(Text, CharacterData);
32331  function Comment() {}
32332  Comment.prototype = {
32333    nodeName: "#comment",
32334    nodeType: COMMENT_NODE
32335  };
32336  _extends(Comment, CharacterData);
32337  function CDATASection() {}
32338  CDATASection.prototype = {
32339    nodeName: "#cdata-section",
32340    nodeType: CDATA_SECTION_NODE
32341  };
32342  _extends(CDATASection, CharacterData);
32343  function DocumentType() {}
32344  DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
32345  _extends(DocumentType, Node);
32346  function Notation() {}
32347  Notation.prototype.nodeType = NOTATION_NODE;
32348  _extends(Notation, Node);
32349  function Entity() {}
32350  Entity.prototype.nodeType = ENTITY_NODE;
32351  _extends(Entity, Node);
32352  function EntityReference() {}
32353  EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
32354  _extends(EntityReference, Node);
32355  function DocumentFragment() {}
32356  DocumentFragment.prototype.nodeName = "#document-fragment";
32357  DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
32358  _extends(DocumentFragment, Node);
32359  function ProcessingInstruction() {}
32360  ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
32361  _extends(ProcessingInstruction, Node);
32362  function XMLSerializer() {}
32363  XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) {
32364    return nodeSerializeToString.call(node, isHtml, nodeFilter);
32365  };
32366  Node.prototype.toString = nodeSerializeToString;
32367  function nodeSerializeToString(isHtml, nodeFilter) {
32368    var buf = [];
32369    var refNode = this.nodeType == 9 && this.documentElement || this;
32370    var prefix = refNode.prefix;
32371    var uri = refNode.namespaceURI;
32372    if (uri && prefix == null) {
32373      //console.log(prefix)
32374      var prefix = refNode.lookupPrefix(uri);
32375      if (prefix == null) {
32376        //isHTML = true;
32377        var visibleNamespaces = [{
32378          namespace: uri,
32379          prefix: null
32380        }
32381        //{namespace:uri,prefix:''}
32382        ];
32383      }
32384    }
32385
32386    serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces);
32387    //console.log('###',this.nodeType,uri,prefix,buf.join(''))
32388    return buf.join('');
32389  }
32390  function needNamespaceDefine(node, isHTML, visibleNamespaces) {
32391    var prefix = node.prefix || '';
32392    var uri = node.namespaceURI;
32393    // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) ,
32394    // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl :
32395    // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty.
32396    // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using)
32397    // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared :
32398    // > [...] Furthermore, the attribute value [...] must not be an empty string.
32399    // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document.
32400    if (!uri) {
32401      return false;
32402    }
32403    if (prefix === "xml" && uri === NAMESPACE$2.XML || uri === NAMESPACE$2.XMLNS) {
32404      return false;
32405    }
32406    var i = visibleNamespaces.length;
32407    while (i--) {
32408      var ns = visibleNamespaces[i];
32409      // get namespace prefix
32410      if (ns.prefix === prefix) {
32411        return ns.namespace !== uri;
32412      }
32413    }
32414    return true;
32415  }
32416  /**
32417   * Well-formed constraint: No < in Attribute Values
32418   * > The replacement text of any entity referred to directly or indirectly
32419   * > in an attribute value must not contain a <.
32420   * @see https://www.w3.org/TR/xml11/#CleanAttrVals
32421   * @see https://www.w3.org/TR/xml11/#NT-AttValue
32422   *
32423   * Literal whitespace other than space that appear in attribute values
32424   * are serialized as their entity references, so they will be preserved.
32425   * (In contrast to whitespace literals in the input which are normalized to spaces)
32426   * @see https://www.w3.org/TR/xml11/#AVNormalize
32427   * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
32428   */
vendor: 5,249 bytes, lines 32429-32568
32429  function addSerializedAttribute(buf, qualifiedName, value) {
32430    buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"');
32431  }
32432  function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) {
32433    if (!visibleNamespaces) {
32434      visibleNamespaces = [];
32435    }
32436    if (nodeFilter) {
32437      node = nodeFilter(node);
32438      if (node) {
32439        if (typeof node == 'string') {
32440          buf.push(node);
32441          return;
32442        }
32443      } else {
32444        return;
32445      }
32446      //buf.sort.apply(attrs, attributeSorter);
32447    }
32448
32449    switch (node.nodeType) {
32450      case ELEMENT_NODE:
32451        var attrs = node.attributes;
32452        var len = attrs.length;
32453        var child = node.firstChild;
32454        var nodeName = node.tagName;
32455        isHTML = NAMESPACE$2.isHTML(node.namespaceURI) || isHTML;
32456        var prefixedNodeName = nodeName;
32457        if (!isHTML && !node.prefix && node.namespaceURI) {
32458          var defaultNS;
32459          // lookup current default ns from `xmlns` attribute
32460          for (var ai = 0; ai < attrs.length; ai++) {
32461            if (attrs.item(ai).name === 'xmlns') {
32462              defaultNS = attrs.item(ai).value;
32463              break;
32464            }
32465          }
32466          if (!defaultNS) {
32467            // lookup current default ns in visibleNamespaces
32468            for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
32469              var namespace = visibleNamespaces[nsi];
32470              if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
32471                defaultNS = namespace.namespace;
32472                break;
32473              }
32474            }
32475          }
32476          if (defaultNS !== node.namespaceURI) {
32477            for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
32478              var namespace = visibleNamespaces[nsi];
32479              if (namespace.namespace === node.namespaceURI) {
32480                if (namespace.prefix) {
32481                  prefixedNodeName = namespace.prefix + ':' + nodeName;
32482                }
32483                break;
32484              }
32485            }
32486          }
32487        }
32488        buf.push('<', prefixedNodeName);
32489        for (var i = 0; i < len; i++) {
32490          // add namespaces for attributes
32491          var attr = attrs.item(i);
32492          if (attr.prefix == 'xmlns') {
32493            visibleNamespaces.push({
32494              prefix: attr.localName,
32495              namespace: attr.value
32496            });
32497          } else if (attr.nodeName == 'xmlns') {
32498            visibleNamespaces.push({
32499              prefix: '',
32500              namespace: attr.value
32501            });
32502          }
32503        }
32504        for (var i = 0; i < len; i++) {
32505          var attr = attrs.item(i);
32506          if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) {
32507            var prefix = attr.prefix || '';
32508            var uri = attr.namespaceURI;
32509            addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
32510            visibleNamespaces.push({
32511              prefix: prefix,
32512              namespace: uri
32513            });
32514          }
32515          serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces);
32516        }
32517
32518        // add namespace for current node
32519        if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
32520          var prefix = node.prefix || '';
32521          var uri = node.namespaceURI;
32522          addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
32523          visibleNamespaces.push({
32524            prefix: prefix,
32525            namespace: uri
32526          });
32527        }
32528        if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
32529          buf.push('>');
32530          //if is cdata child node
32531          if (isHTML && /^script$/i.test(nodeName)) {
32532            while (child) {
32533              if (child.data) {
32534                buf.push(child.data);
32535              } else {
32536                serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32537              }
32538              child = child.nextSibling;
32539            }
32540          } else {
32541            while (child) {
32542              serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32543              child = child.nextSibling;
32544            }
32545          }
32546          buf.push('</', prefixedNodeName, '>');
32547        } else {
32548          buf.push('/>');
32549        }
32550        // remove added visible namespaces
32551        //visibleNamespaces.length = startVisibleNamespaces;
32552        return;
32553      case DOCUMENT_NODE:
32554      case DOCUMENT_FRAGMENT_NODE:
32555        var child = node.firstChild;
32556        while (child) {
32557          serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32558          child = child.nextSibling;
32559        }
32560        return;
32561      case ATTRIBUTE_NODE:
32562        return addSerializedAttribute(buf, node.name, node.value);
32563      case TEXT_NODE:
32564        /**
32565         * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
32566         * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
32567         * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
32568         * `&amp;` and `&lt;` respectively.
vendor: 6,368 bytes, lines 32569-32778
32569         * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
32570         * be escaped using either `&gt;` or a character reference when it appears in the string `]]>` in content,
32571         * when that string is not marking the end of a CDATA section.
32572         *
32573         * In the content of elements, character data is any string of characters
32574         * which does not contain the start-delimiter of any markup
32575         * and does not include the CDATA-section-close delimiter, `]]>`.
32576         *
32577         * @see https://www.w3.org/TR/xml/#NT-CharData
32578         * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
32579         */
32580        return buf.push(node.data.replace(/[<&>]/g, _xmlEncoder));
32581      case CDATA_SECTION_NODE:
32582        return buf.push('<![CDATA[', node.data, ']]>');
32583      case COMMENT_NODE:
32584        return buf.push("<!--", node.data, "-->");
32585      case DOCUMENT_TYPE_NODE:
32586        var pubid = node.publicId;
32587        var sysid = node.systemId;
32588        buf.push('<!DOCTYPE ', node.name);
32589        if (pubid) {
32590          buf.push(' PUBLIC ', pubid);
32591          if (sysid && sysid != '.') {
32592            buf.push(' ', sysid);
32593          }
32594          buf.push('>');
32595        } else if (sysid && sysid != '.') {
32596          buf.push(' SYSTEM ', sysid, '>');
32597        } else {
32598          var sub = node.internalSubset;
32599          if (sub) {
32600            buf.push(" [", sub, "]");
32601          }
32602          buf.push(">");
32603        }
32604        return;
32605      case PROCESSING_INSTRUCTION_NODE:
32606        return buf.push("<?", node.target, " ", node.data, "?>");
32607      case ENTITY_REFERENCE_NODE:
32608        return buf.push('&', node.nodeName, ';');
32609      //case ENTITY_NODE:
32610      //case NOTATION_NODE:
32611      default:
32612        buf.push('??', node.nodeName);
32613    }
32614  }
32615  function importNode(doc, node, deep) {
32616    var node2;
32617    switch (node.nodeType) {
32618      case ELEMENT_NODE:
32619        node2 = node.cloneNode(false);
32620        node2.ownerDocument = doc;
32621      //var attrs = node2.attributes;
32622      //var len = attrs.length;
32623      //for(var i=0;i<len;i++){
32624      //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
32625      //}
32626      case DOCUMENT_FRAGMENT_NODE:
32627        break;
32628      case ATTRIBUTE_NODE:
32629        deep = true;
32630        break;
32631      //case ENTITY_REFERENCE_NODE:
32632      //case PROCESSING_INSTRUCTION_NODE:
32633      ////case TEXT_NODE:
32634      //case CDATA_SECTION_NODE:
32635      //case COMMENT_NODE:
32636      //	deep = false;
32637      //	break;
32638      //case DOCUMENT_NODE:
32639      //case DOCUMENT_TYPE_NODE:
32640      //cannot be imported.
32641      //case ENTITY_NODE:
32642      //case NOTATION_NODE:
32643      //can not hit in level3
32644      //default:throw e;
32645    }
32646
32647    if (!node2) {
32648      node2 = node.cloneNode(false); //false
32649    }
32650
32651    node2.ownerDocument = doc;
32652    node2.parentNode = null;
32653    if (deep) {
32654      var child = node.firstChild;
32655      while (child) {
32656        node2.appendChild(importNode(doc, child, deep));
32657        child = child.nextSibling;
32658      }
32659    }
32660    return node2;
32661  }
32662  //
32663  //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
32664  //					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
32665  function cloneNode(doc, node, deep) {
32666    var node2 = new node.constructor();
32667    for (var n in node) {
32668      if (Object.prototype.hasOwnProperty.call(node, n)) {
32669        var v = node[n];
32670        if (typeof v != "object") {
32671          if (v != node2[n]) {
32672            node2[n] = v;
32673          }
32674        }
32675      }
32676    }
32677    if (node.childNodes) {
32678      node2.childNodes = new NodeList();
32679    }
32680    node2.ownerDocument = doc;
32681    switch (node2.nodeType) {
32682      case ELEMENT_NODE:
32683        var attrs = node.attributes;
32684        var attrs2 = node2.attributes = new NamedNodeMap();
32685        var len = attrs.length;
32686        attrs2._ownerElement = node2;
32687        for (var i = 0; i < len; i++) {
32688          node2.setAttributeNode(cloneNode(doc, attrs.item(i), true));
32689        }
32690        break;
32691      case ATTRIBUTE_NODE:
32692        deep = true;
32693    }
32694    if (deep) {
32695      var child = node.firstChild;
32696      while (child) {
32697        node2.appendChild(cloneNode(doc, child, deep));
32698        child = child.nextSibling;
32699      }
32700    }
32701    return node2;
32702  }
32703  function __set__(object, key, value) {
32704    object[key] = value;
32705  }
32706  //do dynamic
32707  try {
32708    if (Object.defineProperty) {
32709      Object.defineProperty(LiveNodeList.prototype, 'length', {
32710        get: function () {
32711          _updateLiveList(this);
32712          return this.$$length;
32713        }
32714      });
32715      Object.defineProperty(Node.prototype, 'textContent', {
32716        get: function () {
32717          return getTextContent(this);
32718        },
32719        set: function (data) {
32720          switch (this.nodeType) {
32721            case ELEMENT_NODE:
32722            case DOCUMENT_FRAGMENT_NODE:
32723              while (this.firstChild) {
32724                this.removeChild(this.firstChild);
32725              }
32726              if (data || String(data)) {
32727                this.appendChild(this.ownerDocument.createTextNode(data));
32728              }
32729              break;
32730            default:
32731              this.data = data;
32732              this.value = data;
32733              this.nodeValue = data;
32734          }
32735        }
32736      });
32737      function getTextContent(node) {
32738        switch (node.nodeType) {
32739          case ELEMENT_NODE:
32740          case DOCUMENT_FRAGMENT_NODE:
32741            var buf = [];
32742            node = node.firstChild;
32743            while (node) {
32744              if (node.nodeType !== 7 && node.nodeType !== 8) {
32745                buf.push(getTextContent(node));
32746              }
32747              node = node.nextSibling;
32748            }
32749            return buf.join('');
32750          default:
32751            return node.nodeValue;
32752        }
32753      }
32754      __set__ = function (object, key, value) {
32755        //console.log(value)
32756        object['$$' + key] = value;
32757      };
32758    }
32759  } catch (e) {//ie8
32760  }
32761
32762  //if(typeof require == 'function'){
32763  var DocumentType_1 = DocumentType;
32764  var DOMException_1 = DOMException;
32765  var DOMImplementation_1 = DOMImplementation$1;
32766  var Element_1 = Element;
32767  var Node_1 = Node;
32768  var NodeList_1 = NodeList;
32769  var XMLSerializer_1 = XMLSerializer;
32770  //}
32771
32772  var dom = {
32773    DocumentType: DocumentType_1,
32774    DOMException: DOMException_1,
32775    DOMImplementation: DOMImplementation_1,
32776    Element: Element_1,
32777    Node: Node_1,
32778    NodeList: NodeList
vendor: 55,445 bytes, lines 32778-34953
32778_1,
32779    XMLSerializer: XMLSerializer_1
32780  };
32781
32782  var entities = createCommonjsModule(function (module, exports) {
32783
32784    var freeze = conventions.freeze;
32785
32786    /**
32787     * The entities that are predefined in every XML document.
32788     *
32789     * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1
32790     * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0
32791     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia
32792     */
32793    exports.XML_ENTITIES = freeze({
32794      amp: '&',
32795      apos: "'",
32796      gt: '>',
32797      lt: '<',
32798      quot: '"'
32799    });
32800
32801    /**
32802     * A map of all entities that are detected in an HTML document.
32803     * They contain all entries from `XML_ENTITIES`.
32804     *
32805     * @see XML_ENTITIES
32806     * @see DOMParser.parseFromString
32807     * @see DOMImplementation.prototype.createHTMLDocument
32808     * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec
32809     * @see https://html.spec.whatwg.org/entities.json JSON
32810     * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names
32811     * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML
32812     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML)
32813     * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML)
32814     */
32815    exports.HTML_ENTITIES = freeze({
32816      Aacute: '\u00C1',
32817      aacute: '\u00E1',
32818      Abreve: '\u0102',
32819      abreve: '\u0103',
32820      ac: '\u223E',
32821      acd: '\u223F',
32822      acE: '\u223E\u0333',
32823      Acirc: '\u00C2',
32824      acirc: '\u00E2',
32825      acute: '\u00B4',
32826      Acy: '\u0410',
32827      acy: '\u0430',
32828      AElig: '\u00C6',
32829      aelig: '\u00E6',
32830      af: '\u2061',
32831      Afr: '\uD835\uDD04',
32832      afr: '\uD835\uDD1E',
32833      Agrave: '\u00C0',
32834      agrave: '\u00E0',
32835      alefsym: '\u2135',
32836      aleph: '\u2135',
32837      Alpha: '\u0391',
32838      alpha: '\u03B1',
32839      Amacr: '\u0100',
32840      amacr: '\u0101',
32841      amalg: '\u2A3F',
32842      AMP: '\u0026',
32843      amp: '\u0026',
32844      And: '\u2A53',
32845      and: '\u2227',
32846      andand: '\u2A55',
32847      andd: '\u2A5C',
32848      andslope: '\u2A58',
32849      andv: '\u2A5A',
32850      ang: '\u2220',
32851      ange: '\u29A4',
32852      angle: '\u2220',
32853      angmsd: '\u2221',
32854      angmsdaa: '\u29A8',
32855      angmsdab: '\u29A9',
32856      angmsdac: '\u29AA',
32857      angmsdad: '\u29AB',
32858      angmsdae: '\u29AC',
32859      angmsdaf: '\u29AD',
32860      angmsdag: '\u29AE',
32861      angmsdah: '\u29AF',
32862      angrt: '\u221F',
32863      angrtvb: '\u22BE',
32864      angrtvbd: '\u299D',
32865      angsph: '\u2222',
32866      angst: '\u00C5',
32867      angzarr: '\u237C',
32868      Aogon: '\u0104',
32869      aogon: '\u0105',
32870      Aopf: '\uD835\uDD38',
32871      aopf: '\uD835\uDD52',
32872      ap: '\u2248',
32873      apacir: '\u2A6F',
32874      apE: '\u2A70',
32875      ape: '\u224A',
32876      apid: '\u224B',
32877      apos: '\u0027',
32878      ApplyFunction: '\u2061',
32879      approx: '\u2248',
32880      approxeq: '\u224A',
32881      Aring: '\u00C5',
32882      aring: '\u00E5',
32883      Ascr: '\uD835\uDC9C',
32884      ascr: '\uD835\uDCB6',
32885      Assign: '\u2254',
32886      ast: '\u002A',
32887      asymp: '\u2248',
32888      asympeq: '\u224D',
32889      Atilde: '\u00C3',
32890      atilde: '\u00E3',
32891      Auml: '\u00C4',
32892      auml: '\u00E4',
32893      awconint: '\u2233',
32894      awint: '\u2A11',
32895      backcong: '\u224C',
32896      backepsilon: '\u03F6',
32897      backprime: '\u2035',
32898      backsim: '\u223D',
32899      backsimeq: '\u22CD',
32900      Backslash: '\u2216',
32901      Barv: '\u2AE7',
32902      barvee: '\u22BD',
32903      Barwed: '\u2306',
32904      barwed: '\u2305',
32905      barwedge: '\u2305',
32906      bbrk: '\u23B5',
32907      bbrktbrk: '\u23B6',
32908      bcong: '\u224C',
32909      Bcy: '\u0411',
32910      bcy: '\u0431',
32911      bdquo: '\u201E',
32912      becaus: '\u2235',
32913      Because: '\u2235',
32914      because: '\u2235',
32915      bemptyv: '\u29B0',
32916      bepsi: '\u03F6',
32917      bernou: '\u212C',
32918      Bernoullis: '\u212C',
32919      Beta: '\u0392',
32920      beta: '\u03B2',
32921      beth: '\u2136',
32922      between: '\u226C',
32923      Bfr: '\uD835\uDD05',
32924      bfr: '\uD835\uDD1F',
32925      bigcap: '\u22C2',
32926      bigcirc: '\u25EF',
32927      bigcup: '\u22C3',
32928      bigodot: '\u2A00',
32929      bigoplus: '\u2A01',
32930      bigotimes: '\u2A02',
32931      bigsqcup: '\u2A06',
32932      bigstar: '\u2605',
32933      bigtriangledown: '\u25BD',
32934      bigtriangleup: '\u25B3',
32935      biguplus: '\u2A04',
32936      bigvee: '\u22C1',
32937      bigwedge: '\u22C0',
32938      bkarow: '\u290D',
32939      blacklozenge: '\u29EB',
32940      blacksquare: '\u25AA',
32941      blacktriangle: '\u25B4',
32942      blacktriangledown: '\u25BE',
32943      blacktriangleleft: '\u25C2',
32944      blacktriangleright: '\u25B8',
32945      blank: '\u2423',
32946      blk12: '\u2592',
32947      blk14: '\u2591',
32948      blk34: '\u2593',
32949      block: '\u2588',
32950      bne: '\u003D\u20E5',
32951      bnequiv: '\u2261\u20E5',
32952      bNot: '\u2AED',
32953      bnot: '\u2310',
32954      Bopf: '\uD835\uDD39',
32955      bopf: '\uD835\uDD53',
32956      bot: '\u22A5',
32957      bottom: '\u22A5',
32958      bowtie: '\u22C8',
32959      boxbox: '\u29C9',
32960      boxDL: '\u2557',
32961      boxDl: '\u2556',
32962      boxdL: '\u2555',
32963      boxdl: '\u2510',
32964      boxDR: '\u2554',
32965      boxDr: '\u2553',
32966      boxdR: '\u2552',
32967      boxdr: '\u250C',
32968      boxH: '\u2550',
32969      boxh: '\u2500',
32970      boxHD: '\u2566',
32971      boxHd: '\u2564',
32972      boxhD: '\u2565',
32973      boxhd: '\u252C',
32974      boxHU: '\u2569',
32975      boxHu: '\u2567',
32976      boxhU: '\u2568',
32977      boxhu: '\u2534',
32978      boxminus: '\u229F',
32979      boxplus: '\u229E',
32980      boxtimes: '\u22A0',
32981      boxUL: '\u255D',
32982      boxUl: '\u255C',
32983      boxuL: '\u255B',
32984      boxul: '\u2518',
32985      boxUR: '\u255A',
32986      boxUr: '\u2559',
32987      boxuR: '\u2558',
32988      boxur: '\u2514',
32989      boxV: '\u2551',
32990      boxv: '\u2502',
32991      boxVH: '\u256C',
32992      boxVh: '\u256B',
32993      boxvH: '\u256A',
32994      boxvh: '\u253C',
32995      boxVL: '\u2563',
32996      boxVl: '\u2562',
32997      boxvL: '\u2561',
32998      boxvl: '\u2524',
32999      boxVR: '\u2560',
33000      boxVr: '\u255F',
33001      boxvR: '\u255E',
33002      boxvr: '\u251C',
33003      bprime: '\u2035',
33004      Breve: '\u02D8',
33005      breve: '\u02D8',
33006      brvbar: '\u00A6',
33007      Bscr: '\u212C',
33008      bscr: '\uD835\uDCB7',
33009      bsemi: '\u204F',
33010      bsim: '\u223D',
33011      bsime: '\u22CD',
33012      bsol: '\u005C',
33013      bsolb: '\u29C5',
33014      bsolhsub: '\u27C8',
33015      bull: '\u2022',
33016      bullet: '\u2022',
33017      bump: '\u224E',
33018      bumpE: '\u2AAE',
33019      bumpe: '\u224F',
33020      Bumpeq: '\u224E',
33021      bumpeq: '\u224F',
33022      Cacute: '\u0106',
33023      cacute: '\u0107',
33024      Cap: '\u22D2',
33025      cap: '\u2229',
33026      capand: '\u2A44',
33027      capbrcup: '\u2A49',
33028      capcap: '\u2A4B',
33029      capcup: '\u2A47',
33030      capdot: '\u2A40',
33031      CapitalDifferentialD: '\u2145',
33032      caps: '\u2229\uFE00',
33033      caret: '\u2041',
33034      caron: '\u02C7',
33035      Cayleys: '\u212D',
33036      ccaps: '\u2A4D',
33037      Ccaron: '\u010C',
33038      ccaron: '\u010D',
33039      Ccedil: '\u00C7',
33040      ccedil: '\u00E7',
33041      Ccirc: '\u0108',
33042      ccirc: '\u0109',
33043      Cconint: '\u2230',
33044      ccups: '\u2A4C',
33045      ccupssm: '\u2A50',
33046      Cdot: '\u010A',
33047      cdot: '\u010B',
33048      cedil: '\u00B8',
33049      Cedilla: '\u00B8',
33050      cemptyv: '\u29B2',
33051      cent: '\u00A2',
33052      CenterDot: '\u00B7',
33053      centerdot: '\u00B7',
33054      Cfr: '\u212D',
33055      cfr: '\uD835\uDD20',
33056      CHcy: '\u0427',
33057      chcy: '\u0447',
33058      check: '\u2713',
33059      checkmark: '\u2713',
33060      Chi: '\u03A7',
33061      chi: '\u03C7',
33062      cir: '\u25CB',
33063      circ: '\u02C6',
33064      circeq: '\u2257',
33065      circlearrowleft: '\u21BA',
33066      circlearrowright: '\u21BB',
33067      circledast: '\u229B',
33068      circledcirc: '\u229A',
33069      circleddash: '\u229D',
33070      CircleDot: '\u2299',
33071      circledR: '\u00AE',
33072      circledS: '\u24C8',
33073      CircleMinus: '\u2296',
33074      CirclePlus: '\u2295',
33075      CircleTimes: '\u2297',
33076      cirE: '\u29C3',
33077      cire: '\u2257',
33078      cirfnint: '\u2A10',
33079      cirmid: '\u2AEF',
33080      cirscir: '\u29C2',
33081      ClockwiseContourIntegral: '\u2232',
33082      CloseCurlyDoubleQuote: '\u201D',
33083      CloseCurlyQuote: '\u2019',
33084      clubs: '\u2663',
33085      clubsuit: '\u2663',
33086      Colon: '\u2237',
33087      colon: '\u003A',
33088      Colone: '\u2A74',
33089      colone: '\u2254',
33090      coloneq: '\u2254',
33091      comma: '\u002C',
33092      commat: '\u0040',
33093      comp: '\u2201',
33094      compfn: '\u2218',
33095      complement: '\u2201',
33096      complexes: '\u2102',
33097      cong: '\u2245',
33098      congdot: '\u2A6D',
33099      Congruent: '\u2261',
33100      Conint: '\u222F',
33101      conint: '\u222E',
33102      ContourIntegral: '\u222E',
33103      Copf: '\u2102',
33104      copf: '\uD835\uDD54',
33105      coprod: '\u2210',
33106      Coproduct: '\u2210',
33107      COPY: '\u00A9',
33108      copy: '\u00A9',
33109      copysr: '\u2117',
33110      CounterClockwiseContourIntegral: '\u2233',
33111      crarr: '\u21B5',
33112      Cross: '\u2A2F',
33113      cross: '\u2717',
33114      Cscr: '\uD835\uDC9E',
33115      cscr: '\uD835\uDCB8',
33116      csub: '\u2ACF',
33117      csube: '\u2AD1',
33118      csup: '\u2AD0',
33119      csupe: '\u2AD2',
33120      ctdot: '\u22EF',
33121      cudarrl: '\u2938',
33122      cudarrr: '\u2935',
33123      cuepr: '\u22DE',
33124      cuesc: '\u22DF',
33125      cularr: '\u21B6',
33126      cularrp: '\u293D',
33127      Cup: '\u22D3',
33128      cup: '\u222A',
33129      cupbrcap: '\u2A48',
33130      CupCap: '\u224D',
33131      cupcap: '\u2A46',
33132      cupcup: '\u2A4A',
33133      cupdot: '\u228D',
33134      cupor: '\u2A45',
33135      cups: '\u222A\uFE00',
33136      curarr: '\u21B7',
33137      curarrm: '\u293C',
33138      curlyeqprec: '\u22DE',
33139      curlyeqsucc: '\u22DF',
33140      curlyvee: '\u22CE',
33141      curlywedge: '\u22CF',
33142      curren: '\u00A4',
33143      curvearrowleft: '\u21B6',
33144      curvearrowright: '\u21B7',
33145      cuvee: '\u22CE',
33146      cuwed: '\u22CF',
33147      cwconint: '\u2232',
33148      cwint: '\u2231',
33149      cylcty: '\u232D',
33150      Dagger: '\u2021',
33151      dagger: '\u2020',
33152      daleth: '\u2138',
33153      Darr: '\u21A1',
33154      dArr: '\u21D3',
33155      darr: '\u2193',
33156      dash: '\u2010',
33157      Dashv: '\u2AE4',
33158      dashv: '\u22A3',
33159      dbkarow: '\u290F',
33160      dblac: '\u02DD',
33161      Dcaron: '\u010E',
33162      dcaron: '\u010F',
33163      Dcy: '\u0414',
33164      dcy: '\u0434',
33165      DD: '\u2145',
33166      dd: '\u2146',
33167      ddagger: '\u2021',
33168      ddarr: '\u21CA',
33169      DDotrahd: '\u2911',
33170      ddotseq: '\u2A77',
33171      deg: '\u00B0',
33172      Del: '\u2207',
33173      Delta: '\u0394',
33174      delta: '\u03B4',
33175      demptyv: '\u29B1',
33176      dfisht: '\u297F',
33177      Dfr: '\uD835\uDD07',
33178      dfr: '\uD835\uDD21',
33179      dHar: '\u2965',
33180      dharl: '\u21C3',
33181      dharr: '\u21C2',
33182      DiacriticalAcute: '\u00B4',
33183      DiacriticalDot: '\u02D9',
33184      DiacriticalDoubleAcute: '\u02DD',
33185      DiacriticalGrave: '\u0060',
33186      DiacriticalTilde: '\u02DC',
33187      diam: '\u22C4',
33188      Diamond: '\u22C4',
33189      diamond: '\u22C4',
33190      diamondsuit: '\u2666',
33191      diams: '\u2666',
33192      die: '\u00A8',
33193      DifferentialD: '\u2146',
33194      digamma: '\u03DD',
33195      disin: '\u22F2',
33196      div: '\u00F7',
33197      divide: '\u00F7',
33198      divideontimes: '\u22C7',
33199      divonx: '\u22C7',
33200      DJcy: '\u0402',
33201      djcy: '\u0452',
33202      dlcorn: '\u231E',
33203      dlcrop: '\u230D',
33204      dollar: '\u0024',
33205      Dopf: '\uD835\uDD3B',
33206      dopf: '\uD835\uDD55',
33207      Dot: '\u00A8',
33208      dot: '\u02D9',
33209      DotDot: '\u20DC',
33210      doteq: '\u2250',
33211      doteqdot: '\u2251',
33212      DotEqual: '\u2250',
33213      dotminus: '\u2238',
33214      dotplus: '\u2214',
33215      dotsquare: '\u22A1',
33216      doublebarwedge: '\u2306',
33217      DoubleContourIntegral: '\u222F',
33218      DoubleDot: '\u00A8',
33219      DoubleDownArrow: '\u21D3',
33220      DoubleLeftArrow: '\u21D0',
33221      DoubleLeftRightArrow: '\u21D4',
33222      DoubleLeftTee: '\u2AE4',
33223      DoubleLongLeftArrow: '\u27F8',
33224      DoubleLongLeftRightArrow: '\u27FA',
33225      DoubleLongRightArrow: '\u27F9',
33226      DoubleRightArrow: '\u21D2',
33227      DoubleRightTee: '\u22A8',
33228      DoubleUpArrow: '\u21D1',
33229      DoubleUpDownArrow: '\u21D5',
33230      DoubleVerticalBar: '\u2225',
33231      DownArrow: '\u2193',
33232      Downarrow: '\u21D3',
33233      downarrow: '\u2193',
33234      DownArrowBar: '\u2913',
33235      DownArrowUpArrow: '\u21F5',
33236      DownBreve: '\u0311',
33237      downdownarrows: '\u21CA',
33238      downharpoonleft: '\u21C3',
33239      downharpoonright: '\u21C2',
33240      DownLeftRightVector: '\u2950',
33241      DownLeftTeeVector: '\u295E',
33242      DownLeftVector: '\u21BD',
33243      DownLeftVectorBar: '\u2956',
33244      DownRightTeeVector: '\u295F',
33245      DownRightVector: '\u21C1',
33246      DownRightVectorBar: '\u2957',
33247      DownTee: '\u22A4',
33248      DownTeeArrow: '\u21A7',
33249      drbkarow: '\u2910',
33250      drcorn: '\u231F',
33251      drcrop: '\u230C',
33252      Dscr: '\uD835\uDC9F',
33253      dscr: '\uD835\uDCB9',
33254      DScy: '\u0405',
33255      dscy: '\u0455',
33256      dsol: '\u29F6',
33257      Dstrok: '\u0110',
33258      dstrok: '\u0111',
33259      dtdot: '\u22F1',
33260      dtri: '\u25BF',
33261      dtrif: '\u25BE',
33262      duarr: '\u21F5',
33263      duhar: '\u296F',
33264      dwangle: '\u29A6',
33265      DZcy: '\u040F',
33266      dzcy: '\u045F',
33267      dzigrarr: '\u27FF',
33268      Eacute: '\u00C9',
33269      eacute: '\u00E9',
33270      easter: '\u2A6E',
33271      Ecaron: '\u011A',
33272      ecaron: '\u011B',
33273      ecir: '\u2256',
33274      Ecirc: '\u00CA',
33275      ecirc: '\u00EA',
33276      ecolon: '\u2255',
33277      Ecy: '\u042D',
33278      ecy: '\u044D',
33279      eDDot: '\u2A77',
33280      Edot: '\u0116',
33281      eDot: '\u2251',
33282      edot: '\u0117',
33283      ee: '\u2147',
33284      efDot: '\u2252',
33285      Efr: '\uD835\uDD08',
33286      efr: '\uD835\uDD22',
33287      eg: '\u2A9A',
33288      Egrave: '\u00C8',
33289      egrave: '\u00E8',
33290      egs: '\u2A96',
33291      egsdot: '\u2A98',
33292      el: '\u2A99',
33293      Element: '\u2208',
33294      elinters: '\u23E7',
33295      ell: '\u2113',
33296      els: '\u2A95',
33297      elsdot: '\u2A97',
33298      Emacr: '\u0112',
33299      emacr: '\u0113',
33300      empty: '\u2205',
33301      emptyset: '\u2205',
33302      EmptySmallSquare: '\u25FB',
33303      emptyv: '\u2205',
33304      EmptyVerySmallSquare: '\u25AB',
33305      emsp: '\u2003',
33306      emsp13: '\u2004',
33307      emsp14: '\u2005',
33308      ENG: '\u014A',
33309      eng: '\u014B',
33310      ensp: '\u2002',
33311      Eogon: '\u0118',
33312      eogon: '\u0119',
33313      Eopf: '\uD835\uDD3C',
33314      eopf: '\uD835\uDD56',
33315      epar: '\u22D5',
33316      eparsl: '\u29E3',
33317      eplus: '\u2A71',
33318      epsi: '\u03B5',
33319      Epsilon: '\u0395',
33320      epsilon: '\u03B5',
33321      epsiv: '\u03F5',
33322      eqcirc: '\u2256',
33323      eqcolon: '\u2255',
33324      eqsim: '\u2242',
33325      eqslantgtr: '\u2A96',
33326      eqslantless: '\u2A95',
33327      Equal: '\u2A75',
33328      equals: '\u003D',
33329      EqualTilde: '\u2242',
33330      equest: '\u225F',
33331      Equilibrium: '\u21CC',
33332      equiv: '\u2261',
33333      equivDD: '\u2A78',
33334      eqvparsl: '\u29E5',
33335      erarr: '\u2971',
33336      erDot: '\u2253',
33337      Escr: '\u2130',
33338      escr: '\u212F',
33339      esdot: '\u2250',
33340      Esim: '\u2A73',
33341      esim: '\u2242',
33342      Eta: '\u0397',
33343      eta: '\u03B7',
33344      ETH: '\u00D0',
33345      eth: '\u00F0',
33346      Euml: '\u00CB',
33347      euml: '\u00EB',
33348      euro: '\u20AC',
33349      excl: '\u0021',
33350      exist: '\u2203',
33351      Exists: '\u2203',
33352      expectation: '\u2130',
33353      ExponentialE: '\u2147',
33354      exponentiale: '\u2147',
33355      fallingdotseq: '\u2252',
33356      Fcy: '\u0424',
33357      fcy: '\u0444',
33358      female: '\u2640',
33359      ffilig: '\uFB03',
33360      fflig: '\uFB00',
33361      ffllig: '\uFB04',
33362      Ffr: '\uD835\uDD09',
33363      ffr: '\uD835\uDD23',
33364      filig: '\uFB01',
33365      FilledSmallSquare: '\u25FC',
33366      FilledVerySmallSquare: '\u25AA',
33367      fjlig: '\u0066\u006A',
33368      flat: '\u266D',
33369      fllig: '\uFB02',
33370      fltns: '\u25B1',
33371      fnof: '\u0192',
33372      Fopf: '\uD835\uDD3D',
33373      fopf: '\uD835\uDD57',
33374      ForAll: '\u2200',
33375      forall: '\u2200',
33376      fork: '\u22D4',
33377      forkv: '\u2AD9',
33378      Fouriertrf: '\u2131',
33379      fpartint: '\u2A0D',
33380      frac12: '\u00BD',
33381      frac13: '\u2153',
33382      frac14: '\u00BC',
33383      frac15: '\u2155',
33384      frac16: '\u2159',
33385      frac18: '\u215B',
33386      frac23: '\u2154',
33387      frac25: '\u2156',
33388      frac34: '\u00BE',
33389      frac35: '\u2157',
33390      frac38: '\u215C',
33391      frac45: '\u2158',
33392      frac56: '\u215A',
33393      frac58: '\u215D',
33394      frac78: '\u215E',
33395      frasl: '\u2044',
33396      frown: '\u2322',
33397      Fscr: '\u2131',
33398      fscr: '\uD835\uDCBB',
33399      gacute: '\u01F5',
33400      Gamma: '\u0393',
33401      gamma: '\u03B3',
33402      Gammad: '\u03DC',
33403      gammad: '\u03DD',
33404      gap: '\u2A86',
33405      Gbreve: '\u011E',
33406      gbreve: '\u011F',
33407      Gcedil: '\u0122',
33408      Gcirc: '\u011C',
33409      gcirc: '\u011D',
33410      Gcy: '\u0413',
33411      gcy: '\u0433',
33412      Gdot: '\u0120',
33413      gdot: '\u0121',
33414      gE: '\u2267',
33415      ge: '\u2265',
33416      gEl: '\u2A8C',
33417      gel: '\u22DB',
33418      geq: '\u2265',
33419      geqq: '\u2267',
33420      geqslant: '\u2A7E',
33421      ges: '\u2A7E',
33422      gescc: '\u2AA9',
33423      gesdot: '\u2A80',
33424      gesdoto: '\u2A82',
33425      gesdotol: '\u2A84',
33426      gesl: '\u22DB\uFE00',
33427      gesles: '\u2A94',
33428      Gfr: '\uD835\uDD0A',
33429      gfr: '\uD835\uDD24',
33430      Gg: '\u22D9',
33431      gg: '\u226B',
33432      ggg: '\u22D9',
33433      gimel: '\u2137',
33434      GJcy: '\u0403',
33435      gjcy: '\u0453',
33436      gl: '\u2277',
33437      gla: '\u2AA5',
33438      glE: '\u2A92',
33439      glj: '\u2AA4',
33440      gnap: '\u2A8A',
33441      gnapprox: '\u2A8A',
33442      gnE: '\u2269',
33443      gne: '\u2A88',
33444      gneq: '\u2A88',
33445      gneqq: '\u2269',
33446      gnsim: '\u22E7',
33447      Gopf: '\uD835\uDD3E',
33448      gopf: '\uD835\uDD58',
33449      grave: '\u0060',
33450      GreaterEqual: '\u2265',
33451      GreaterEqualLess: '\u22DB',
33452      GreaterFullEqual: '\u2267',
33453      GreaterGreater: '\u2AA2',
33454      GreaterLess: '\u2277',
33455      GreaterSlantEqual: '\u2A7E',
33456      GreaterTilde: '\u2273',
33457      Gscr: '\uD835\uDCA2',
33458      gscr: '\u210A',
33459      gsim: '\u2273',
33460      gsime: '\u2A8E',
33461      gsiml: '\u2A90',
33462      Gt: '\u226B',
33463      GT: '\u003E',
33464      gt: '\u003E',
33465      gtcc: '\u2AA7',
33466      gtcir: '\u2A7A',
33467      gtdot: '\u22D7',
33468      gtlPar: '\u2995',
33469      gtquest: '\u2A7C',
33470      gtrapprox: '\u2A86',
33471      gtrarr: '\u2978',
33472      gtrdot: '\u22D7',
33473      gtreqless: '\u22DB',
33474      gtreqqless: '\u2A8C',
33475      gtrless: '\u2277',
33476      gtrsim: '\u2273',
33477      gvertneqq: '\u2269\uFE00',
33478      gvnE: '\u2269\uFE00',
33479      Hacek: '\u02C7',
33480      hairsp: '\u200A',
33481      half: '\u00BD',
33482      hamilt: '\u210B',
33483      HARDcy: '\u042A',
33484      hardcy: '\u044A',
33485      hArr: '\u21D4',
33486      harr: '\u2194',
33487      harrcir: '\u2948',
33488      harrw: '\u21AD',
33489      Hat: '\u005E',
33490      hbar: '\u210F',
33491      Hcirc: '\u0124',
33492      hcirc: '\u0125',
33493      hearts: '\u2665',
33494      heartsuit: '\u2665',
33495      hellip: '\u2026',
33496      hercon: '\u22B9',
33497      Hfr: '\u210C',
33498      hfr: '\uD835\uDD25',
33499      HilbertSpace: '\u210B',
33500      hksearow: '\u2925',
33501      hkswarow: '\u2926',
33502      hoarr: '\u21FF',
33503      homtht: '\u223B',
33504      hookleftarrow: '\u21A9',
33505      hookrightarrow: '\u21AA',
33506      Hopf: '\u210D',
33507      hopf: '\uD835\uDD59',
33508      horbar: '\u2015',
33509      HorizontalLine: '\u2500',
33510      Hscr: '\u210B',
33511      hscr: '\uD835\uDCBD',
33512      hslash: '\u210F',
33513      Hstrok: '\u0126',
33514      hstrok: '\u0127',
33515      HumpDownHump: '\u224E',
33516      HumpEqual: '\u224F',
33517      hybull: '\u2043',
33518      hyphen: '\u2010',
33519      Iacute: '\u00CD',
33520      iacute: '\u00ED',
33521      ic: '\u2063',
33522      Icirc: '\u00CE',
33523      icirc: '\u00EE',
33524      Icy: '\u0418',
33525      icy: '\u0438',
33526      Idot: '\u0130',
33527      IEcy: '\u0415',
33528      iecy: '\u0435',
33529      iexcl: '\u00A1',
33530      iff: '\u21D4',
33531      Ifr: '\u2111',
33532      ifr: '\uD835\uDD26',
33533      Igrave: '\u00CC',
33534      igrave: '\u00EC',
33535      ii: '\u2148',
33536      iiiint: '\u2A0C',
33537      iiint: '\u222D',
33538      iinfin: '\u29DC',
33539      iiota: '\u2129',
33540      IJlig: '\u0132',
33541      ijlig: '\u0133',
33542      Im: '\u2111',
33543      Imacr: '\u012A',
33544      imacr: '\u012B',
33545      image: '\u2111',
33546      ImaginaryI: '\u2148',
33547      imagline: '\u2110',
33548      imagpart: '\u2111',
33549      imath: '\u0131',
33550      imof: '\u22B7',
33551      imped: '\u01B5',
33552      Implies: '\u21D2',
33553      in: '\u2208',
33554      incare: '\u2105',
33555      infin: '\u221E',
33556      infintie: '\u29DD',
33557      inodot: '\u0131',
33558      Int: '\u222C',
33559      int: '\u222B',
33560      intcal: '\u22BA',
33561      integers: '\u2124',
33562      Integral: '\u222B',
33563      intercal: '\u22BA',
33564      Intersection: '\u22C2',
33565      intlarhk: '\u2A17',
33566      intprod: '\u2A3C',
33567      InvisibleComma: '\u2063',
33568      InvisibleTimes: '\u2062',
33569      IOcy: '\u0401',
33570      iocy: '\u0451',
33571      Iogon: '\u012E',
33572      iogon: '\u012F',
33573      Iopf: '\uD835\uDD40',
33574      iopf: '\uD835\uDD5A',
33575      Iota: '\u0399',
33576      iota: '\u03B9',
33577      iprod: '\u2A3C',
33578      iquest: '\u00BF',
33579      Iscr: '\u2110',
33580      iscr: '\uD835\uDCBE',
33581      isin: '\u2208',
33582      isindot: '\u22F5',
33583      isinE: '\u22F9',
33584      isins: '\u22F4',
33585      isinsv: '\u22F3',
33586      isinv: '\u2208',
33587      it: '\u2062',
33588      Itilde: '\u0128',
33589      itilde: '\u0129',
33590      Iukcy: '\u0406',
33591      iukcy: '\u0456',
33592      Iuml: '\u00CF',
33593      iuml: '\u00EF',
33594      Jcirc: '\u0134',
33595      jcirc: '\u0135',
33596      Jcy: '\u0419',
33597      jcy: '\u0439',
33598      Jfr: '\uD835\uDD0D',
33599      jfr: '\uD835\uDD27',
33600      jmath: '\u0237',
33601      Jopf: '\uD835\uDD41',
33602      jopf: '\uD835\uDD5B',
33603      Jscr: '\uD835\uDCA5',
33604      jscr: '\uD835\uDCBF',
33605      Jsercy: '\u0408',
33606      jsercy: '\u0458',
33607      Jukcy: '\u0404',
33608      jukcy: '\u0454',
33609      Kappa: '\u039A',
33610      kappa: '\u03BA',
33611      kappav: '\u03F0',
33612      Kcedil: '\u0136',
33613      kcedil: '\u0137',
33614      Kcy: '\u041A',
33615      kcy: '\u043A',
33616      Kfr: '\uD835\uDD0E',
33617      kfr: '\uD835\uDD28',
33618      kgreen: '\u0138',
33619      KHcy: '\u0425',
33620      khcy: '\u0445',
33621      KJcy: '\u040C',
33622      kjcy: '\u045C',
33623      Kopf: '\uD835\uDD42',
33624      kopf: '\uD835\uDD5C',
33625      Kscr: '\uD835\uDCA6',
33626      kscr: '\uD835\uDCC0',
33627      lAarr: '\u21DA',
33628      Lacute: '\u0139',
33629      lacute: '\u013A',
33630      laemptyv: '\u29B4',
33631      lagran: '\u2112',
33632      Lambda: '\u039B',
33633      lambda: '\u03BB',
33634      Lang: '\u27EA',
33635      lang: '\u27E8',
33636      langd: '\u2991',
33637      langle: '\u27E8',
33638      lap: '\u2A85',
33639      Laplacetrf: '\u2112',
33640      laquo: '\u00AB',
33641      Larr: '\u219E',
33642      lArr: '\u21D0',
33643      larr: '\u2190',
33644      larrb: '\u21E4',
33645      larrbfs: '\u291F',
33646      larrfs: '\u291D',
33647      larrhk: '\u21A9',
33648      larrlp: '\u21AB',
33649      larrpl: '\u2939',
33650      larrsim: '\u2973',
33651      larrtl: '\u21A2',
33652      lat: '\u2AAB',
33653      lAtail: '\u291B',
33654      latail: '\u2919',
33655      late: '\u2AAD',
33656      lates: '\u2AAD\uFE00',
33657      lBarr: '\u290E',
33658      lbarr: '\u290C',
33659      lbbrk: '\u2772',
33660      lbrace: '\u007B',
33661      lbrack: '\u005B',
33662      lbrke: '\u298B',
33663      lbrksld: '\u298F',
33664      lbrkslu: '\u298D',
33665      Lcaron: '\u013D',
33666      lcaron: '\u013E',
33667      Lcedil: '\u013B',
33668      lcedil: '\u013C',
33669      lceil: '\u2308',
33670      lcub: '\u007B',
33671      Lcy: '\u041B',
33672      lcy: '\u043B',
33673      ldca: '\u2936',
33674      ldquo: '\u201C',
33675      ldquor: '\u201E',
33676      ldrdhar: '\u2967',
33677      ldrushar: '\u294B',
33678      ldsh: '\u21B2',
33679      lE: '\u2266',
33680      le: '\u2264',
33681      LeftAngleBracket: '\u27E8',
33682      LeftArrow: '\u2190',
33683      Leftarrow: '\u21D0',
33684      leftarrow: '\u2190',
33685      LeftArrowBar: '\u21E4',
33686      LeftArrowRightArrow: '\u21C6',
33687      leftarrowtail: '\u21A2',
33688      LeftCeiling: '\u2308',
33689      LeftDoubleBracket: '\u27E6',
33690      LeftDownTeeVector: '\u2961',
33691      LeftDownVector: '\u21C3',
33692      LeftDownVectorBar: '\u2959',
33693      LeftFloor: '\u230A',
33694      leftharpoondown: '\u21BD',
33695      leftharpoonup: '\u21BC',
33696      leftleftarrows: '\u21C7',
33697      LeftRightArrow: '\u2194',
33698      Leftrightarrow: '\u21D4',
33699      leftrightarrow: '\u2194',
33700      leftrightarrows: '\u21C6',
33701      leftrightharpoons: '\u21CB',
33702      leftrightsquigarrow: '\u21AD',
33703      LeftRightVector: '\u294E',
33704      LeftTee: '\u22A3',
33705      LeftTeeArrow: '\u21A4',
33706      LeftTeeVector: '\u295A',
33707      leftthreetimes: '\u22CB',
33708      LeftTriangle: '\u22B2',
33709      LeftTriangleBar: '\u29CF',
33710      LeftTriangleEqual: '\u22B4',
33711      LeftUpDownVector: '\u2951',
33712      LeftUpTeeVector: '\u2960',
33713      LeftUpVector: '\u21BF',
33714      LeftUpVectorBar: '\u2958',
33715      LeftVector: '\u21BC',
33716      LeftVectorBar: '\u2952',
33717      lEg: '\u2A8B',
33718      leg: '\u22DA',
33719      leq: '\u2264',
33720      leqq: '\u2266',
33721      leqslant: '\u2A7D',
33722      les: '\u2A7D',
33723      lescc: '\u2AA8',
33724      lesdot: '\u2A7F',
33725      lesdoto: '\u2A81',
33726      lesdotor: '\u2A83',
33727      lesg: '\u22DA\uFE00',
33728      lesges: '\u2A93',
33729      lessapprox: '\u2A85',
33730      lessdot: '\u22D6',
33731      lesseqgtr: '\u22DA',
33732      lesseqqgtr: '\u2A8B',
33733      LessEqualGreater: '\u22DA',
33734      LessFullEqual: '\u2266',
33735      LessGreater: '\u2276',
33736      lessgtr: '\u2276',
33737      LessLess: '\u2AA1',
33738      lesssim: '\u2272',
33739      LessSlantEqual: '\u2A7D',
33740      LessTilde: '\u2272',
33741      lfisht: '\u297C',
33742      lfloor: '\u230A',
33743      Lfr: '\uD835\uDD0F',
33744      lfr: '\uD835\uDD29',
33745      lg: '\u2276',
33746      lgE: '\u2A91',
33747      lHar: '\u2962',
33748      lhard: '\u21BD',
33749      lharu: '\u21BC',
33750      lharul: '\u296A',
33751      lhblk: '\u2584',
33752      LJcy: '\u0409',
33753      ljcy: '\u0459',
33754      Ll: '\u22D8',
33755      ll: '\u226A',
33756      llarr: '\u21C7',
33757      llcorner: '\u231E',
33758      Lleftarrow: '\u21DA',
33759      llhard: '\u296B',
33760      lltri: '\u25FA',
33761      Lmidot: '\u013F',
33762      lmidot: '\u0140',
33763      lmoust: '\u23B0',
33764      lmoustache: '\u23B0',
33765      lnap: '\u2A89',
33766      lnapprox: '\u2A89',
33767      lnE: '\u2268',
33768      lne: '\u2A87',
33769      lneq: '\u2A87',
33770      lneqq: '\u2268',
33771      lnsim: '\u22E6',
33772      loang: '\u27EC',
33773      loarr: '\u21FD',
33774      lobrk: '\u27E6',
33775      LongLeftArrow: '\u27F5',
33776      Longleftarrow: '\u27F8',
33777      longleftarrow: '\u27F5',
33778      LongLeftRightArrow: '\u27F7',
33779      Longleftrightarrow: '\u27FA',
33780      longleftrightarrow: '\u27F7',
33781      longmapsto: '\u27FC',
33782      LongRightArrow: '\u27F6',
33783      Longrightarrow: '\u27F9',
33784      longrightarrow: '\u27F6',
33785      looparrowleft: '\u21AB',
33786      looparrowright: '\u21AC',
33787      lopar: '\u2985',
33788      Lopf: '\uD835\uDD43',
33789      lopf: '\uD835\uDD5D',
33790      loplus: '\u2A2D',
33791      lotimes: '\u2A34',
33792      lowast: '\u2217',
33793      lowbar: '\u005F',
33794      LowerLeftArrow: '\u2199',
33795      LowerRightArrow: '\u2198',
33796      loz: '\u25CA',
33797      lozenge: '\u25CA',
33798      lozf: '\u29EB',
33799      lpar: '\u0028',
33800      lparlt: '\u2993',
33801      lrarr: '\u21C6',
33802      lrcorner: '\u231F',
33803      lrhar: '\u21CB',
33804      lrhard: '\u296D',
33805      lrm: '\u200E',
33806      lrtri: '\u22BF',
33807      lsaquo: '\u2039',
33808      Lscr: '\u2112',
33809      lscr: '\uD835\uDCC1',
33810      Lsh: '\u21B0',
33811      lsh: '\u21B0',
33812      lsim: '\u2272',
33813      lsime: '\u2A8D',
33814      lsimg: '\u2A8F',
33815      lsqb: '\u005B',
33816      lsquo: '\u2018',
33817      lsquor: '\u201A',
33818      Lstrok: '\u0141',
33819      lstrok: '\u0142',
33820      Lt: '\u226A',
33821      LT: '\u003C',
33822      lt: '\u003C',
33823      ltcc: '\u2AA6',
33824      ltcir: '\u2A79',
33825      ltdot: '\u22D6',
33826      lthree: '\u22CB',
33827      ltimes: '\u22C9',
33828      ltlarr: '\u2976',
33829      ltquest: '\u2A7B',
33830      ltri: '\u25C3',
33831      ltrie: '\u22B4',
33832      ltrif: '\u25C2',
33833      ltrPar: '\u2996',
33834      lurdshar: '\u294A',
33835      luruhar: '\u2966',
33836      lvertneqq: '\u2268\uFE00',
33837      lvnE: '\u2268\uFE00',
33838      macr: '\u00AF',
33839      male: '\u2642',
33840      malt: '\u2720',
33841      maltese: '\u2720',
33842      Map: '\u2905',
33843      map: '\u21A6',
33844      mapsto: '\u21A6',
33845      mapstodown: '\u21A7',
33846      mapstoleft: '\u21A4',
33847      mapstoup: '\u21A5',
33848      marker: '\u25AE',
33849      mcomma: '\u2A29',
33850      Mcy: '\u041C',
33851      mcy: '\u043C',
33852      mdash: '\u2014',
33853      mDDot: '\u223A',
33854      measuredangle: '\u2221',
33855      MediumSpace: '\u205F',
33856      Mellintrf: '\u2133',
33857      Mfr: '\uD835\uDD10',
33858      mfr: '\uD835\uDD2A',
33859      mho: '\u2127',
33860      micro: '\u00B5',
33861      mid: '\u2223',
33862      midast: '\u002A',
33863      midcir: '\u2AF0',
33864      middot: '\u00B7',
33865      minus: '\u2212',
33866      minusb: '\u229F',
33867      minusd: '\u2238',
33868      minusdu: '\u2A2A',
33869      MinusPlus: '\u2213',
33870      mlcp: '\u2ADB',
33871      mldr: '\u2026',
33872      mnplus: '\u2213',
33873      models: '\u22A7',
33874      Mopf: '\uD835\uDD44',
33875      mopf: '\uD835\uDD5E',
33876      mp: '\u2213',
33877      Mscr: '\u2133',
33878      mscr: '\uD835\uDCC2',
33879      mstpos: '\u223E',
33880      Mu: '\u039C',
33881      mu: '\u03BC',
33882      multimap: '\u22B8',
33883      mumap: '\u22B8',
33884      nabla: '\u2207',
33885      Nacute: '\u0143',
33886      nacute: '\u0144',
33887      nang: '\u2220\u20D2',
33888      nap: '\u2249',
33889      napE: '\u2A70\u0338',
33890      napid: '\u224B\u0338',
33891      napos: '\u0149',
33892      napprox: '\u2249',
33893      natur: '\u266E',
33894      natural: '\u266E',
33895      naturals: '\u2115',
33896      nbsp: '\u00A0',
33897      nbump: '\u224E\u0338',
33898      nbumpe: '\u224F\u0338',
33899      ncap: '\u2A43',
33900      Ncaron: '\u0147',
33901      ncaron: '\u0148',
33902      Ncedil: '\u0145',
33903      ncedil: '\u0146',
33904      ncong: '\u2247',
33905      ncongdot: '\u2A6D\u0338',
33906      ncup: '\u2A42',
33907      Ncy: '\u041D',
33908      ncy: '\u043D',
33909      ndash: '\u2013',
33910      ne: '\u2260',
33911      nearhk: '\u2924',
33912      neArr: '\u21D7',
33913      nearr: '\u2197',
33914      nearrow: '\u2197',
33915      nedot: '\u2250\u0338',
33916      NegativeMediumSpace: '\u200B',
33917      NegativeThickSpace: '\u200B',
33918      NegativeThinSpace: '\u200B',
33919      NegativeVeryThinSpace: '\u200B',
33920      nequiv: '\u2262',
33921      nesear: '\u2928',
33922      nesim: '\u2242\u0338',
33923      NestedGreaterGreater: '\u226B',
33924      NestedLessLess: '\u226A',
33925      NewLine: '\u000A',
33926      nexist: '\u2204',
33927      nexists: '\u2204',
33928      Nfr: '\uD835\uDD11',
33929      nfr: '\uD835\uDD2B',
33930      ngE: '\u2267\u0338',
33931      nge: '\u2271',
33932      ngeq: '\u2271',
33933      ngeqq: '\u2267\u0338',
33934      ngeqslant: '\u2A7E\u0338',
33935      nges: '\u2A7E\u0338',
33936      nGg: '\u22D9\u0338',
33937      ngsim: '\u2275',
33938      nGt: '\u226B\u20D2',
33939      ngt: '\u226F',
33940      ngtr: '\u226F',
33941      nGtv: '\u226B\u0338',
33942      nhArr: '\u21CE',
33943      nharr: '\u21AE',
33944      nhpar: '\u2AF2',
33945      ni: '\u220B',
33946      nis: '\u22FC',
33947      nisd: '\u22FA',
33948      niv: '\u220B',
33949      NJcy: '\u040A',
33950      njcy: '\u045A',
33951      nlArr: '\u21CD',
33952      nlarr: '\u219A',
33953      nldr: '\u2025',
33954      nlE: '\u2266\u0338',
33955      nle: '\u2270',
33956      nLeftarrow: '\u21CD',
33957      nleftarrow: '\u219A',
33958      nLeftrightarrow: '\u21CE',
33959      nleftrightarrow: '\u21AE',
33960      nleq: '\u2270',
33961      nleqq: '\u2266\u0338',
33962      nleqslant: '\u2A7D\u0338',
33963      nles: '\u2A7D\u0338',
33964      nless: '\u226E',
33965      nLl: '\u22D8\u0338',
33966      nlsim: '\u2274',
33967      nLt: '\u226A\u20D2',
33968      nlt: '\u226E',
33969      nltri: '\u22EA',
33970      nltrie: '\u22EC',
33971      nLtv: '\u226A\u0338',
33972      nmid: '\u2224',
33973      NoBreak: '\u2060',
33974      NonBreakingSpace: '\u00A0',
33975      Nopf: '\u2115',
33976      nopf: '\uD835\uDD5F',
33977      Not: '\u2AEC',
33978      not: '\u00AC',
33979      NotCongruent: '\u2262',
33980      NotCupCap: '\u226D',
33981      NotDoubleVerticalBar: '\u2226',
33982      NotElement: '\u2209',
33983      NotEqual: '\u2260',
33984      NotEqualTilde: '\u2242\u0338',
33985      NotExists: '\u2204',
33986      NotGreater: '\u226F',
33987      NotGreaterEqual: '\u2271',
33988      NotGreaterFullEqual: '\u2267\u0338',
33989      NotGreaterGreater: '\u226B\u0338',
33990      NotGreaterLess: '\u2279',
33991      NotGreaterSlantEqual: '\u2A7E\u0338',
33992      NotGreaterTilde: '\u2275',
33993      NotHumpDownHump: '\u224E\u0338',
33994      NotHumpEqual: '\u224F\u0338',
33995      notin: '\u2209',
33996      notindot: '\u22F5\u0338',
33997      notinE: '\u22F9\u0338',
33998      notinva: '\u2209',
33999      notinvb: '\u22F7',
34000      notinvc: '\u22F6',
34001      NotLeftTriangle: '\u22EA',
34002      NotLeftTriangleBar: '\u29CF\u0338',
34003      NotLeftTriangleEqual: '\u22EC',
34004      NotLess: '\u226E',
34005      NotLessEqual: '\u2270',
34006      NotLessGreater: '\u2278',
34007      NotLessLess: '\u226A\u0338',
34008      NotLessSlantEqual: '\u2A7D\u0338',
34009      NotLessTilde: '\u2274',
34010      NotNestedGreaterGreater: '\u2AA2\u0338',
34011      NotNestedLessLess: '\u2AA1\u0338',
34012      notni: '\u220C',
34013      notniva: '\u220C',
34014      notnivb: '\u22FE',
34015      notnivc: '\u22FD',
34016      NotPrecedes: '\u2280',
34017      NotPrecedesEqual: '\u2AAF\u0338',
34018      NotPrecedesSlantEqual: '\u22E0',
34019      NotReverseElement: '\u220C',
34020      NotRightTriangle: '\u22EB',
34021      NotRightTriangleBar: '\u29D0\u0338',
34022      NotRightTriangleEqual: '\u22ED',
34023      NotSquareSubset: '\u228F\u0338',
34024      NotSquareSubsetEqual: '\u22E2',
34025      NotSquareSuperset: '\u2290\u0338',
34026      NotSquareSupersetEqual: '\u22E3',
34027      NotSubset: '\u2282\u20D2',
34028      NotSubsetEqual: '\u2288',
34029      NotSucceeds: '\u2281',
34030      NotSucceedsEqual: '\u2AB0\u0338',
34031      NotSucceedsSlantEqual: '\u22E1',
34032      NotSucceedsTilde: '\u227F\u0338',
34033      NotSuperset: '\u2283\u20D2',
34034      NotSupersetEqual: '\u2289',
34035      NotTilde: '\u2241',
34036      NotTildeEqual: '\u2244',
34037      NotTildeFullEqual: '\u2247',
34038      NotTildeTilde: '\u2249',
34039      NotVerticalBar: '\u2224',
34040      npar: '\u2226',
34041      nparallel: '\u2226',
34042      nparsl: '\u2AFD\u20E5',
34043      npart: '\u2202\u0338',
34044      npolint: '\u2A14',
34045      npr: '\u2280',
34046      nprcue: '\u22E0',
34047      npre: '\u2AAF\u0338',
34048      nprec: '\u2280',
34049      npreceq: '\u2AAF\u0338',
34050      nrArr: '\u21CF',
34051      nrarr: '\u219B',
34052      nrarrc: '\u2933\u0338',
34053      nrarrw: '\u219D\u0338',
34054      nRightarrow: '\u21CF',
34055      nrightarrow: '\u219B',
34056      nrtri: '\u22EB',
34057      nrtrie: '\u22ED',
34058      nsc: '\u2281',
34059      nsccue: '\u22E1',
34060      nsce: '\u2AB0\u0338',
34061      Nscr: '\uD835\uDCA9',
34062      nscr: '\uD835\uDCC3',
34063      nshortmid: '\u2224',
34064      nshortparallel: '\u2226',
34065      nsim: '\u2241',
34066      nsime: '\u2244',
34067      nsimeq: '\u2244',
34068      nsmid: '\u2224',
34069      nspar: '\u2226',
34070      nsqsube: '\u22E2',
34071      nsqsupe: '\u22E3',
34072      nsub: '\u2284',
34073      nsubE: '\u2AC5\u0338',
34074      nsube: '\u2288',
34075      nsubset: '\u2282\u20D2',
34076      nsubseteq: '\u2288',
34077      nsubseteqq: '\u2AC5\u0338',
34078      nsucc: '\u2281',
34079      nsucceq: '\u2AB0\u0338',
34080      nsup: '\u2285',
34081      nsupE: '\u2AC6\u0338',
34082      nsupe: '\u2289',
34083      nsupset: '\u2283\u20D2',
34084      nsupseteq: '\u2289',
34085      nsupseteqq: '\u2AC6\u0338',
34086      ntgl: '\u2279',
34087      Ntilde: '\u00D1',
34088      ntilde: '\u00F1',
34089      ntlg: '\u2278',
34090      ntriangleleft: '\u22EA',
34091      ntrianglelefteq: '\u22EC',
34092      ntriangleright: '\u22EB',
34093      ntrianglerighteq: '\u22ED',
34094      Nu: '\u039D',
34095      nu: '\u03BD',
34096      num: '\u0023',
34097      numero: '\u2116',
34098      numsp: '\u2007',
34099      nvap: '\u224D\u20D2',
34100      nVDash: '\u22AF',
34101      nVdash: '\u22AE',
34102      nvDash: '\u22AD',
34103      nvdash: '\u22AC',
34104      nvge: '\u2265\u20D2',
34105      nvgt: '\u003E\u20D2',
34106      nvHarr: '\u2904',
34107      nvinfin: '\u29DE',
34108      nvlArr: '\u2902',
34109      nvle: '\u2264\u20D2',
34110      nvlt: '\u003C\u20D2',
34111      nvltrie: '\u22B4\u20D2',
34112      nvrArr: '\u2903',
34113      nvrtrie: '\u22B5\u20D2',
34114      nvsim: '\u223C\u20D2',
34115      nwarhk: '\u2923',
34116      nwArr: '\u21D6',
34117      nwarr: '\u2196',
34118      nwarrow: '\u2196',
34119      nwnear: '\u2927',
34120      Oacute: '\u00D3',
34121      oacute: '\u00F3',
34122      oast: '\u229B',
34123      ocir: '\u229A',
34124      Ocirc: '\u00D4',
34125      ocirc: '\u00F4',
34126      Ocy: '\u041E',
34127      ocy: '\u043E',
34128      odash: '\u229D',
34129      Odblac: '\u0150',
34130      odblac: '\u0151',
34131      odiv: '\u2A38',
34132      odot: '\u2299',
34133      odsold: '\u29BC',
34134      OElig: '\u0152',
34135      oelig: '\u0153',
34136      ofcir: '\u29BF',
34137      Ofr: '\uD835\uDD12',
34138      ofr: '\uD835\uDD2C',
34139      ogon: '\u02DB',
34140      Ograve: '\u00D2',
34141      ograve: '\u00F2',
34142      ogt: '\u29C1',
34143      ohbar: '\u29B5',
34144      ohm: '\u03A9',
34145      oint: '\u222E',
34146      olarr: '\u21BA',
34147      olcir: '\u29BE',
34148      olcross: '\u29BB',
34149      oline: '\u203E',
34150      olt: '\u29C0',
34151      Omacr: '\u014C',
34152      omacr: '\u014D',
34153      Omega: '\u03A9',
34154      omega: '\u03C9',
34155      Omicron: '\u039F',
34156      omicron: '\u03BF',
34157      omid: '\u29B6',
34158      ominus: '\u2296',
34159      Oopf: '\uD835\uDD46',
34160      oopf: '\uD835\uDD60',
34161      opar: '\u29B7',
34162      OpenCurlyDoubleQuote: '\u201C',
34163      OpenCurlyQuote: '\u2018',
34164      operp: '\u29B9',
34165      oplus: '\u2295',
34166      Or: '\u2A54',
34167      or: '\u2228',
34168      orarr: '\u21BB',
34169      ord: '\u2A5D',
34170      order: '\u2134',
34171      orderof: '\u2134',
34172      ordf: '\u00AA',
34173      ordm: '\u00BA',
34174      origof: '\u22B6',
34175      oror: '\u2A56',
34176      orslope: '\u2A57',
34177      orv: '\u2A5B',
34178      oS: '\u24C8',
34179      Oscr: '\uD835\uDCAA',
34180      oscr: '\u2134',
34181      Oslash: '\u00D8',
34182      oslash: '\u00F8',
34183      osol: '\u2298',
34184      Otilde: '\u00D5',
34185      otilde: '\u00F5',
34186      Otimes: '\u2A37',
34187      otimes: '\u2297',
34188      otimesas: '\u2A36',
34189      Ouml: '\u00D6',
34190      ouml: '\u00F6',
34191      ovbar: '\u233D',
34192      OverBar: '\u203E',
34193      OverBrace: '\u23DE',
34194      OverBracket: '\u23B4',
34195      OverParenthesis: '\u23DC',
34196      par: '\u2225',
34197      para: '\u00B6',
34198      parallel: '\u2225',
34199      parsim: '\u2AF3',
34200      parsl: '\u2AFD',
34201      part: '\u2202',
34202      PartialD: '\u2202',
34203      Pcy: '\u041F',
34204      pcy: '\u043F',
34205      percnt: '\u0025',
34206      period: '\u002E',
34207      permil: '\u2030',
34208      perp: '\u22A5',
34209      pertenk: '\u2031',
34210      Pfr: '\uD835\uDD13',
34211      pfr: '\uD835\uDD2D',
34212      Phi: '\u03A6',
34213      phi: '\u03C6',
34214      phiv: '\u03D5',
34215      phmmat: '\u2133',
34216      phone: '\u260E',
34217      Pi: '\u03A0',
34218      pi: '\u03C0',
34219      pitchfork: '\u22D4',
34220      piv: '\u03D6',
34221      planck: '\u210F',
34222      planckh: '\u210E',
34223      plankv: '\u210F',
34224      plus: '\u002B',
34225      plusacir: '\u2A23',
34226      plusb: '\u229E',
34227      pluscir: '\u2A22',
34228      plusdo: '\u2214',
34229      plusdu: '\u2A25',
34230      pluse: '\u2A72',
34231      PlusMinus: '\u00B1',
34232      plusmn: '\u00B1',
34233      plussim: '\u2A26',
34234      plustwo: '\u2A27',
34235      pm: '\u00B1',
34236      Poincareplane: '\u210C',
34237      pointint: '\u2A15',
34238      Popf: '\u2119',
34239      popf: '\uD835\uDD61',
34240      pound: '\u00A3',
34241      Pr: '\u2ABB',
34242      pr: '\u227A',
34243      prap: '\u2AB7',
34244      prcue: '\u227C',
34245      prE: '\u2AB3',
34246      pre: '\u2AAF',
34247      prec: '\u227A',
34248      precapprox: '\u2AB7',
34249      preccurlyeq: '\u227C',
34250      Precedes: '\u227A',
34251      PrecedesEqual: '\u2AAF',
34252      PrecedesSlantEqual: '\u227C',
34253      PrecedesTilde: '\u227E',
34254      preceq: '\u2AAF',
34255      precnapprox: '\u2AB9',
34256      precneqq: '\u2AB5',
34257      precnsim: '\u22E8',
34258      precsim: '\u227E',
34259      Prime: '\u2033',
34260      prime: '\u2032',
34261      primes: '\u2119',
34262      prnap: '\u2AB9',
34263      prnE: '\u2AB5',
34264      prnsim: '\u22E8',
34265      prod: '\u220F',
34266      Product: '\u220F',
34267      profalar: '\u232E',
34268      profline: '\u2312',
34269      profsurf: '\u2313',
34270      prop: '\u221D',
34271      Proportion: '\u2237',
34272      Proportional: '\u221D',
34273      propto: '\u221D',
34274      prsim: '\u227E',
34275      prurel: '\u22B0',
34276      Pscr: '\uD835\uDCAB',
34277      pscr: '\uD835\uDCC5',
34278      Psi: '\u03A8',
34279      psi: '\u03C8',
34280      puncsp: '\u2008',
34281      Qfr: '\uD835\uDD14',
34282      qfr: '\uD835\uDD2E',
34283      qint: '\u2A0C',
34284      Qopf: '\u211A',
34285      qopf: '\uD835\uDD62',
34286      qprime: '\u2057',
34287      Qscr: '\uD835\uDCAC',
34288      qscr: '\uD835\uDCC6',
34289      quaternions: '\u210D',
34290      quatint: '\u2A16',
34291      quest: '\u003F',
34292      questeq: '\u225F',
34293      QUOT: '\u0022',
34294      quot: '\u0022',
34295      rAarr: '\u21DB',
34296      race: '\u223D\u0331',
34297      Racute: '\u0154',
34298      racute: '\u0155',
34299      radic: '\u221A',
34300      raemptyv: '\u29B3',
34301      Rang: '\u27EB',
34302      rang: '\u27E9',
34303      rangd: '\u2992',
34304      range: '\u29A5',
34305      rangle: '\u27E9',
34306      raquo: '\u00BB',
34307      Rarr: '\u21A0',
34308      rArr: '\u21D2',
34309      rarr: '\u2192',
34310      rarrap: '\u2975',
34311      rarrb: '\u21E5',
34312      rarrbfs: '\u2920',
34313      rarrc: '\u2933',
34314      rarrfs: '\u291E',
34315      rarrhk: '\u21AA',
34316      rarrlp: '\u21AC',
34317      rarrpl: '\u2945',
34318      rarrsim: '\u2974',
34319      Rarrtl: '\u2916',
34320      rarrtl: '\u21A3',
34321      rarrw: '\u219D',
34322      rAtail: '\u291C',
34323      ratail: '\u291A',
34324      ratio: '\u2236',
34325      rationals: '\u211A',
34326      RBarr: '\u2910',
34327      rBarr: '\u290F',
34328      rbarr: '\u290D',
34329      rbbrk: '\u2773',
34330      rbrace: '\u007D',
34331      rbrack: '\u005D',
34332      rbrke: '\u298C',
34333      rbrksld: '\u298E',
34334      rbrkslu: '\u2990',
34335      Rcaron: '\u0158',
34336      rcaron: '\u0159',
34337      Rcedil: '\u0156',
34338      rcedil: '\u0157',
34339      rceil: '\u2309',
34340      rcub: '\u007D',
34341      Rcy: '\u0420',
34342      rcy: '\u0440',
34343      rdca: '\u2937',
34344      rdldhar: '\u2969',
34345      rdquo: '\u201D',
34346      rdquor: '\u201D',
34347      rdsh: '\u21B3',
34348      Re: '\u211C',
34349      real: '\u211C',
34350      realine: '\u211B',
34351      realpart: '\u211C',
34352      reals: '\u211D',
34353      rect: '\u25AD',
34354      REG: '\u00AE',
34355      reg: '\u00AE',
34356      ReverseElement: '\u220B',
34357      ReverseEquilibrium: '\u21CB',
34358      ReverseUpEquilibrium: '\u296F',
34359      rfisht: '\u297D',
34360      rfloor: '\u230B',
34361      Rfr: '\u211C',
34362      rfr: '\uD835\uDD2F',
34363      rHar: '\u2964',
34364      rhard: '\u21C1',
34365      rharu: '\u21C0',
34366      rharul: '\u296C',
34367      Rho: '\u03A1',
34368      rho: '\u03C1',
34369      rhov: '\u03F1',
34370      RightAngleBracket: '\u27E9',
34371      RightArrow: '\u2192',
34372      Rightarrow: '\u21D2',
34373      rightarrow: '\u2192',
34374      RightArrowBar: '\u21E5',
34375      RightArrowLeftArrow: '\u21C4',
34376      rightarrowtail: '\u21A3',
34377      RightCeiling: '\u2309',
34378      RightDoubleBracket: '\u27E7',
34379      RightDownTeeVector: '\u295D',
34380      RightDownVector: '\u21C2',
34381      RightDownVectorBar: '\u2955',
34382      RightFloor: '\u230B',
34383      rightharpoondown: '\u21C1',
34384      rightharpoonup: '\u21C0',
34385      rightleftarrows: '\u21C4',
34386      rightleftharpoons: '\u21CC',
34387      rightrightarrows: '\u21C9',
34388      rightsquigarrow: '\u219D',
34389      RightTee: '\u22A2',
34390      RightTeeArrow: '\u21A6',
34391      RightTeeVector: '\u295B',
34392      rightthreetimes: '\u22CC',
34393      RightTriangle: '\u22B3',
34394      RightTriangleBar: '\u29D0',
34395      RightTriangleEqual: '\u22B5',
34396      RightUpDownVector: '\u294F',
34397      RightUpTeeVector: '\u295C',
34398      RightUpVector: '\u21BE',
34399      RightUpVectorBar: '\u2954',
34400      RightVector: '\u21C0',
34401      RightVectorBar: '\u2953',
34402      ring: '\u02DA',
34403      risingdotseq: '\u2253',
34404      rlarr: '\u21C4',
34405      rlhar: '\u21CC',
34406      rlm: '\u200F',
34407      rmoust: '\u23B1',
34408      rmoustache: '\u23B1',
34409      rnmid: '\u2AEE',
34410      roang: '\u27ED',
34411      roarr: '\u21FE',
34412      robrk: '\u27E7',
34413      ropar: '\u2986',
34414      Ropf: '\u211D',
34415      ropf: '\uD835\uDD63',
34416      roplus: '\u2A2E',
34417      rotimes: '\u2A35',
34418      RoundImplies: '\u2970',
34419      rpar: '\u0029',
34420      rpargt: '\u2994',
34421      rppolint: '\u2A12',
34422      rrarr: '\u21C9',
34423      Rrightarrow: '\u21DB',
34424      rsaquo: '\u203A',
34425      Rscr: '\u211B',
34426      rscr: '\uD835\uDCC7',
34427      Rsh: '\u21B1',
34428      rsh: '\u21B1',
34429      rsqb: '\u005D',
34430      rsquo: '\u2019',
34431      rsquor: '\u2019',
34432      rthree: '\u22CC',
34433      rtimes: '\u22CA',
34434      rtri: '\u25B9',
34435      rtrie: '\u22B5',
34436      rtrif: '\u25B8',
34437      rtriltri: '\u29CE',
34438      RuleDelayed: '\u29F4',
34439      ruluhar: '\u2968',
34440      rx: '\u211E',
34441      Sacute: '\u015A',
34442      sacute: '\u015B',
34443      sbquo: '\u201A',
34444      Sc: '\u2ABC',
34445      sc: '\u227B',
34446      scap: '\u2AB8',
34447      Scaron: '\u0160',
34448      scaron: '\u0161',
34449      sccue: '\u227D',
34450      scE: '\u2AB4',
34451      sce: '\u2AB0',
34452      Scedil: '\u015E',
34453      scedil: '\u015F',
34454      Scirc: '\u015C',
34455      scirc: '\u015D',
34456      scnap: '\u2ABA',
34457      scnE: '\u2AB6',
34458      scnsim: '\u22E9',
34459      scpolint: '\u2A13',
34460      scsim: '\u227F',
34461      Scy: '\u0421',
34462      scy: '\u0441',
34463      sdot: '\u22C5',
34464      sdotb: '\u22A1',
34465      sdote: '\u2A66',
34466      searhk: '\u2925',
34467      seArr: '\u21D8',
34468      searr: '\u2198',
34469      searrow: '\u2198',
34470      sect: '\u00A7',
34471      semi: '\u003B',
34472      seswar: '\u2929',
34473      setminus: '\u2216',
34474      setmn: '\u2216',
34475      sext: '\u2736',
34476      Sfr: '\uD835\uDD16',
34477      sfr: '\uD835\uDD30',
34478      sfrown: '\u2322',
34479      sharp: '\u266F',
34480      SHCHcy: '\u0429',
34481      shchcy: '\u0449',
34482      SHcy: '\u0428',
34483      shcy: '\u0448',
34484      ShortDownArrow: '\u2193',
34485      ShortLeftArrow: '\u2190',
34486      shortmid: '\u2223',
34487      shortparallel: '\u2225',
34488      ShortRightArrow: '\u2192',
34489      ShortUpArrow: '\u2191',
34490      shy: '\u00AD',
34491      Sigma: '\u03A3',
34492      sigma: '\u03C3',
34493      sigmaf: '\u03C2',
34494      sigmav: '\u03C2',
34495      sim: '\u223C',
34496      simdot: '\u2A6A',
34497      sime: '\u2243',
34498      simeq: '\u2243',
34499      simg: '\u2A9E',
34500      simgE: '\u2AA0',
34501      siml: '\u2A9D',
34502      simlE: '\u2A9F',
34503      simne: '\u2246',
34504      simplus: '\u2A24',
34505      simrarr: '\u2972',
34506      slarr: '\u2190',
34507      SmallCircle: '\u2218',
34508      smallsetminus: '\u2216',
34509      smashp: '\u2A33',
34510      smeparsl: '\u29E4',
34511      smid: '\u2223',
34512      smile: '\u2323',
34513      smt: '\u2AAA',
34514      smte: '\u2AAC',
34515      smtes: '\u2AAC\uFE00',
34516      SOFTcy: '\u042C',
34517      softcy: '\u044C',
34518      sol: '\u002F',
34519      solb: '\u29C4',
34520      solbar: '\u233F',
34521      Sopf: '\uD835\uDD4A',
34522      sopf: '\uD835\uDD64',
34523      spades: '\u2660',
34524      spadesuit: '\u2660',
34525      spar: '\u2225',
34526      sqcap: '\u2293',
34527      sqcaps: '\u2293\uFE00',
34528      sqcup: '\u2294',
34529      sqcups: '\u2294\uFE00',
34530      Sqrt: '\u221A',
34531      sqsub: '\u228F',
34532      sqsube: '\u2291',
34533      sqsubset: '\u228F',
34534      sqsubseteq: '\u2291',
34535      sqsup: '\u2290',
34536      sqsupe: '\u2292',
34537      sqsupset: '\u2290',
34538      sqsupseteq: '\u2292',
34539      squ: '\u25A1',
34540      Square: '\u25A1',
34541      square: '\u25A1',
34542      SquareIntersection: '\u2293',
34543      SquareSubset: '\u228F',
34544      SquareSubsetEqual: '\u2291',
34545      SquareSuperset: '\u2290',
34546      SquareSupersetEqual: '\u2292',
34547      SquareUnion: '\u2294',
34548      squarf: '\u25AA',
34549      squf: '\u25AA',
34550      srarr: '\u2192',
34551      Sscr: '\uD835\uDCAE',
34552      sscr: '\uD835\uDCC8',
34553      ssetmn: '\u2216',
34554      ssmile: '\u2323',
34555      sstarf: '\u22C6',
34556      Star: '\u22C6',
34557      star: '\u2606',
34558      starf: '\u2605',
34559      straightepsilon: '\u03F5',
34560      straightphi: '\u03D5',
34561      strns: '\u00AF',
34562      Sub: '\u22D0',
34563      sub: '\u2282',
34564      subdot: '\u2ABD',
34565      subE: '\u2AC5',
34566      sube: '\u2286',
34567      subedot: '\u2AC3',
34568      submult: '\u2AC1',
34569      subnE: '\u2ACB',
34570      subne: '\u228A',
34571      subplus: '\u2ABF',
34572      subrarr: '\u2979',
34573      Subset: '\u22D0',
34574      subset: '\u2282',
34575      subseteq: '\u2286',
34576      subseteqq: '\u2AC5',
34577      SubsetEqual: '\u2286',
34578      subsetneq: '\u228A',
34579      subsetneqq: '\u2ACB',
34580      subsim: '\u2AC7',
34581      subsub: '\u2AD5',
34582      subsup: '\u2AD3',
34583      succ: '\u227B',
34584      succapprox: '\u2AB8',
34585      succcurlyeq: '\u227D',
34586      Succeeds: '\u227B',
34587      SucceedsEqual: '\u2AB0',
34588      SucceedsSlantEqual: '\u227D',
34589      SucceedsTilde: '\u227F',
34590      succeq: '\u2AB0',
34591      succnapprox: '\u2ABA',
34592      succneqq: '\u2AB6',
34593      succnsim: '\u22E9',
34594      succsim: '\u227F',
34595      SuchThat: '\u220B',
34596      Sum: '\u2211',
34597      sum: '\u2211',
34598      sung: '\u266A',
34599      Sup: '\u22D1',
34600      sup: '\u2283',
34601      sup1: '\u00B9',
34602      sup2: '\u00B2',
34603      sup3: '\u00B3',
34604      supdot: '\u2ABE',
34605      supdsub: '\u2AD8',
34606      supE: '\u2AC6',
34607      supe: '\u2287',
34608      supedot: '\u2AC4',
34609      Superset: '\u2283',
34610      SupersetEqual: '\u2287',
34611      suphsol: '\u27C9',
34612      suphsub: '\u2AD7',
34613      suplarr: '\u297B',
34614      supmult: '\u2AC2',
34615      supnE: '\u2ACC',
34616      supne: '\u228B',
34617      supplus: '\u2AC0',
34618      Supset: '\u22D1',
34619      supset: '\u2283',
34620      supseteq: '\u2287',
34621      supseteqq: '\u2AC6',
34622      supsetneq: '\u228B',
34623      supsetneqq: '\u2ACC',
34624      supsim: '\u2AC8',
34625      supsub: '\u2AD4',
34626      supsup: '\u2AD6',
34627      swarhk: '\u2926',
34628      swArr: '\u21D9',
34629      swarr: '\u2199',
34630      swarrow: '\u2199',
34631      swnwar: '\u292A',
34632      szlig: '\u00DF',
34633      Tab: '\u0009',
34634      target: '\u2316',
34635      Tau: '\u03A4',
34636      tau: '\u03C4',
34637      tbrk: '\u23B4',
34638      Tcaron: '\u0164',
34639      tcaron: '\u0165',
34640      Tcedil: '\u0162',
34641      tcedil: '\u0163',
34642      Tcy: '\u0422',
34643      tcy: '\u0442',
34644      tdot: '\u20DB',
34645      telrec: '\u2315',
34646      Tfr: '\uD835\uDD17',
34647      tfr: '\uD835\uDD31',
34648      there4: '\u2234',
34649      Therefore: '\u2234',
34650      therefore: '\u2234',
34651      Theta: '\u0398',
34652      theta: '\u03B8',
34653      thetasym: '\u03D1',
34654      thetav: '\u03D1',
34655      thickapprox: '\u2248',
34656      thicksim: '\u223C',
34657      ThickSpace: '\u205F\u200A',
34658      thinsp: '\u2009',
34659      ThinSpace: '\u2009',
34660      thkap: '\u2248',
34661      thksim: '\u223C',
34662      THORN: '\u00DE',
34663      thorn: '\u00FE',
34664      Tilde: '\u223C',
34665      tilde: '\u02DC',
34666      TildeEqual: '\u2243',
34667      TildeFullEqual: '\u2245',
34668      TildeTilde: '\u2248',
34669      times: '\u00D7',
34670      timesb: '\u22A0',
34671      timesbar: '\u2A31',
34672      timesd: '\u2A30',
34673      tint: '\u222D',
34674      toea: '\u2928',
34675      top: '\u22A4',
34676      topbot: '\u2336',
34677      topcir: '\u2AF1',
34678      Topf: '\uD835\uDD4B',
34679      topf: '\uD835\uDD65',
34680      topfork: '\u2ADA',
34681      tosa: '\u2929',
34682      tprime: '\u2034',
34683      TRADE: '\u2122',
34684      trade: '\u2122',
34685      triangle: '\u25B5',
34686      triangledown: '\u25BF',
34687      triangleleft: '\u25C3',
34688      trianglelefteq: '\u22B4',
34689      triangleq: '\u225C',
34690      triangleright: '\u25B9',
34691      trianglerighteq: '\u22B5',
34692      tridot: '\u25EC',
34693      trie: '\u225C',
34694      triminus: '\u2A3A',
34695      TripleDot: '\u20DB',
34696      triplus: '\u2A39',
34697      trisb: '\u29CD',
34698      tritime: '\u2A3B',
34699      trpezium: '\u23E2',
34700      Tscr: '\uD835\uDCAF',
34701      tscr: '\uD835\uDCC9',
34702      TScy: '\u0426',
34703      tscy: '\u0446',
34704      TSHcy: '\u040B',
34705      tshcy: '\u045B',
34706      Tstrok: '\u0166',
34707      tstrok: '\u0167',
34708      twixt: '\u226C',
34709      twoheadleftarrow: '\u219E',
34710      twoheadrightarrow: '\u21A0',
34711      Uacute: '\u00DA',
34712      uacute: '\u00FA',
34713      Uarr: '\u219F',
34714      uArr: '\u21D1',
34715      uarr: '\u2191',
34716      Uarrocir: '\u2949',
34717      Ubrcy: '\u040E',
34718      ubrcy: '\u045E',
34719      Ubreve: '\u016C',
34720      ubreve: '\u016D',
34721      Ucirc: '\u00DB',
34722      ucirc: '\u00FB',
34723      Ucy: '\u0423',
34724      ucy: '\u0443',
34725      udarr: '\u21C5',
34726      Udblac: '\u0170',
34727      udblac: '\u0171',
34728      udhar: '\u296E',
34729      ufisht: '\u297E',
34730      Ufr: '\uD835\uDD18',
34731      ufr: '\uD835\uDD32',
34732      Ugrave: '\u00D9',
34733      ugrave: '\u00F9',
34734      uHar: '\u2963',
34735      uharl: '\u21BF',
34736      uharr: '\u21BE',
34737      uhblk: '\u2580',
34738      ulcorn: '\u231C',
34739      ulcorner: '\u231C',
34740      ulcrop: '\u230F',
34741      ultri: '\u25F8',
34742      Umacr: '\u016A',
34743      umacr: '\u016B',
34744      uml: '\u00A8',
34745      UnderBar: '\u005F',
34746      UnderBrace: '\u23DF',
34747      UnderBracket: '\u23B5',
34748      UnderParenthesis: '\u23DD',
34749      Union: '\u22C3',
34750      UnionPlus: '\u228E',
34751      Uogon: '\u0172',
34752      uogon: '\u0173',
34753      Uopf: '\uD835\uDD4C',
34754      uopf: '\uD835\uDD66',
34755      UpArrow: '\u2191',
34756      Uparrow: '\u21D1',
34757      uparrow: '\u2191',
34758      UpArrowBar: '\u2912',
34759      UpArrowDownArrow: '\u21C5',
34760      UpDownArrow: '\u2195',
34761      Updownarrow: '\u21D5',
34762      updownarrow: '\u2195',
34763      UpEquilibrium: '\u296E',
34764      upharpoonleft: '\u21BF',
34765      upharpoonright: '\u21BE',
34766      uplus: '\u228E',
34767      UpperLeftArrow: '\u2196',
34768      UpperRightArrow: '\u2197',
34769      Upsi: '\u03D2',
34770      upsi: '\u03C5',
34771      upsih: '\u03D2',
34772      Upsilon: '\u03A5',
34773      upsilon: '\u03C5',
34774      UpTee: '\u22A5',
34775      UpTeeArrow: '\u21A5',
34776      upuparrows: '\u21C8',
34777      urcorn: '\u231D',
34778      urcorner: '\u231D',
34779      urcrop: '\u230E',
34780      Uring: '\u016E',
34781      uring: '\u016F',
34782      urtri: '\u25F9',
34783      Uscr: '\uD835\uDCB0',
34784      uscr: '\uD835\uDCCA',
34785      utdot: '\u22F0',
34786      Utilde: '\u0168',
34787      utilde: '\u0169',
34788      utri: '\u25B5',
34789      utrif: '\u25B4',
34790      uuarr: '\u21C8',
34791      Uuml: '\u00DC',
34792      uuml: '\u00FC',
34793      uwangle: '\u29A7',
34794      vangrt: '\u299C',
34795      varepsilon: '\u03F5',
34796      varkappa: '\u03F0',
34797      varnothing: '\u2205',
34798      varphi: '\u03D5',
34799      varpi: '\u03D6',
34800      varpropto: '\u221D',
34801      vArr: '\u21D5',
34802      varr: '\u2195',
34803      varrho: '\u03F1',
34804      varsigma: '\u03C2',
34805      varsubsetneq: '\u228A\uFE00',
34806      varsubsetneqq: '\u2ACB\uFE00',
34807      varsupsetneq: '\u228B\uFE00',
34808      varsupsetneqq: '\u2ACC\uFE00',
34809      vartheta: '\u03D1',
34810      vartriangleleft: '\u22B2',
34811      vartriangleright: '\u22B3',
34812      Vbar: '\u2AEB',
34813      vBar: '\u2AE8',
34814      vBarv: '\u2AE9',
34815      Vcy: '\u0412',
34816      vcy: '\u0432',
34817      VDash: '\u22AB',
34818      Vdash: '\u22A9',
34819      vDash: '\u22A8',
34820      vdash: '\u22A2',
34821      Vdashl: '\u2AE6',
34822      Vee: '\u22C1',
34823      vee: '\u2228',
34824      veebar: '\u22BB',
34825      veeeq: '\u225A',
34826      vellip: '\u22EE',
34827      Verbar: '\u2016',
34828      verbar: '\u007C',
34829      Vert: '\u2016',
34830      vert: '\u007C',
34831      VerticalBar: '\u2223',
34832      VerticalLine: '\u007C',
34833      VerticalSeparator: '\u2758',
34834      VerticalTilde: '\u2240',
34835      VeryThinSpace: '\u200A',
34836      Vfr: '\uD835\uDD19',
34837      vfr: '\uD835\uDD33',
34838      vltri: '\u22B2',
34839      vnsub: '\u2282\u20D2',
34840      vnsup: '\u2283\u20D2',
34841      Vopf: '\uD835\uDD4D',
34842      vopf: '\uD835\uDD67',
34843      vprop: '\u221D',
34844      vrtri: '\u22B3',
34845      Vscr: '\uD835\uDCB1',
34846      vscr: '\uD835\uDCCB',
34847      vsubnE: '\u2ACB\uFE00',
34848      vsubne: '\u228A\uFE00',
34849      vsupnE: '\u2ACC\uFE00',
34850      vsupne: '\u228B\uFE00',
34851      Vvdash: '\u22AA',
34852      vzigzag: '\u299A',
34853      Wcirc: '\u0174',
34854      wcirc: '\u0175',
34855      wedbar: '\u2A5F',
34856      Wedge: '\u22C0',
34857      wedge: '\u2227',
34858      wedgeq: '\u2259',
34859      weierp: '\u2118',
34860      Wfr: '\uD835\uDD1A',
34861      wfr: '\uD835\uDD34',
34862      Wopf: '\uD835\uDD4E',
34863      wopf: '\uD835\uDD68',
34864      wp: '\u2118',
34865      wr: '\u2240',
34866      wreath: '\u2240',
34867      Wscr: '\uD835\uDCB2',
34868      wscr: '\uD835\uDCCC',
34869      xcap: '\u22C2',
34870      xcirc: '\u25EF',
34871      xcup: '\u22C3',
34872      xdtri: '\u25BD',
34873      Xfr: '\uD835\uDD1B',
34874      xfr: '\uD835\uDD35',
34875      xhArr: '\u27FA',
34876      xharr: '\u27F7',
34877      Xi: '\u039E',
34878      xi: '\u03BE',
34879      xlArr: '\u27F8',
34880      xlarr: '\u27F5',
34881      xmap: '\u27FC',
34882      xnis: '\u22FB',
34883      xodot: '\u2A00',
34884      Xopf: '\uD835\uDD4F',
34885      xopf: '\uD835\uDD69',
34886      xoplus: '\u2A01',
34887      xotime: '\u2A02',
34888      xrArr: '\u27F9',
34889      xrarr: '\u27F6',
34890      Xscr: '\uD835\uDCB3',
34891      xscr: '\uD835\uDCCD',
34892      xsqcup: '\u2A06',
34893      xuplus: '\u2A04',
34894      xutri: '\u25B3',
34895      xvee: '\u22C1',
34896      xwedge: '\u22C0',
34897      Yacute: '\u00DD',
34898      yacute: '\u00FD',
34899      YAcy: '\u042F',
34900      yacy: '\u044F',
34901      Ycirc: '\u0176',
34902      ycirc: '\u0177',
34903      Ycy: '\u042B',
34904      ycy: '\u044B',
34905      yen: '\u00A5',
34906      Yfr: '\uD835\uDD1C',
34907      yfr: '\uD835\uDD36',
34908      YIcy: '\u0407',
34909      yicy: '\u0457',
34910      Yopf: '\uD835\uDD50',
34911      yopf: '\uD835\uDD6A',
34912      Yscr: '\uD835\uDCB4',
34913      yscr: '\uD835\uDCCE',
34914      YUcy: '\u042E',
34915      yucy: '\u044E',
34916      Yuml: '\u0178',
34917      yuml: '\u00FF',
34918      Zacute: '\u0179',
34919      zacute: '\u017A',
34920      Zcaron: '\u017D',
34921      zcaron: '\u017E',
34922      Zcy: '\u0417',
34923      zcy: '\u0437',
34924      Zdot: '\u017B',
34925      zdot: '\u017C',
34926      zeetrf: '\u2128',
34927      ZeroWidthSpace: '\u200B',
34928      Zeta: '\u0396',
34929      zeta: '\u03B6',
34930      Zfr: '\u2128',
34931      zfr: '\uD835\uDD37',
34932      ZHcy: '\u0416',
34933      zhcy: '\u0436',
34934      zigrarr: '\u21DD',
34935      Zopf: '\u2124',
34936      zopf: '\uD835\uDD6B',
34937      Zscr: '\uD835\uDCB5',
34938      zscr: '\uD835\uDCCF',
34939      zwj: '\u200D',
34940      zwnj: '\u200C'
34941    });
34942
34943    /**
34944     * @deprecated use `HTML_ENTITIES` instead
34945     * @see HTML_ENTITIES
34946     */
34947    exports.entityMap = exports.HTML_ENTITIES;
34948  });
34949  entities.XML_ENTITIES;
34950  entities.HTML_ENTITIES;
34951  entities.entityMap;
34952
34953  var NAMESPACE$1 = conventions.NAMESPACE;
vendor: 4,452 bytes, lines 34954-35051
34954
34955  //[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]
34956  //[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
34957  //[5]   	Name	   ::=   	NameStartChar (NameChar)*
34958  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
34959  var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
34960  var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$');
34961  //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
34962  //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(',')
34963
34964  //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
34965  //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
34966  var S_TAG = 0; //tag name offerring
34967  var S_ATTR = 1; //attr name offerring
34968  var S_ATTR_SPACE = 2; //attr name end and space offer
34969  var S_EQ = 3; //=space?
34970  var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
34971  var S_ATTR_END = 5; //attr value end and no space(quot end)
34972  var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
34973  var S_TAG_CLOSE = 7; //closed el<el />
34974
34975  /**
34976   * Creates an error that will not be caught by XMLReader aka the SAX parser.
34977   *
34978   * @param {string} message
34979   * @param {any?} locator Optional, can provide details about the location in the source
34980   * @constructor
34981   */
34982  function ParseError$1(message, locator) {
34983    this.message = message;
34984    this.locator = locator;
34985    if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError$1);
34986  }
34987  ParseError$1.prototype = new Error();
34988  ParseError$1.prototype.name = ParseError$1.name;
34989  function XMLReader$1() {}
34990  XMLReader$1.prototype = {
34991    parse: function (source, defaultNSMap, entityMap) {
34992      var domBuilder = this.domBuilder;
34993      domBuilder.startDocument();
34994      _copy(defaultNSMap, defaultNSMap = {});
34995      parse$1(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
34996      domBuilder.endDocument();
34997    }
34998  };
34999  function parse$1(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
35000    function fixedFromCharCode(code) {
35001      // String.prototype.fromCharCode does not supports
35002      // > 2 bytes unicode chars directly
35003      if (code > 0xffff) {
35004        code -= 0x10000;
35005        var surrogate1 = 0xd800 + (code >> 10),
35006          surrogate2 = 0xdc00 + (code & 0x3ff);
35007        return String.fromCharCode(surrogate1, surrogate2);
35008      } else {
35009        return String.fromCharCode(code);
35010      }
35011    }
35012    function entityReplacer(a) {
35013      var k = a.slice(1, -1);
35014      if (Object.hasOwnProperty.call(entityMap, k)) {
35015        return entityMap[k];
35016      } else if (k.charAt(0) === '#') {
35017        return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x')));
35018      } else {
35019        errorHandler.error('entity not found:' + a);
35020        return a;
35021      }
35022    }
35023    function appendText(end) {
35024      //has some bugs
35025      if (end > start) {
35026        var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer);
35027        locator && position(start);
35028        domBuilder.characters(xt, 0, end - start);
35029        start = end;
35030      }
35031    }
35032    function position(p, m) {
35033      while (p >= lineEnd && (m = linePattern.exec(source))) {
35034        lineStart = m.index;
35035        lineEnd = lineStart + m[0].length;
35036        locator.lineNumber++;
35037        //console.log('line++:',locator,startPos,endPos)
35038      }
35039
35040      locator.columnNumber = p - lineStart + 1;
35041    }
35042    var lineStart = 0;
35043    var lineEnd = 0;
35044    var linePattern = /.*(?:\r\n?|\n)|.*$/g;
35045    var locator = domBuilder.locator;
35046    var parseStack = [{
35047      currentNSMap: defaultNSMapCopy
35048    }];
35049    var closeMap = {};
35050    var start = 0;
35051    while (true) {
vendor: 16,142 bytes, lines 35052-35508
35052      try {
35053        var tagStart = source.indexOf('<', start);
35054        if (tagStart < 0) {
35055          if (!source.substr(start).match(/^\s*$/)) {
35056            var doc = domBuilder.doc;
35057            var text = doc.createTextNode(source.substr(start));
35058            doc.appendChild(text);
35059            domBuilder.currentElement = text;
35060          }
35061          return;
35062        }
35063        if (tagStart > start) {
35064          appendText(tagStart);
35065        }
35066        switch (source.charAt(tagStart + 1)) {
35067          case '/':
35068            var end = source.indexOf('>', tagStart + 3);
35069            var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');
35070            var config = parseStack.pop();
35071            if (end < 0) {
35072              tagName = source.substring(tagStart + 2).replace(/[\s<].*/, '');
35073              errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName);
35074              end = tagStart + 1 + tagName.length;
35075            } else if (tagName.match(/\s</)) {
35076              tagName = tagName.replace(/[\s<].*/, '');
35077              errorHandler.error("end tag name: " + tagName + ' maybe not complete');
35078              end = tagStart + 1 + tagName.length;
35079            }
35080            var localNSMap = config.localNSMap;
35081            var endMatch = config.tagName == tagName;
35082            var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase();
35083            if (endIgnoreCaseMach) {
35084              domBuilder.endElement(config.uri, config.localName, tagName);
35085              if (localNSMap) {
35086                for (var prefix in localNSMap) {
35087                  if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
35088                    domBuilder.endPrefixMapping(prefix);
35089                  }
35090                }
35091              }
35092              if (!endMatch) {
35093                errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); // No known test case
35094              }
35095            } else {
35096              parseStack.push(config);
35097            }
35098            end++;
35099            break;
35100          // end elment
35101          case '?':
35102            // <?...?>
35103            locator && position(tagStart);
35104            end = parseInstruction(source, tagStart, domBuilder);
35105            break;
35106          case '!':
35107            // <!doctype,<![CDATA,<!--
35108            locator && position(tagStart);
35109            end = parseDCC(source, tagStart, domBuilder, errorHandler);
35110            break;
35111          default:
35112            locator && position(tagStart);
35113            var el = new ElementAttributes();
35114            var currentNSMap = parseStack[parseStack.length - 1].currentNSMap;
35115            //elStartEnd
35116            var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler);
35117            var len = el.length;
35118            if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) {
35119              el.closed = true;
35120              if (!entityMap.nbsp) {
35121                errorHandler.warning('unclosed xml attribute');
35122              }
35123            }
35124            if (locator && len) {
35125              var locator2 = copyLocator(locator, {});
35126              //try{//attribute position fixed
35127              for (var i = 0; i < len; i++) {
35128                var a = el[i];
35129                position(a.offset);
35130                a.locator = copyLocator(locator, {});
35131              }
35132              domBuilder.locator = locator2;
35133              if (appendElement$1(el, domBuilder, currentNSMap)) {
35134                parseStack.push(el);
35135              }
35136              domBuilder.locator = locator;
35137            } else {
35138              if (appendElement$1(el, domBuilder, currentNSMap)) {
35139                parseStack.push(el);
35140              }
35141            }
35142            if (NAMESPACE$1.isHTML(el.uri) && !el.closed) {
35143              end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
35144            } else {
35145              end++;
35146            }
35147        }
35148      } catch (e) {
35149        if (e instanceof ParseError$1) {
35150          throw e;
35151        }
35152        errorHandler.error('element parse error: ' + e);
35153        end = -1;
35154      }
35155      if (end > start) {
35156        start = end;
35157      } else {
35158        //TODO: 这里有可能sax回退,有位置错误风险
35159        appendText(Math.max(tagStart, start) + 1);
35160      }
35161    }
35162  }
35163  function copyLocator(f, t) {
35164    t.lineNumber = f.lineNumber;
35165    t.columnNumber = f.columnNumber;
35166    return t;
35167  }
35168
35169  /**
35170   * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
35171   * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
35172   */
35173  function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) {
35174    /**
35175     * @param {string} qname
35176     * @param {string} value
35177     * @param {number} startIndex
35178     */
35179    function addAttribute(qname, value, startIndex) {
35180      if (el.attributeNames.hasOwnProperty(qname)) {
35181        errorHandler.fatalError('Attribute ' + qname + ' redefined');
35182      }
35183      el.addValue(qname,
35184      // @see https://www.w3.org/TR/xml/#AVNormalize
35185      // since the xmldom sax parser does not "interpret" DTD the following is not implemented:
35186      // - recursive replacement of (DTD) entity references
35187      // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
35188      value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer), startIndex);
35189    }
35190    var attrName;
35191    var value;
35192    var p = ++start;
35193    var s = S_TAG; //status
35194    while (true) {
35195      var c = source.charAt(p);
35196      switch (c) {
35197        case '=':
35198          if (s === S_ATTR) {
35199            //attrName
35200            attrName = source.slice(start, p);
35201            s = S_EQ;
35202          } else if (s === S_ATTR_SPACE) {
35203            s = S_EQ;
35204          } else {
35205            //fatalError: equal must after attrName or space after attrName
35206            throw new Error('attribute equal must after attrName'); // No known test case
35207          }
35208
35209          break;
35210        case '\'':
35211        case '"':
35212          if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
35213          ) {
35214            //equal
35215            if (s === S_ATTR) {
35216              errorHandler.warning('attribute value must after "="');
35217              attrName = source.slice(start, p);
35218            }
35219            start = p + 1;
35220            p = source.indexOf(c, start);
35221            if (p > 0) {
35222              value = source.slice(start, p);
35223              addAttribute(attrName, value, start - 1);
35224              s = S_ATTR_END;
35225            } else {
35226              //fatalError: no end quot match
35227              throw new Error('attribute value no end \'' + c + '\' match');
35228            }
35229          } else if (s == S_ATTR_NOQUOT_VALUE) {
35230            value = source.slice(start, p);
35231            addAttribute(attrName, value, start);
35232            errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
35233            start = p + 1;
35234            s = S_ATTR_END;
35235          } else {
35236            //fatalError: no equal before
35237            throw new Error('attribute value must after "="'); // No known test case
35238          }
35239
35240          break;
35241        case '/':
35242          switch (s) {
35243            case S_TAG:
35244              el.setTagName(source.slice(start, p));
35245            case S_ATTR_END:
35246            case S_TAG_SPACE:
35247            case S_TAG_CLOSE:
35248              s = S_TAG_CLOSE;
35249              el.closed = true;
35250            case S_ATTR_NOQUOT_VALUE:
35251            case S_ATTR:
35252              break;
35253            case S_ATTR_SPACE:
35254              el.closed = true;
35255              break;
35256            //case S_EQ:
35257            default:
35258              throw new Error("attribute invalid close char('/')");
35259            // No known test case
35260          }
35261
35262          break;
35263        case '':
35264          //end document
35265          errorHandler.error('unexpected end of input');
35266          if (s == S_TAG) {
35267            el.setTagName(source.slice(start, p));
35268          }
35269          return p;
35270        case '>':
35271          switch (s) {
35272            case S_TAG:
35273              el.setTagName(source.slice(start, p));
35274            case S_ATTR_END:
35275            case S_TAG_SPACE:
35276            case S_TAG_CLOSE:
35277              break;
35278            //normal
35279            case S_ATTR_NOQUOT_VALUE: //Compatible state
35280            case S_ATTR:
35281              value = source.slice(start, p);
35282              if (value.slice(-1) === '/') {
35283                el.closed = true;
35284                value = value.slice(0, -1);
35285              }
35286            case S_ATTR_SPACE:
35287              if (s === S_ATTR_SPACE) {
35288                value = attrName;
35289              }
35290              if (s == S_ATTR_NOQUOT_VALUE) {
35291                errorHandler.warning('attribute "' + value + '" missed quot(")!');
35292                addAttribute(attrName, value, start);
35293              } else {
35294                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)) {
35295                  errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
35296                }
35297                addAttribute(value, value, start);
35298              }
35299              break;
35300            case S_EQ:
35301              throw new Error('attribute value missed!!');
35302          }
35303          //			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
35304          return p;
35305        /*xml space '\x20' | #x9 | #xD | #xA; */
35306        case '\u0080':
35307          c = ' ';
35308        default:
35309          if (c <= ' ') {
35310            //space
35311            switch (s) {
35312              case S_TAG:
35313                el.setTagName(source.slice(start, p)); //tagName
35314                s = S_TAG_SPACE;
35315                break;
35316              case S_ATTR:
35317                attrName = source.slice(start, p);
35318                s = S_ATTR_SPACE;
35319                break;
35320              case S_ATTR_NOQUOT_VALUE:
35321                var value = source.slice(start, p);
35322                errorHandler.warning('attribute "' + value + '" missed quot(")!!');
35323                addAttribute(attrName, value, start);
35324              case S_ATTR_END:
35325                s = S_TAG_SPACE;
35326                break;
35327              //case S_TAG_SPACE:
35328              //case S_EQ:
35329              //case S_ATTR_SPACE:
35330              //	void();break;
35331              //case S_TAG_CLOSE:
35332              //ignore warning
35333            }
35334          } else {
35335            //not space
35336            //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
35337            //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
35338            switch (s) {
35339              //case S_TAG:void();break;
35340              //case S_ATTR:void();break;
35341              //case S_ATTR_NOQUOT_VALUE:void();break;
35342              case S_ATTR_SPACE:
35343                el.tagName;
35344                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
35345                  errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
35346                }
35347                addAttribute(attrName, attrName, start);
35348                start = p;
35349                s = S_ATTR;
35350                break;
35351              case S_ATTR_END:
35352                errorHandler.warning('attribute space is required"' + attrName + '"!!');
35353              case S_TAG_SPACE:
35354                s = S_ATTR;
35355                start = p;
35356                break;
35357              case S_EQ:
35358                s = S_ATTR_NOQUOT_VALUE;
35359                start = p;
35360                break;
35361              case S_TAG_CLOSE:
35362                throw new Error("elements closed character '/' and '>' must be connected to");
35363            }
35364          }
35365      } //end outer switch
35366      //console.log('p++',p)
35367      p++;
35368    }
35369  }
35370  /**
35371   * @return true if has new namespace define
35372   */
35373  function appendElement$1(el, domBuilder, currentNSMap) {
35374    var tagName = el.tagName;
35375    var localNSMap = null;
35376    //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
35377    var i = el.length;
35378    while (i--) {
35379      var a = el[i];
35380      var qName = a.qName;
35381      var value = a.value;
35382      var nsp = qName.indexOf(':');
35383      if (nsp > 0) {
35384        var prefix = a.prefix = qName.slice(0, nsp);
35385        var localName = qName.slice(nsp + 1);
35386        var nsPrefix = prefix === 'xmlns' && localName;
35387      } else {
35388        localName = qName;
35389        prefix = null;
35390        nsPrefix = qName === 'xmlns' && '';
35391      }
35392      //can not set prefix,because prefix !== ''
35393      a.localName = localName;
35394      //prefix == null for no ns prefix attribute
35395      if (nsPrefix !== false) {
35396        //hack!!
35397        if (localNSMap == null) {
35398          localNSMap = {};
35399          //console.log(currentNSMap,0)
35400          _copy(currentNSMap, currentNSMap = {});
35401          //console.log(currentNSMap,1)
35402        }
35403
35404        currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
35405        a.uri = NAMESPACE$1.XMLNS;
35406        domBuilder.startPrefixMapping(nsPrefix, value);
35407      }
35408    }
35409    var i = el.length;
35410    while (i--) {
35411      a = el[i];
35412      var prefix = a.prefix;
35413      if (prefix) {
35414        //no prefix attribute has no namespace
35415        if (prefix === 'xml') {
35416          a.uri = NAMESPACE$1.XML;
35417        }
35418        if (prefix !== 'xmlns') {
35419          a.uri = currentNSMap[prefix || ''];
35420
35421          //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
35422        }
35423      }
35424    }
35425
35426    var nsp = tagName.indexOf(':');
35427    if (nsp > 0) {
35428      prefix = el.prefix = tagName.slice(0, nsp);
35429      localName = el.localName = tagName.slice(nsp + 1);
35430    } else {
35431      prefix = null; //important!!
35432      localName = el.localName = tagName;
35433    }
35434    //no prefix element has default namespace
35435    var ns = el.uri = currentNSMap[prefix || ''];
35436    domBuilder.startElement(ns, localName, tagName, el);
35437    //endPrefixMapping and startPrefixMapping have not any help for dom builder
35438    //localNSMap = null
35439    if (el.closed) {
35440      domBuilder.endElement(ns, localName, tagName);
35441      if (localNSMap) {
35442        for (prefix in localNSMap) {
35443          if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
35444            domBuilder.endPrefixMapping(prefix);
35445          }
35446        }
35447      }
35448    } else {
35449      el.currentNSMap = currentNSMap;
35450      el.localNSMap = localNSMap;
35451      //parseStack.push(el);
35452      return true;
35453    }
35454  }
35455  function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
35456    if (/^(?:script|textarea)$/i.test(tagName)) {
35457      var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd);
35458      var text = source.substring(elStartEnd + 1, elEndStart);
35459      if (/[&<]/.test(text)) {
35460        if (/^script$/i.test(tagName)) {
35461          //if(!/\]\]>/.test(text)){
35462          //lexHandler.startCDATA();
35463          domBuilder.characters(text, 0, text.length);
35464          //lexHandler.endCDATA();
35465          return elEndStart;
35466          //}
35467        } //}else{//text area
35468        text = text.replace(/&#?\w+;/g, entityReplacer);
35469        domBuilder.characters(text, 0, text.length);
35470        return elEndStart;
35471        //}
35472      }
35473    }
35474
35475    return elStartEnd + 1;
35476  }
35477  function fixSelfClosed(source, elStartEnd, tagName, closeMap) {
35478    //if(tagName in closeMap){
35479    var pos = closeMap[tagName];
35480    if (pos == null) {
35481      //console.log(tagName)
35482      pos = source.lastIndexOf('</' + tagName + '>');
35483      if (pos < elStartEnd) {
35484        //忘记闭合
35485        pos = source.lastIndexOf('</' + tagName);
35486      }
35487      closeMap[tagName] = pos;
35488    }
35489    return pos < elStartEnd;
35490    //}
35491  }
35492
35493  function _copy(source, target) {
35494    for (var n in source) {
35495      if (Object.prototype.hasOwnProperty.call(source, n)) {
35496        target[n] = source[n];
35497      }
35498    }
35499  }
35500  function parseDCC(source, start, domBuilder, errorHandler) {
35501    //sure start with '<!'
35502    var next = source.charAt(start + 2);
35503    switch (next) {
35504      case '-':
35505        if (source.charAt(start + 3) === '-') {
35506          var end = source.indexOf('-->', start + 4);
35507          //append comment source.substring(4,end)//<!--
35508          if (end > start) {
vendor: 14,774 bytes, lines 35509-35950
35509            domBuilder.comment(source, start + 4, end - start - 4);
35510            return end + 3;
35511          } else {
35512            errorHandler.error("Unclosed comment");
35513            return -1;
35514          }
35515        } else {
35516          //error
35517          return -1;
35518        }
35519      default:
35520        if (source.substr(start + 3, 6) == 'CDATA[') {
35521          var end = source.indexOf(']]>', start + 9);
35522          domBuilder.startCDATA();
35523          domBuilder.characters(source, start + 9, end - start - 9);
35524          domBuilder.endCDATA();
35525          return end + 3;
35526        }
35527        //<!DOCTYPE
35528        //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
35529        var matchs = split(source, start);
35530        var len = matchs.length;
35531        if (len > 1 && /!doctype/i.test(matchs[0][0])) {
35532          var name = matchs[1][0];
35533          var pubid = false;
35534          var sysid = false;
35535          if (len > 3) {
35536            if (/^public$/i.test(matchs[2][0])) {
35537              pubid = matchs[3][0];
35538              sysid = len > 4 && matchs[4][0];
35539            } else if (/^system$/i.test(matchs[2][0])) {
35540              sysid = matchs[3][0];
35541            }
35542          }
35543          var lastMatch = matchs[len - 1];
35544          domBuilder.startDTD(name, pubid, sysid);
35545          domBuilder.endDTD();
35546          return lastMatch.index + lastMatch[0].length;
35547        }
35548    }
35549    return -1;
35550  }
35551  function parseInstruction(source, start, domBuilder) {
35552    var end = source.indexOf('?>', start);
35553    if (end) {
35554      var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
35555      if (match) {
35556        match[0].length;
35557        domBuilder.processingInstruction(match[1], match[2]);
35558        return end + 2;
35559      } else {
35560        //error
35561        return -1;
35562      }
35563    }
35564    return -1;
35565  }
35566  function ElementAttributes() {
35567    this.attributeNames = {};
35568  }
35569  ElementAttributes.prototype = {
35570    setTagName: function (tagName) {
35571      if (!tagNamePattern.test(tagName)) {
35572        throw new Error('invalid tagName:' + tagName);
35573      }
35574      this.tagName = tagName;
35575    },
35576    addValue: function (qName, value, offset) {
35577      if (!tagNamePattern.test(qName)) {
35578        throw new Error('invalid attribute:' + qName);
35579      }
35580      this.attributeNames[qName] = this.length;
35581      this[this.length++] = {
35582        qName: qName,
35583        value: value,
35584        offset: offset
35585      };
35586    },
35587    length: 0,
35588    getLocalName: function (i) {
35589      return this[i].localName;
35590    },
35591    getLocator: function (i) {
35592      return this[i].locator;
35593    },
35594    getQName: function (i) {
35595      return this[i].qName;
35596    },
35597    getURI: function (i) {
35598      return this[i].uri;
35599    },
35600    getValue: function (i) {
35601      return this[i].value;
35602    }
35603    //	,getIndex:function(uri, localName)){
35604    //		if(localName){
35605    //
35606    //		}else{
35607    //			var qName = uri
35608    //		}
35609    //	},
35610    //	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
35611    //	getType:function(uri,localName){}
35612    //	getType:function(i){},
35613  };
35614
35615  function split(source, start) {
35616    var match;
35617    var buf = [];
35618    var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
35619    reg.lastIndex = start;
35620    reg.exec(source); //skip <
35621    while (match = reg.exec(source)) {
35622      buf.push(match);
35623      if (match[1]) return buf;
35624    }
35625  }
35626  var XMLReader_1 = XMLReader$1;
35627  var ParseError_1 = ParseError$1;
35628  var sax = {
35629    XMLReader: XMLReader_1,
35630    ParseError: ParseError_1
35631  };
35632
35633  var DOMImplementation = dom.DOMImplementation;
35634  var NAMESPACE = conventions.NAMESPACE;
35635  var ParseError = sax.ParseError;
35636  var XMLReader = sax.XMLReader;
35637
35638  /**
35639   * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
35640   *
35641   * > XML parsed entities are often stored in computer files which,
35642   * > for editing convenience, are organized into lines.
35643   * > These lines are typically separated by some combination
35644   * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
35645   * >
35646   * > To simplify the tasks of applications, the XML processor must behave
35647   * > as if it normalized all line breaks in external parsed entities (including the document entity)
35648   * > on input, before parsing, by translating all of the following to a single #xA character:
35649   * >
35650   * > 1. the two-character sequence #xD #xA
35651   * > 2. the two-character sequence #xD #x85
35652   * > 3. the single character #x85
35653   * > 4. the single character #x2028
35654   * > 5. any #xD character that is not immediately followed by #xA or #x85.
35655   *
35656   * @param {string} input
35657   * @returns {string}
35658   */
35659  function normalizeLineEndings(input) {
35660    return input.replace(/\r[\n\u0085]/g, '\n').replace(/[\r\u0085\u2028]/g, '\n');
35661  }
35662
35663  /**
35664   * @typedef Locator
35665   * @property {number} [columnNumber]
35666   * @property {number} [lineNumber]
35667   */
35668
35669  /**
35670   * @typedef DOMParserOptions
35671   * @property {DOMHandler} [domBuilder]
35672   * @property {Function} [errorHandler]
35673   * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
35674   * 						defaults to `normalizeLineEndings`
35675   * @property {Locator} [locator]
35676   * @property {Record<string, string>} [xmlns]
35677   *
35678   * @see normalizeLineEndings
35679   */
35680
35681  /**
35682   * The DOMParser interface provides the ability to parse XML or HTML source code
35683   * from a string into a DOM `Document`.
35684   *
35685   * _xmldom is different from the spec in that it allows an `options` parameter,
35686   * to override the default behavior._
35687   *
35688   * @param {DOMParserOptions} [options]
35689   * @constructor
35690   *
35691   * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
35692   * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
35693   */
35694  function DOMParser$1(options) {
35695    this.options = options || {
35696      locator: {}
35697    };
35698  }
35699  DOMParser$1.prototype.parseFromString = function (source, mimeType) {
35700    var options = this.options;
35701    var sax = new XMLReader();
35702    var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler
35703    var errorHandler = options.errorHandler;
35704    var locator = options.locator;
35705    var defaultNSMap = options.xmlns || {};
35706    var isHTML = /\/x?html?$/.test(mimeType); //mimeType.toLowerCase().indexOf('html') > -1;
35707    var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
35708    if (locator) {
35709      domBuilder.setDocumentLocator(locator);
35710    }
35711    sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator);
35712    sax.domBuilder = options.domBuilder || domBuilder;
35713    if (isHTML) {
35714      defaultNSMap[''] = NAMESPACE.HTML;
35715    }
35716    defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
35717    var normalize = options.normalizeLineEndings || normalizeLineEndings;
35718    if (source && typeof source === 'string') {
35719      sax.parse(normalize(source), defaultNSMap, entityMap);
35720    } else {
35721      sax.errorHandler.error('invalid doc source');
35722    }
35723    return domBuilder.doc;
35724  };
35725  function buildErrorHandler(errorImpl, domBuilder, locator) {
35726    if (!errorImpl) {
35727      if (domBuilder instanceof DOMHandler) {
35728        return domBuilder;
35729      }
35730      errorImpl = domBuilder;
35731    }
35732    var errorHandler = {};
35733    var isCallback = errorImpl instanceof Function;
35734    locator = locator || {};
35735    function build(key) {
35736      var fn = errorImpl[key];
35737      if (!fn && isCallback) {
35738        fn = errorImpl.length == 2 ? function (msg) {
35739          errorImpl(key, msg);
35740        } : errorImpl;
35741      }
35742      errorHandler[key] = fn && function (msg) {
35743        fn('[xmldom ' + key + ']\t' + msg + _locator(locator));
35744      } || function () {};
35745    }
35746    build('warning');
35747    build('error');
35748    build('fatalError');
35749    return errorHandler;
35750  }
35751
35752  //console.log('#\n\n\n\n\n\n\n####')
35753  /**
35754   * +ContentHandler+ErrorHandler
35755   * +LexicalHandler+EntityResolver2
35756   * -DeclHandler-DTDHandler
35757   *
35758   * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
35759   * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
35760   * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
35761   */
35762  function DOMHandler() {
35763    this.cdata = false;
35764  }
35765  function position(locator, node) {
35766    node.lineNumber = locator.lineNumber;
35767    node.columnNumber = locator.columnNumber;
35768  }
35769  /**
35770   * @see org.xml.sax.ContentHandler#startDocument
35771   * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
35772   */
35773  DOMHandler.prototype = {
35774    startDocument: function () {
35775      this.doc = new DOMImplementation().createDocument(null, null, null);
35776      if (this.locator) {
35777        this.doc.documentURI = this.locator.systemId;
35778      }
35779    },
35780    startElement: function (namespaceURI, localName, qName, attrs) {
35781      var doc = this.doc;
35782      var el = doc.createElementNS(namespaceURI, qName || localName);
35783      var len = attrs.length;
35784      appendElement(this, el);
35785      this.currentElement = el;
35786      this.locator && position(this.locator, el);
35787      for (var i = 0; i < len; i++) {
35788        var namespaceURI = attrs.getURI(i);
35789        var value = attrs.getValue(i);
35790        var qName = attrs.getQName(i);
35791        var attr = doc.createAttributeNS(namespaceURI, qName);
35792        this.locator && position(attrs.getLocator(i), attr);
35793        attr.value = attr.nodeValue = value;
35794        el.setAttributeNode(attr);
35795      }
35796    },
35797    endElement: function (namespaceURI, localName, qName) {
35798      var current = this.currentElement;
35799      current.tagName;
35800      this.currentElement = current.parentNode;
35801    },
35802    startPrefixMapping: function (prefix, uri) {},
35803    endPrefixMapping: function (prefix) {},
35804    processingInstruction: function (target, data) {
35805      var ins = this.doc.createProcessingInstruction(target, data);
35806      this.locator && position(this.locator, ins);
35807      appendElement(this, ins);
35808    },
35809    ignorableWhitespace: function (ch, start, length) {},
35810    characters: function (chars, start, length) {
35811      chars = _toString.apply(this, arguments);
35812      //console.log(chars)
35813      if (chars) {
35814        if (this.cdata) {
35815          var charNode = this.doc.createCDATASection(chars);
35816        } else {
35817          var charNode = this.doc.createTextNode(chars);
35818        }
35819        if (this.currentElement) {
35820          this.currentElement.appendChild(charNode);
35821        } else if (/^\s*$/.test(chars)) {
35822          this.doc.appendChild(charNode);
35823          //process xml
35824        }
35825
35826        this.locator && position(this.locator, charNode);
35827      }
35828    },
35829    skippedEntity: function (name) {},
35830    endDocument: function () {
35831      this.doc.normalize();
35832    },
35833    setDocumentLocator: function (locator) {
35834      if (this.locator = locator) {
35835        // && !('lineNumber' in locator)){
35836        locator.lineNumber = 0;
35837      }
35838    },
35839    //LexicalHandler
35840    comment: function (chars, start, length) {
35841      chars = _toString.apply(this, arguments);
35842      var comm = this.doc.createComment(chars);
35843      this.locator && position(this.locator, comm);
35844      appendElement(this, comm);
35845    },
35846    startCDATA: function () {
35847      //used in characters() methods
35848      this.cdata = true;
35849    },
35850    endCDATA: function () {
35851      this.cdata = false;
35852    },
35853    startDTD: function (name, publicId, systemId) {
35854      var impl = this.doc.implementation;
35855      if (impl && impl.createDocumentType) {
35856        var dt = impl.createDocumentType(name, publicId, systemId);
35857        this.locator && position(this.locator, dt);
35858        appendElement(this, dt);
35859        this.doc.doctype = dt;
35860      }
35861    },
35862    /**
35863     * @see org.xml.sax.ErrorHandler
35864     * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
35865     */
35866    warning: function (error) {
35867      console.warn('[xmldom warning]\t' + error, _locator(this.locator));
35868    },
35869    error: function (error) {
35870      console.error('[xmldom error]\t' + error, _locator(this.locator));
35871    },
35872    fatalError: function (error) {
35873      throw new ParseError(error, this.locator);
35874    }
35875  };
35876  function _locator(l) {
35877    if (l) {
35878      return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']';
35879    }
35880  }
35881  function _toString(chars, start, length) {
35882    if (typeof chars == 'string') {
35883      return chars.substr(start, length);
35884    } else {
35885      //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
35886      if (chars.length >= start + length || start) {
35887        return new java.lang.String(chars, start, length) + '';
35888      }
35889      return chars;
35890    }
35891  }
35892
35893  /*
35894   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
35895   * used method of org.xml.sax.ext.LexicalHandler:
35896   *  #comment(chars, start, length)
35897   *  #startCDATA()
35898   *  #endCDATA()
35899   *  #startDTD(name, publicId, systemId)
35900   *
35901   *
35902   * IGNORED method of org.xml.sax.ext.LexicalHandler:
35903   *  #endDTD()
35904   *  #startEntity(name)
35905   *  #endEntity(name)
35906   *
35907   *
35908   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
35909   * IGNORED method of org.xml.sax.ext.DeclHandler
35910   * 	#attributeDecl(eName, aName, type, mode, value)
35911   *  #elementDecl(name, model)
35912   *  #externalEntityDecl(name, publicId, systemId)
35913   *  #internalEntityDecl(name, value)
35914   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
35915   * IGNORED method of org.xml.sax.EntityResolver2
35916   *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
35917   *  #resolveEntity(publicId, systemId)
35918   *  #getExternalSubset(name, baseURI)
35919   * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
35920   * IGNORED method of org.xml.sax.DTDHandler
35921   *  #notationDecl(name, publicId, systemId) {};
35922   *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
35923   */
35924  "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) {
35925    DOMHandler.prototype[key] = function () {
35926      return null;
35927    };
35928  });
35929
35930  /* 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 */
35931  function appendElement(hander, node) {
35932    if (!hander.currentElement) {
35933      hander.doc.appendChild(node);
35934    } else {
35935      hander.currentElement.appendChild(node);
35936    }
35937  } //appendChild and setAttributeNS are preformance key
35938
35939  var __DOMHandler = DOMHandler;
35940  var normalizeLineEndings_1 = normalizeLineEndings;
35941  var DOMParser_1 = DOMParser$1;
35942  var domParser = {
35943    __DOMHandler: __DOMHandler,
35944    normalizeLineEndings: normalizeLineEndings_1,
35945    DOMParser: DOMParser_1
35946  };
35947
35948  var DOMParser = domParser.DOMParser;
35949
35950  /*! @name mpd-parser @version 1.3
vendor: 16,826 bytes, lines 35950-36454
35950.0 @license Apache-2.0 */
35951  const isObject = obj => {
35952    return !!obj && typeof obj === 'object';
35953  };
35954  const merge$1 = (...objects) => {
35955    return objects.reduce((result, source) => {
35956      if (typeof source !== 'object') {
35957        return result;
35958      }
35959      Object.keys(source).forEach(key => {
35960        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
35961          result[key] = result[key].concat(source[key]);
35962        } else if (isObject(result[key]) && isObject(source[key])) {
35963          result[key] = merge$1(result[key], source[key]);
35964        } else {
35965          result[key] = source[key];
35966        }
35967      });
35968      return result;
35969    }, {});
35970  };
35971  const values = o => Object.keys(o).map(k => o[k]);
35972  const range = (start, end) => {
35973    const result = [];
35974    for (let i = start; i < end; i++) {
35975      result.push(i);
35976    }
35977    return result;
35978  };
35979  const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
35980  const from = list => {
35981    if (!list.length) {
35982      return [];
35983    }
35984    const result = [];
35985    for (let i = 0; i < list.length; i++) {
35986      result.push(list[i]);
35987    }
35988    return result;
35989  };
35990  const findIndexes = (l, key) => l.reduce((a, e, i) => {
35991    if (e[key]) {
35992      a.push(i);
35993    }
35994    return a;
35995  }, []);
35996  /**
35997   * Returns a union of the included lists provided each element can be identified by a key.
35998   *
35999   * @param {Array} list - list of lists to get the union of
36000   * @param {Function} keyFunction - the function to use as a key for each element
36001   *
36002   * @return {Array} the union of the arrays
36003   */
36004
36005  const union = (lists, keyFunction) => {
36006    return values(lists.reduce((acc, list) => {
36007      list.forEach(el => {
36008        acc[keyFunction(el)] = el;
36009      });
36010      return acc;
36011    }, {}));
36012  };
36013  var errors = {
36014    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
36015    INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING',
36016    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
36017    DASH_INVALID_XML: 'DASH_INVALID_XML',
36018    NO_BASE_URL: 'NO_BASE_URL',
36019    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
36020    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
36021    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
36022  };
36023
36024  /**
36025   * @typedef {Object} SingleUri
36026   * @property {string} uri - relative location of segment
36027   * @property {string} resolvedUri - resolved location of segment
36028   * @property {Object} byterange - Object containing information on how to make byte range
36029   *   requests following byte-range-spec per RFC2616.
36030   * @property {String} byterange.length - length of range request
36031   * @property {String} byterange.offset - byte offset of range request
36032   *
36033   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
36034   */
36035
36036  /**
36037   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
36038   * that conforms to how m3u8-parser is structured
36039   *
36040   * @see https://github.com/videojs/m3u8-parser
36041   *
36042   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
36043   * @param {string} source - source url for segment
36044   * @param {string} range - optional range used for range calls,
36045   *   follows  RFC 2616, Clause 14.35.1
36046   * @return {SingleUri} full segment information transformed into a format similar
36047   *   to m3u8-parser
36048   */
36049
36050  const urlTypeToSegment = ({
36051    baseUrl = '',
36052    source = '',
36053    range = '',
36054    indexRange = ''
36055  }) => {
36056    const segment = {
36057      uri: source,
36058      resolvedUri: resolveUrl$1(baseUrl || '', source)
36059    };
36060    if (range || indexRange) {
36061      const rangeStr = range ? range : indexRange;
36062      const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
36063
36064      let startRange = window.BigInt ? window.BigInt(ranges[0]) : parseInt(ranges[0], 10);
36065      let endRange = window.BigInt ? window.BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
36066
36067      if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
36068        startRange = Number(startRange);
36069      }
36070      if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
36071        endRange = Number(endRange);
36072      }
36073      let length;
36074      if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
36075        length = window.BigInt(endRange) - window.BigInt(startRange) + window.BigInt(1);
36076      } else {
36077        length = endRange - startRange + 1;
36078      }
36079      if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
36080        length = Number(length);
36081      } // byterange should be inclusive according to
36082      // RFC 2616, Clause 14.35.1
36083
36084      segment.byterange = {
36085        length,
36086        offset: startRange
36087      };
36088    }
36089    return segment;
36090  };
36091  const byteRangeToString = byterange => {
36092    // `endRange` is one less than `offset + length` because the HTTP range
36093    // header uses inclusive ranges
36094    let endRange;
36095    if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
36096      endRange = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
36097    } else {
36098      endRange = byterange.offset + byterange.length - 1;
36099    }
36100    return `${byterange.offset}-${endRange}`;
36101  };
36102
36103  /**
36104   * parse the end number attribue that can be a string
36105   * number, or undefined.
36106   *
36107   * @param {string|number|undefined} endNumber
36108   *        The end number attribute.
36109   *
36110   * @return {number|null}
36111   *          The result of parsing the end number.
36112   */
36113
36114  const parseEndNumber = endNumber => {
36115    if (endNumber && typeof endNumber !== 'number') {
36116      endNumber = parseInt(endNumber, 10);
36117    }
36118    if (isNaN(endNumber)) {
36119      return null;
36120    }
36121    return endNumber;
36122  };
36123  /**
36124   * Functions for calculating the range of available segments in static and dynamic
36125   * manifests.
36126   */
36127
36128  const segmentRange = {
36129    /**
36130     * Returns the entire range of available segments for a static MPD
36131     *
36132     * @param {Object} attributes
36133     *        Inheritied MPD attributes
36134     * @return {{ start: number, end: number }}
36135     *         The start and end numbers for available segments
36136     */
36137    static(attributes) {
36138      const {
36139        duration,
36140        timescale = 1,
36141        sourceDuration,
36142        periodDuration
36143      } = attributes;
36144      const endNumber = parseEndNumber(attributes.endNumber);
36145      const segmentDuration = duration / timescale;
36146      if (typeof endNumber === 'number') {
36147        return {
36148          start: 0,
36149          end: endNumber
36150        };
36151      }
36152      if (typeof periodDuration === 'number') {
36153        return {
36154          start: 0,
36155          end: periodDuration / segmentDuration
36156        };
36157      }
36158      return {
36159        start: 0,
36160        end: sourceDuration / segmentDuration
36161      };
36162    },
36163    /**
36164     * Returns the current live window range of available segments for a dynamic MPD
36165     *
36166     * @param {Object} attributes
36167     *        Inheritied MPD attributes
36168     * @return {{ start: number, end: number }}
36169     *         The start and end numbers for available segments
36170     */
36171    dynamic(attributes) {
36172      const {
36173        NOW,
36174        clientOffset,
36175        availabilityStartTime,
36176        timescale = 1,
36177        duration,
36178        periodStart = 0,
36179        minimumUpdatePeriod = 0,
36180        timeShiftBufferDepth = Infinity
36181      } = attributes;
36182      const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
36183      // after retrieving UTC server time.
36184
36185      const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
36186      // Convert the period start time to EPOCH.
36187
36188      const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
36189
36190      const periodEndWC = now + minimumUpdatePeriod;
36191      const periodDuration = periodEndWC - periodStartWC;
36192      const segmentCount = Math.ceil(periodDuration * timescale / duration);
36193      const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
36194      const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
36195      return {
36196        start: Math.max(0, availableStart),
36197        end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
36198      };
36199    }
36200  };
36201  /**
36202   * Maps a range of numbers to objects with information needed to build the corresponding
36203   * segment list
36204   *
36205   * @name toSegmentsCallback
36206   * @function
36207   * @param {number} number
36208   *        Number of the segment
36209   * @param {number} index
36210   *        Index of the number in the range list
36211   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
36212   *         Object with segment timing and duration info
36213   */
36214
36215  /**
36216   * Returns a callback for Array.prototype.map for mapping a range of numbers to
36217   * information needed to build the segment list.
36218   *
36219   * @param {Object} attributes
36220   *        Inherited MPD attributes
36221   * @return {toSegmentsCallback}
36222   *         Callback map function
36223   */
36224
36225  const toSegments = attributes => number => {
36226    const {
36227      duration,
36228      timescale = 1,
36229      periodStart,
36230      startNumber = 1
36231    } = attributes;
36232    return {
36233      number: startNumber + number,
36234      duration: duration / timescale,
36235      timeline: periodStart,
36236      time: number * duration
36237    };
36238  };
36239  /**
36240   * Returns a list of objects containing segment timing and duration info used for
36241   * building the list of segments. This uses the @duration attribute specified
36242   * in the MPD manifest to derive the range of segments.
36243   *
36244   * @param {Object} attributes
36245   *        Inherited MPD attributes
36246   * @return {{number: number, duration: number, time: number, timeline: number}[]}
36247   *         List of Objects with segment timing and duration info
36248   */
36249
36250  const parseByDuration = attributes => {
36251    const {
36252      type,
36253      duration,
36254      timescale = 1,
36255      periodDuration,
36256      sourceDuration
36257    } = attributes;
36258    const {
36259      start,
36260      end
36261    } = segmentRange[type](attributes);
36262    const segments = range(start, end).map(toSegments(attributes));
36263    if (type === 'static') {
36264      const index = segments.length - 1; // section is either a period or the full source
36265
36266      const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
36267
36268      segments[index].duration = sectionDuration - duration / timescale * index;
36269    }
36270    return segments;
36271  };
36272
36273  /**
36274   * Translates SegmentBase into a set of segments.
36275   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
36276   * node should be translated into segment.
36277   *
36278   * @param {Object} attributes
36279   *   Object containing all inherited attributes from parent elements with attribute
36280   *   names as keys
36281   * @return {Object.<Array>} list of segments
36282   */
36283
36284  const segmentsFromBase = attributes => {
36285    const {
36286      baseUrl,
36287      initialization = {},
36288      sourceDuration,
36289      indexRange = '',
36290      periodStart,
36291      presentationTime,
36292      number = 0,
36293      duration
36294    } = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
36295
36296    if (!baseUrl) {
36297      throw new Error(errors.NO_BASE_URL);
36298    }
36299    const initSegment = urlTypeToSegment({
36300      baseUrl,
36301      source: initialization.sourceURL,
36302      range: initialization.range
36303    });
36304    const segment = urlTypeToSegment({
36305      baseUrl,
36306      source: baseUrl,
36307      indexRange
36308    });
36309    segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
36310    // (since SegmentBase is only for one total segment)
36311
36312    if (duration) {
36313      const segmentTimeInfo = parseByDuration(attributes);
36314      if (segmentTimeInfo.length) {
36315        segment.duration = segmentTimeInfo[0].duration;
36316        segment.timeline = segmentTimeInfo[0].timeline;
36317      }
36318    } else if (sourceDuration) {
36319      segment.duration = sourceDuration;
36320      segment.timeline = periodStart;
36321    } // If presentation time is provided, these segments are being generated by SIDX
36322    // references, and should use the time provided. For the general case of SegmentBase,
36323    // there should only be one segment in the period, so its presentation time is the same
36324    // as its period start.
36325
36326    segment.presentationTime = presentationTime || periodStart;
36327    segment.number = number;
36328    return [segment];
36329  };
36330  /**
36331   * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
36332   * according to the sidx information given.
36333   *
36334   * playlist.sidx has metadadata about the sidx where-as the sidx param
36335   * is the parsed sidx box itself.
36336   *
36337   * @param {Object} playlist the playlist to update the sidx information for
36338   * @param {Object} sidx the parsed sidx box
36339   * @return {Object} the playlist object with the updated sidx information
36340   */
36341
36342  const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => {
36343    // Retain init segment information
36344    const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
36345
36346    const sourceDuration = playlist.sidx.duration; // Retain source timeline
36347
36348    const timeline = playlist.timeline || 0;
36349    const sidxByteRange = playlist.sidx.byterange;
36350    const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
36351
36352    const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
36353
36354    const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
36355    const segments = [];
36356    const type = playlist.endList ? 'static' : 'dynamic';
36357    const periodStart = playlist.sidx.timeline;
36358    let presentationTime = periodStart;
36359    let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
36360
36361    let startIndex; // eslint-disable-next-line
36362
36363    if (typeof sidx.firstOffset === 'bigint') {
36364      startIndex = window.BigInt(sidxEnd) + sidx.firstOffset;
36365    } else {
36366      startIndex = sidxEnd + sidx.firstOffset;
36367    }
36368    for (let i = 0; i < mediaReferences.length; i++) {
36369      const reference = sidx.references[i]; // size of the referenced (sub)segment
36370
36371      const size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
36372      // this will be converted to seconds when generating segments
36373
36374      const duration = reference.subsegmentDuration; // should be an inclusive range
36375
36376      let endIndex; // eslint-disable-next-line
36377
36378      if (typeof startIndex === 'bigint') {
36379        endIndex = startIndex + window.BigInt(size) - window.BigInt(1);
36380      } else {
36381        endIndex = startIndex + size - 1;
36382      }
36383      const indexRange = `${startIndex}-${endIndex}`;
36384      const attributes = {
36385        baseUrl,
36386        timescale,
36387        timeline,
36388        periodStart,
36389        presentationTime,
36390        number,
36391        duration,
36392        sourceDuration,
36393        indexRange,
36394        type
36395      };
36396      const segment = segmentsFromBase(attributes)[0];
36397      if (initSegment) {
36398        segment.map = initSegment;
36399      }
36400      segments.push(segment);
36401      if (typeof startIndex === 'bigint') {
36402        startIndex += window.BigInt(size);
36403      } else {
36404        startIndex += size;
36405      }
36406      presentationTime += duration / timescale;
36407      number++;
36408    }
36409    playlist.segments = segments;
36410    return playlist;
36411  };
36412  const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
36413
36414  const TIME_FUDGE = 1 / 60;
36415  /**
36416   * Given a list of timelineStarts, combines, dedupes, and sorts them.
36417   *
36418   * @param {TimelineStart[]} timelineStarts - list of timeline starts
36419   *
36420   * @return {TimelineStart[]} the combined and deduped timeline starts
36421   */
36422
36423  const getUniqueTimelineStarts = timelineStarts => {
36424    return union(timelineStarts, ({
36425      timeline
36426    }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
36427  };
36428  /**
36429   * Finds the playlist with the matching NAME attribute.
36430   *
36431   * @param {Array} playlists - playlists to search through
36432   * @param {string} name - the NAME attribute to search for
36433   *
36434   * @return {Object|null} the matching playlist object, or null
36435   */
36436
36437  const findPlaylistWithName = (playlists, name) => {
36438    for (let i = 0; i < playlists.length; i++) {
36439      if (playlists[i].attributes.NAME === name) {
36440        return playlists[i];
36441      }
36442    }
36443    return null;
36444  };
36445  /**
36446   * Gets a flattened array of media group playlists.
36447   *
36448   * @param {Object} manifest - the main manifest object
36449   *
36450   * @return {Array} the media group playlists
36451   */
36452
36453  const getMediaGroupPlaylists = manifest => {
36454    let mediaGroupPlaylists = [];
vendor: 1,489 bytes, lines 36455-36496
36455    forEachMediaGroup$1(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
36456      mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
36457    });
36458    return mediaGroupPlaylists;
36459  };
36460  /**
36461   * Updates the playlist's media sequence numbers.
36462   *
36463   * @param {Object} config - options object
36464   * @param {Object} config.playlist - the playlist to update
36465   * @param {number} config.mediaSequence - the mediaSequence number to start with
36466   */
36467
36468  const updateMediaSequenceForPlaylist = ({
36469    playlist,
36470    mediaSequence
36471  }) => {
36472    playlist.mediaSequence = mediaSequence;
36473    playlist.segments.forEach((segment, index) => {
36474      segment.number = playlist.mediaSequence + index;
36475    });
36476  };
36477  /**
36478   * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
36479   * and a complete list of timeline starts.
36480   *
36481   * If no matching playlist is found, only the discontinuity sequence number of the playlist
36482   * will be updated.
36483   *
36484   * Since early available timelines are not supported, at least one segment must be present.
36485   *
36486   * @param {Object} config - options object
36487   * @param {Object[]} oldPlaylists - the old playlists to use as a reference
36488   * @param {Object[]} newPlaylists - the new playlists to update
36489   * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
36490   */
36491
36492  const updateSequenceNumbers = ({
36493    oldPlaylists,
36494    newPlaylists,
36495    timelineStarts
36496  }) => {
vendor: 14,798 bytes, lines 36497-36831
36497    newPlaylists.forEach(playlist => {
36498      playlist.discontinuitySequence = timelineStarts.findIndex(function ({
36499        timeline
36500      }) {
36501        return timeline === playlist.timeline;
36502      }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
36503      // (see ISO_23009-1-2012 5.3.5.2).
36504      //
36505      // If the same Representation existed in a prior Period, it will retain the same NAME.
36506
36507      const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
36508      if (!oldPlaylist) {
36509        // Since this is a new playlist, the media sequence values can start from 0 without
36510        // consequence.
36511        return;
36512      } // TODO better support for live SIDX
36513      //
36514      // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
36515      // This is evident by a playlist only having a single SIDX reference. In a multiperiod
36516      // playlist there would need to be multiple SIDX references. In addition, live SIDX is
36517      // not supported when the SIDX properties change on refreshes.
36518      //
36519      // In the future, if support needs to be added, the merging logic here can be called
36520      // after SIDX references are resolved. For now, exit early to prevent exceptions being
36521      // thrown due to undefined references.
36522
36523      if (playlist.sidx) {
36524        return;
36525      } // Since we don't yet support early available timelines, we don't need to support
36526      // playlists with no segments.
36527
36528      const firstNewSegment = playlist.segments[0];
36529      const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) {
36530        return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
36531      }); // No matching segment from the old playlist means the entire playlist was refreshed.
36532      // In this case the media sequence should account for this update, and the new segments
36533      // should be marked as discontinuous from the prior content, since the last prior
36534      // timeline was removed.
36535
36536      if (oldMatchingSegmentIndex === -1) {
36537        updateMediaSequenceForPlaylist({
36538          playlist,
36539          mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
36540        });
36541        playlist.segments[0].discontinuity = true;
36542        playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
36543        //
36544        // If the new playlist's timeline is the same as the last seen segment's timeline,
36545        // then a discontinuity can be added to identify that there's potentially missing
36546        // content. If there's no missing content, the discontinuity should still be rather
36547        // harmless. It's possible that if segment durations are accurate enough, that the
36548        // existence of a gap can be determined using the presentation times and durations,
36549        // but if the segment timing info is off, it may introduce more problems than simply
36550        // adding the discontinuity.
36551        //
36552        // If the new playlist's timeline is different from the last seen segment's timeline,
36553        // then a discontinuity can be added to identify that this is the first seen segment
36554        // of a new timeline. However, the logic at the start of this function that
36555        // determined the disconinuity sequence by timeline index is now off by one (the
36556        // discontinuity of the newest timeline hasn't yet fallen off the manifest...since
36557        // we added it), so the disconinuity sequence must be decremented.
36558        //
36559        // A period may also have a duration of zero, so the case of no segments is handled
36560        // here even though we don't yet support early available periods.
36561
36562        if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
36563          playlist.discontinuitySequence--;
36564        }
36565        return;
36566      } // If the first segment matched with a prior segment on a discontinuity (it's matching
36567      // on the first segment of a period), then the discontinuitySequence shouldn't be the
36568      // timeline's matching one, but instead should be the one prior, and the first segment
36569      // of the new manifest should be marked with a discontinuity.
36570      //
36571      // The reason for this special case is that discontinuity sequence shows how many
36572      // discontinuities have fallen off of the playlist, and discontinuities are marked on
36573      // the first segment of a new "timeline." Because of this, while DASH will retain that
36574      // Period while the "timeline" exists, HLS keeps track of it via the discontinuity
36575      // sequence, and that first segment is an indicator, but can be removed before that
36576      // timeline is gone.
36577
36578      const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
36579      if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
36580        firstNewSegment.discontinuity = true;
36581        playlist.discontinuityStarts.unshift(0);
36582        playlist.discontinuitySequence--;
36583      }
36584      updateMediaSequenceForPlaylist({
36585        playlist,
36586        mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
36587      });
36588    });
36589  };
36590  /**
36591   * Given an old parsed manifest object and a new parsed manifest object, updates the
36592   * sequence and timing values within the new manifest to ensure that it lines up with the
36593   * old.
36594   *
36595   * @param {Array} oldManifest - the old main manifest object
36596   * @param {Array} newManifest - the new main manifest object
36597   *
36598   * @return {Object} the updated new manifest object
36599   */
36600
36601  const positionManifestOnTimeline = ({
36602    oldManifest,
36603    newManifest
36604  }) => {
36605    // Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
36606    //
36607    // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
36608    //
36609    // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
36610    //
36611    // Because of this change, and the difficulty of supporting periods with changing start
36612    // times, periods with changing start times are not supported. This makes the logic much
36613    // simpler, since periods with the same start time can be considerred the same period
36614    // across refreshes.
36615    //
36616    // To give an example as to the difficulty of handling periods where the start time may
36617    // change, if a single period manifest is refreshed with another manifest with a single
36618    // period, and both the start and end times are increased, then the only way to determine
36619    // if it's a new period or an old one that has changed is to look through the segments of
36620    // each playlist and determine the presentation time bounds to find a match. In addition,
36621    // if the period start changed to exceed the old period end, then there would be no
36622    // match, and it would not be possible to determine whether the refreshed period is a new
36623    // one or the old one.
36624    const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
36625    const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
36626    // there's a "memory leak" in that it will never stop growing, in reality, only a couple
36627    // of properties are saved for each seen Period. Even long running live streams won't
36628    // generate too many Periods, unless the stream is watched for decades. In the future,
36629    // this can be optimized by mapping to discontinuity sequence numbers for each timeline,
36630    // but it may not become an issue, and the additional info can be useful for debugging.
36631
36632    newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
36633    updateSequenceNumbers({
36634      oldPlaylists,
36635      newPlaylists,
36636      timelineStarts: newManifest.timelineStarts
36637    });
36638    return newManifest;
36639  };
36640  const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
36641  const mergeDiscontiguousPlaylists = playlists => {
36642    // Break out playlists into groups based on their baseUrl
36643    const playlistsByBaseUrl = playlists.reduce(function (acc, cur) {
36644      if (!acc[cur.attributes.baseUrl]) {
36645        acc[cur.attributes.baseUrl] = [];
36646      }
36647      acc[cur.attributes.baseUrl].push(cur);
36648      return acc;
36649    }, {});
36650    let allPlaylists = [];
36651    Object.values(playlistsByBaseUrl).forEach(playlistGroup => {
36652      const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => {
36653        // assuming playlist IDs are the same across periods
36654        // TODO: handle multiperiod where representation sets are not the same
36655        // across periods
36656        const name = playlist.attributes.id + (playlist.attributes.lang || '');
36657        if (!acc[name]) {
36658          // First Period
36659          acc[name] = playlist;
36660          acc[name].attributes.timelineStarts = [];
36661        } else {
36662          // Subsequent Periods
36663          if (playlist.segments) {
36664            // first segment of subsequent periods signal a discontinuity
36665            if (playlist.segments[0]) {
36666              playlist.segments[0].discontinuity = true;
36667            }
36668            acc[name].segments.push(...playlist.segments);
36669          } // bubble up contentProtection, this assumes all DRM content
36670          // has the same contentProtection
36671
36672          if (playlist.attributes.contentProtection) {
36673            acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
36674          }
36675        }
36676        acc[name].attributes.timelineStarts.push({
36677          // Although they represent the same number, it's important to have both to make it
36678          // compatible with HLS potentially having a similar attribute.
36679          start: playlist.attributes.periodStart,
36680          timeline: playlist.attributes.periodStart
36681        });
36682        return acc;
36683      }, {}));
36684      allPlaylists = allPlaylists.concat(mergedPlaylists);
36685    });
36686    return allPlaylists.map(playlist => {
36687      playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
36688      return playlist;
36689    });
36690  };
36691  const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
36692    const sidxKey = generateSidxKey(playlist.sidx);
36693    const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
36694    if (sidxMatch) {
36695      addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
36696    }
36697    return playlist;
36698  };
36699  const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
36700    if (!Object.keys(sidxMapping).length) {
36701      return playlists;
36702    }
36703    for (const i in playlists) {
36704      playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
36705    }
36706    return playlists;
36707  };
36708  const formatAudioPlaylist = ({
36709    attributes,
36710    segments,
36711    sidx,
36712    mediaSequence,
36713    discontinuitySequence,
36714    discontinuityStarts
36715  }, isAudioOnly) => {
36716    const playlist = {
36717      attributes: {
36718        NAME: attributes.id,
36719        BANDWIDTH: attributes.bandwidth,
36720        CODECS: attributes.codecs,
36721        ['PROGRAM-ID']: 1
36722      },
36723      uri: '',
36724      endList: attributes.type === 'static',
36725      timeline: attributes.periodStart,
36726      resolvedUri: attributes.baseUrl || '',
36727      targetDuration: attributes.duration,
36728      discontinuitySequence,
36729      discontinuityStarts,
36730      timelineStarts: attributes.timelineStarts,
36731      mediaSequence,
36732      segments
36733    };
36734    if (attributes.contentProtection) {
36735      playlist.contentProtection = attributes.contentProtection;
36736    }
36737    if (attributes.serviceLocation) {
36738      playlist.attributes.serviceLocation = attributes.serviceLocation;
36739    }
36740    if (sidx) {
36741      playlist.sidx = sidx;
36742    }
36743    if (isAudioOnly) {
36744      playlist.attributes.AUDIO = 'audio';
36745      playlist.attributes.SUBTITLES = 'subs';
36746    }
36747    return playlist;
36748  };
36749  const formatVttPlaylist = ({
36750    attributes,
36751    segments,
36752    mediaSequence,
36753    discontinuityStarts,
36754    discontinuitySequence
36755  }) => {
36756    if (typeof segments === 'undefined') {
36757      // vtt tracks may use single file in BaseURL
36758      segments = [{
36759        uri: attributes.baseUrl,
36760        timeline: attributes.periodStart,
36761        resolvedUri: attributes.baseUrl || '',
36762        duration: attributes.sourceDuration,
36763        number: 0
36764      }]; // targetDuration should be the same duration as the only segment
36765
36766      attributes.duration = attributes.sourceDuration;
36767    }
36768    const m3u8Attributes = {
36769      NAME: attributes.id,
36770      BANDWIDTH: attributes.bandwidth,
36771      ['PROGRAM-ID']: 1
36772    };
36773    if (attributes.codecs) {
36774      m3u8Attributes.CODECS = attributes.codecs;
36775    }
36776    const vttPlaylist = {
36777      attributes: m3u8Attributes,
36778      uri: '',
36779      endList: attributes.type === 'static',
36780      timeline: attributes.periodStart,
36781      resolvedUri: attributes.baseUrl || '',
36782      targetDuration: attributes.duration,
36783      timelineStarts: attributes.timelineStarts,
36784      discontinuityStarts,
36785      discontinuitySequence,
36786      mediaSequence,
36787      segments
36788    };
36789    if (attributes.serviceLocation) {
36790      vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
36791    }
36792    return vttPlaylist;
36793  };
36794  const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
36795    let mainPlaylist;
36796    const formattedPlaylists = playlists.reduce((a, playlist) => {
36797      const role = playlist.attributes.role && playlist.attributes.role.value || '';
36798      const language = playlist.attributes.lang || '';
36799      let label = playlist.attributes.label || 'main';
36800      if (language && !playlist.attributes.label) {
36801        const roleLabel = role ? ` (${role})` : '';
36802        label = `${playlist.attributes.lang}${roleLabel}`;
36803      }
36804      if (!a[label]) {
36805        a[label] = {
36806          language,
36807          autoselect: true,
36808          default: role === 'main',
36809          playlists: [],
36810          uri: ''
36811        };
36812      }
36813      const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
36814      a[label].playlists.push(formatted);
36815      if (typeof mainPlaylist === 'undefined' && role === 'main') {
36816        mainPlaylist = playlist;
36817        mainPlaylist.default = true;
36818      }
36819      return a;
36820    }, {}); // if no playlists have role "main", mark the first as main
36821
36822    if (!mainPlaylist) {
36823      const firstLabel = Object.keys(formattedPlaylists)[0];
36824      formattedPlaylists[firstLabel].default = true;
36825    }
36826    return formattedPlaylists;
36827  };
36828  const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
36829    return playlists.reduce((a, playlist) => {
36830      const label = playlist.attributes.label || playlist.attributes.lang || 'text';
36831      
36831if (!a[label]) {
36832        a[label] = {
36833          language: label,
36834          default: false,
36835          autoselect: false,
36836          playlists: [],
36837          uri: ''
36838        };
36839      }
36840      a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
36841      return a;
36842    }, {});
36843  };
36844  const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
36845    if (!svc) {
36846      return svcObj;
36847    }
vendor: 1,424 bytes, lines 36848-36900
36848    svc.forEach(service => {
36849      const {
36850        channel,
36851        language
36852      } = service;
36853      svcObj[language] = {
36854        autoselect: false,
36855        default: false,
36856        instreamId: channel,
36857        language
36858      };
36859      if (service.hasOwnProperty('aspectRatio')) {
36860        svcObj[language].aspectRatio = service.aspectRatio;
36861      }
36862      if (service.hasOwnProperty('easyReader')) {
36863        svcObj[language].easyReader = service.easyReader;
36864      }
36865      if (service.hasOwnProperty('3D')) {
36866        svcObj[language]['3D'] = service['3D'];
36867      }
36868    });
36869    return svcObj;
36870  }, {});
36871  const formatVideoPlaylist = ({
36872    attributes,
36873    segments,
36874    sidx,
36875    discontinuityStarts
36876  }) => {
36877    const playlist = {
36878      attributes: {
36879        NAME: attributes.id,
36880        AUDIO: 'audio',
36881        SUBTITLES: 'subs',
36882        RESOLUTION: {
36883          width: attributes.width,
36884          height: attributes.height
36885        },
36886        CODECS: attributes.codecs,
36887        BANDWIDTH: attributes.bandwidth,
36888        ['PROGRAM-ID']: 1
36889      },
36890      uri: '',
36891      endList: attributes.type === 'static',
36892      timeline: attributes.periodStart,
36893      resolvedUri: attributes.baseUrl || '',
36894      targetDuration: attributes.duration,
36895      discontinuityStarts,
36896      timelineStarts: attributes.timelineStarts,
36897      segments
36898    };
36899    if (attributes.frameRate) {
36900      playlist.attributes['FRAME-RATE'] = attributes.frameRate
vendor: 30,136 bytes, lines 36900-37760
36900;
36901    }
36902    if (attributes.contentProtection) {
36903      playlist.contentProtection = attributes.contentProtection;
36904    }
36905    if (attributes.serviceLocation) {
36906      playlist.attributes.serviceLocation = attributes.serviceLocation;
36907    }
36908    if (sidx) {
36909      playlist.sidx = sidx;
36910    }
36911    return playlist;
36912  };
36913  const videoOnly = ({
36914    attributes
36915  }) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
36916  const audioOnly = ({
36917    attributes
36918  }) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
36919  const vttOnly = ({
36920    attributes
36921  }) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
36922  /**
36923   * Contains start and timeline properties denoting a timeline start. For DASH, these will
36924   * be the same number.
36925   *
36926   * @typedef {Object} TimelineStart
36927   * @property {number} start - the start time of the timeline
36928   * @property {number} timeline - the timeline number
36929   */
36930
36931  /**
36932   * Adds appropriate media and discontinuity sequence values to the segments and playlists.
36933   *
36934   * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
36935   * DASH specific attribute used in constructing segment URI's from templates. However, from
36936   * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
36937   * value, which should start at the original media sequence value (or 0) and increment by 1
36938   * for each segment thereafter. Since DASH's `startNumber` values are independent per
36939   * period, it doesn't make sense to use it for `number`. Instead, assume everything starts
36940   * from a 0 mediaSequence value and increment from there.
36941   *
36942   * Note that VHS currently doesn't use the `number` property, but it can be helpful for
36943   * debugging and making sense of the manifest.
36944   *
36945   * For live playlists, to account for values increasing in manifests when periods are
36946   * removed on refreshes, merging logic should be used to update the numbers to their
36947   * appropriate values (to ensure they're sequential and increasing).
36948   *
36949   * @param {Object[]} playlists - the playlists to update
36950   * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
36951   */
36952
36953  const addMediaSequenceValues = (playlists, timelineStarts) => {
36954    // increment all segments sequentially
36955    playlists.forEach(playlist => {
36956      playlist.mediaSequence = 0;
36957      playlist.discontinuitySequence = timelineStarts.findIndex(function ({
36958        timeline
36959      }) {
36960        return timeline === playlist.timeline;
36961      });
36962      if (!playlist.segments) {
36963        return;
36964      }
36965      playlist.segments.forEach((segment, index) => {
36966        segment.number = index;
36967      });
36968    });
36969  };
36970  /**
36971   * Given a media group object, flattens all playlists within the media group into a single
36972   * array.
36973   *
36974   * @param {Object} mediaGroupObject - the media group object
36975   *
36976   * @return {Object[]}
36977   *         The media group playlists
36978   */
36979
36980  const flattenMediaGroupPlaylists = mediaGroupObject => {
36981    if (!mediaGroupObject) {
36982      return [];
36983    }
36984    return Object.keys(mediaGroupObject).reduce((acc, label) => {
36985      const labelContents = mediaGroupObject[label];
36986      return acc.concat(labelContents.playlists);
36987    }, []);
36988  };
36989  const toM3u8 = ({
36990    dashPlaylists,
36991    locations,
36992    contentSteering,
36993    sidxMapping = {},
36994    previousManifest,
36995    eventStream
36996  }) => {
36997    if (!dashPlaylists.length) {
36998      return {};
36999    } // grab all main manifest attributes
37000
37001    const {
37002      sourceDuration: duration,
37003      type,
37004      suggestedPresentationDelay,
37005      minimumUpdatePeriod
37006    } = dashPlaylists[0].attributes;
37007    const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
37008    const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
37009    const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
37010    const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean);
37011    const manifest = {
37012      allowCache: true,
37013      discontinuityStarts: [],
37014      segments: [],
37015      endList: true,
37016      mediaGroups: {
37017        AUDIO: {},
37018        VIDEO: {},
37019        ['CLOSED-CAPTIONS']: {},
37020        SUBTITLES: {}
37021      },
37022      uri: '',
37023      duration,
37024      playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
37025    };
37026    if (minimumUpdatePeriod >= 0) {
37027      manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
37028    }
37029    if (locations) {
37030      manifest.locations = locations;
37031    }
37032    if (contentSteering) {
37033      manifest.contentSteering = contentSteering;
37034    }
37035    if (type === 'dynamic') {
37036      manifest.suggestedPresentationDelay = suggestedPresentationDelay;
37037    }
37038    if (eventStream && eventStream.length > 0) {
37039      manifest.eventStream = eventStream;
37040    }
37041    const isAudioOnly = manifest.playlists.length === 0;
37042    const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
37043    const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
37044    const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
37045    const playlistTimelineStarts = formattedPlaylists.map(({
37046      timelineStarts
37047    }) => timelineStarts);
37048    manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
37049    addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
37050    if (organizedAudioGroup) {
37051      manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
37052    }
37053    if (organizedVttGroup) {
37054      manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
37055    }
37056    if (captions.length) {
37057      manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
37058    }
37059    if (previousManifest) {
37060      return positionManifestOnTimeline({
37061        oldManifest: previousManifest,
37062        newManifest: manifest
37063      });
37064    }
37065    return manifest;
37066  };
37067
37068  /**
37069   * Calculates the R (repetition) value for a live stream (for the final segment
37070   * in a manifest where the r value is negative 1)
37071   *
37072   * @param {Object} attributes
37073   *        Object containing all inherited attributes from parent elements with attribute
37074   *        names as keys
37075   * @param {number} time
37076   *        current time (typically the total time up until the final segment)
37077   * @param {number} duration
37078   *        duration property for the given <S />
37079   *
37080   * @return {number}
37081   *        R value to reach the end of the given period
37082   */
37083  const getLiveRValue = (attributes, time, duration) => {
37084    const {
37085      NOW,
37086      clientOffset,
37087      availabilityStartTime,
37088      timescale = 1,
37089      periodStart = 0,
37090      minimumUpdatePeriod = 0
37091    } = attributes;
37092    const now = (NOW + clientOffset) / 1000;
37093    const periodStartWC = availabilityStartTime + periodStart;
37094    const periodEndWC = now + minimumUpdatePeriod;
37095    const periodDuration = periodEndWC - periodStartWC;
37096    return Math.ceil((periodDuration * timescale - time) / duration);
37097  };
37098  /**
37099   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
37100   * timing and duration
37101   *
37102   * @param {Object} attributes
37103   *        Object containing all inherited attributes from parent elements with attribute
37104   *        names as keys
37105   * @param {Object[]} segmentTimeline
37106   *        List of objects representing the attributes of each S element contained within
37107   *
37108   * @return {{number: number, duration: number, time: number, timeline: number}[]}
37109   *         List of Objects with segment timing and duration info
37110   */
37111
37112  const parseByTimeline = (attributes, segmentTimeline) => {
37113    const {
37114      type,
37115      minimumUpdatePeriod = 0,
37116      media = '',
37117      sourceDuration,
37118      timescale = 1,
37119      startNumber = 1,
37120      periodStart: timeline
37121    } = attributes;
37122    const segments = [];
37123    let time = -1;
37124    for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
37125      const S = segmentTimeline[sIndex];
37126      const duration = S.d;
37127      const repeat = S.r || 0;
37128      const segmentTime = S.t || 0;
37129      if (time < 0) {
37130        // first segment
37131        time = segmentTime;
37132      }
37133      if (segmentTime && segmentTime > time) {
37134        // discontinuity
37135        // TODO: How to handle this type of discontinuity
37136        // timeline++ here would treat it like HLS discontuity and content would
37137        // get appended without gap
37138        // E.G.
37139        //  <S t="0" d="1" />
37140        //  <S d="1" />
37141        //  <S d="1" />
37142        //  <S t="5" d="1" />
37143        // would have $Time$ values of [0, 1, 2, 5]
37144        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
37145        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
37146        // does the value of sourceDuration consider this when calculating arbitrary
37147        // negative @r repeat value?
37148        // E.G. Same elements as above with this added at the end
37149        //  <S d="1" r="-1" />
37150        //  with a sourceDuration of 10
37151        // Would the 2 gaps be included in the time duration calculations resulting in
37152        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
37153        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
37154        time = segmentTime;
37155      }
37156      let count;
37157      if (repeat < 0) {
37158        const nextS = sIndex + 1;
37159        if (nextS === segmentTimeline.length) {
37160          // last segment
37161          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
37162            count = getLiveRValue(attributes, time, duration);
37163          } else {
37164            // TODO: This may be incorrect depending on conclusion of TODO above
37165            count = (sourceDuration * timescale - time) / duration;
37166          }
37167        } else {
37168          count = (segmentTimeline[nextS].t - time) / duration;
37169        }
37170      } else {
37171        count = repeat + 1;
37172      }
37173      const end = startNumber + segments.length + count;
37174      let number = startNumber + segments.length;
37175      while (number < end) {
37176        segments.push({
37177          number,
37178          duration: duration / timescale,
37179          time,
37180          timeline
37181        });
37182        time += duration;
37183        number++;
37184      }
37185    }
37186    return segments;
37187  };
37188  const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
37189  /**
37190   * Replaces template identifiers with corresponding values. To be used as the callback
37191   * for String.prototype.replace
37192   *
37193   * @name replaceCallback
37194   * @function
37195   * @param {string} match
37196   *        Entire match of identifier
37197   * @param {string} identifier
37198   *        Name of matched identifier
37199   * @param {string} format
37200   *        Format tag string. Its presence indicates that padding is expected
37201   * @param {string} width
37202   *        Desired length of the replaced value. Values less than this width shall be left
37203   *        zero padded
37204   * @return {string}
37205   *         Replacement for the matched identifier
37206   */
37207
37208  /**
37209   * Returns a function to be used as a callback for String.prototype.replace to replace
37210   * template identifiers
37211   *
37212   * @param {Obect} values
37213   *        Object containing values that shall be used to replace known identifiers
37214   * @param {number} values.RepresentationID
37215   *        Value of the Representation@id attribute
37216   * @param {number} values.Number
37217   *        Number of the corresponding segment
37218   * @param {number} values.Bandwidth
37219   *        Value of the Representation@bandwidth attribute.
37220   * @param {number} values.Time
37221   *        Timestamp value of the corresponding segment
37222   * @return {replaceCallback}
37223   *         Callback to be used with String.prototype.replace to replace identifiers
37224   */
37225
37226  const identifierReplacement = values => (match, identifier, format, width) => {
37227    if (match === '$$') {
37228      // escape sequence
37229      return '$';
37230    }
37231    if (typeof values[identifier] === 'undefined') {
37232      return match;
37233    }
37234    const value = '' + values[identifier];
37235    if (identifier === 'RepresentationID') {
37236      // Format tag shall not be present with RepresentationID
37237      return value;
37238    }
37239    if (!format) {
37240      width = 1;
37241    } else {
37242      width = parseInt(width, 10);
37243    }
37244    if (value.length >= width) {
37245      return value;
37246    }
37247    return `${new Array(width - value.length + 1).join('0')}${value}`;
37248  };
37249  /**
37250   * Constructs a segment url from a template string
37251   *
37252   * @param {string} url
37253   *        Template string to construct url from
37254   * @param {Obect} values
37255   *        Object containing values that shall be used to replace known identifiers
37256   * @param {number} values.RepresentationID
37257   *        Value of the Representation@id attribute
37258   * @param {number} values.Number
37259   *        Number of the corresponding segment
37260   * @param {number} values.Bandwidth
37261   *        Value of the Representation@bandwidth attribute.
37262   * @param {number} values.Time
37263   *        Timestamp value of the corresponding segment
37264   * @return {string}
37265   *         Segment url with identifiers replaced
37266   */
37267
37268  const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values));
37269  /**
37270   * Generates a list of objects containing timing and duration information about each
37271   * segment needed to generate segment uris and the complete segment object
37272   *
37273   * @param {Object} attributes
37274   *        Object containing all inherited attributes from parent elements with attribute
37275   *        names as keys
37276   * @param {Object[]|undefined} segmentTimeline
37277   *        List of objects representing the attributes of each S element contained within
37278   *        the SegmentTimeline element
37279   * @return {{number: number, duration: number, time: number, timeline: number}[]}
37280   *         List of Objects with segment timing and duration info
37281   */
37282
37283  const parseTemplateInfo = (attributes, segmentTimeline) => {
37284    if (!attributes.duration && !segmentTimeline) {
37285      // if neither @duration or SegmentTimeline are present, then there shall be exactly
37286      // one media segment
37287      return [{
37288        number: attributes.startNumber || 1,
37289        duration: attributes.sourceDuration,
37290        time: 0,
37291        timeline: attributes.periodStart
37292      }];
37293    }
37294    if (attributes.duration) {
37295      return parseByDuration(attributes);
37296    }
37297    return parseByTimeline(attributes, segmentTimeline);
37298  };
37299  /**
37300   * Generates a list of segments using information provided by the SegmentTemplate element
37301   *
37302   * @param {Object} attributes
37303   *        Object containing all inherited attributes from parent elements with attribute
37304   *        names as keys
37305   * @param {Object[]|undefined} segmentTimeline
37306   *        List of objects representing the attributes of each S element contained within
37307   *        the SegmentTimeline element
37308   * @return {Object[]}
37309   *         List of segment objects
37310   */
37311
37312  const segmentsFromTemplate = (attributes, segmentTimeline) => {
37313    const templateValues = {
37314      RepresentationID: attributes.id,
37315      Bandwidth: attributes.bandwidth || 0
37316    };
37317    const {
37318      initialization = {
37319        sourceURL: '',
37320        range: ''
37321      }
37322    } = attributes;
37323    const mapSegment = urlTypeToSegment({
37324      baseUrl: attributes.baseUrl,
37325      source: constructTemplateUrl(initialization.sourceURL, templateValues),
37326      range: initialization.range
37327    });
37328    const segments = parseTemplateInfo(attributes, segmentTimeline);
37329    return segments.map(segment => {
37330      templateValues.Number = segment.number;
37331      templateValues.Time = segment.time;
37332      const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
37333      // - if timescale isn't present on any level, default to 1.
37334
37335      const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37336
37337      const presentationTimeOffset = attributes.presentationTimeOffset || 0;
37338      const presentationTime =
37339      // Even if the @t attribute is not specified for the segment, segment.time is
37340      // calculated in mpd-parser prior to this, so it's assumed to be available.
37341      attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
37342      const map = {
37343        uri,
37344        timeline: segment.timeline,
37345        duration: segment.duration,
37346        resolvedUri: resolveUrl$1(attributes.baseUrl || '', uri),
37347        map: mapSegment,
37348        number: segment.number,
37349        presentationTime
37350      };
37351      return map;
37352    });
37353  };
37354
37355  /**
37356   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
37357   * to an object that matches the output of a segment in videojs/mpd-parser
37358   *
37359   * @param {Object} attributes
37360   *   Object containing all inherited attributes from parent elements with attribute
37361   *   names as keys
37362   * @param {Object} segmentUrl
37363   *   <SegmentURL> node to translate into a segment object
37364   * @return {Object} translated segment object
37365   */
37366
37367  const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
37368    const {
37369      baseUrl,
37370      initialization = {}
37371    } = attributes;
37372    const initSegment = urlTypeToSegment({
37373      baseUrl,
37374      source: initialization.sourceURL,
37375      range: initialization.range
37376    });
37377    const segment = urlTypeToSegment({
37378      baseUrl,
37379      source: segmentUrl.media,
37380      range: segmentUrl.mediaRange
37381    });
37382    segment.map = initSegment;
37383    return segment;
37384  };
37385  /**
37386   * Generates a list of segments using information provided by the SegmentList element
37387   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
37388   * node should be translated into segment.
37389   *
37390   * @param {Object} attributes
37391   *   Object containing all inherited attributes from parent elements with attribute
37392   *   names as keys
37393   * @param {Object[]|undefined} segmentTimeline
37394   *        List of objects representing the attributes of each S element contained within
37395   *        the SegmentTimeline element
37396   * @return {Object.<Array>} list of segments
37397   */
37398
37399  const segmentsFromList = (attributes, segmentTimeline) => {
37400    const {
37401      duration,
37402      segmentUrls = [],
37403      periodStart
37404    } = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR
37405    // if both SegmentTimeline and @duration are defined, it is outside of spec.
37406
37407    if (!duration && !segmentTimeline || duration && segmentTimeline) {
37408      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
37409    }
37410    const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
37411    let segmentTimeInfo;
37412    if (duration) {
37413      segmentTimeInfo = parseByDuration(attributes);
37414    }
37415    if (segmentTimeline) {
37416      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
37417    }
37418    const segments = segmentTimeInfo.map((segmentTime, index) => {
37419      if (segmentUrlMap[index]) {
37420        const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
37421        // - if timescale isn't present on any level, default to 1.
37422
37423        const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37424
37425        const presentationTimeOffset = attributes.presentationTimeOffset || 0;
37426        segment.timeline = segmentTime.timeline;
37427        segment.duration = segmentTime.duration;
37428        segment.number = segmentTime.number;
37429        segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
37430        return segment;
37431      } // Since we're mapping we should get rid of any blank segments (in case
37432      // the given SegmentTimeline is handling for more elements than we have
37433      // SegmentURLs for).
37434    }).filter(segment => segment);
37435    return segments;
37436  };
37437  const generateSegments = ({
37438    attributes,
37439    segmentInfo
37440  }) => {
37441    let segmentAttributes;
37442    let segmentsFn;
37443    if (segmentInfo.template) {
37444      segmentsFn = segmentsFromTemplate;
37445      segmentAttributes = merge$1(attributes, segmentInfo.template);
37446    } else if (segmentInfo.base) {
37447      segmentsFn = segmentsFromBase;
37448      segmentAttributes = merge$1(attributes, segmentInfo.base);
37449    } else if (segmentInfo.list) {
37450      segmentsFn = segmentsFromList;
37451      segmentAttributes = merge$1(attributes, segmentInfo.list);
37452    }
37453    const segmentsInfo = {
37454      attributes
37455    };
37456    if (!segmentsFn) {
37457      return segmentsInfo;
37458    }
37459    const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
37460    // must be in seconds. Since we've generated the segment list, we no longer need
37461    // @duration to be in @timescale units, so we can convert it here.
37462
37463    if (segmentAttributes.duration) {
37464      const {
37465        duration,
37466        timescale = 1
37467      } = segmentAttributes;
37468      segmentAttributes.duration = duration / timescale;
37469    } else if (segments.length) {
37470      // if there is no @duration attribute, use the largest segment duration as
37471      // as target duration
37472      segmentAttributes.duration = segments.reduce((max, segment) => {
37473        return Math.max(max, Math.ceil(segment.duration));
37474      }, 0);
37475    } else {
37476      segmentAttributes.duration = 0;
37477    }
37478    segmentsInfo.attributes = segmentAttributes;
37479    segmentsInfo.segments = segments; // This is a sidx box without actual segment information
37480
37481    if (segmentInfo.base && segmentAttributes.indexRange) {
37482      segmentsInfo.sidx = segments[0];
37483      segmentsInfo.segments = [];
37484    }
37485    return segmentsInfo;
37486  };
37487  const toPlaylists = representations => representations.map(generateSegments);
37488  const findChildren = (element, name) => from(element.childNodes).filter(({
37489    tagName
37490  }) => tagName === name);
37491  const getContent = element => element.textContent.trim();
37492
37493  /**
37494   * Converts the provided string that may contain a division operation to a number.
37495   *
37496   * @param {string} value - the provided string value
37497   *
37498   * @return {number} the parsed string value
37499   */
37500  const parseDivisionValue = value => {
37501    return parseFloat(value.split('/').reduce((prev, current) => prev / current));
37502  };
37503  const parseDuration = str => {
37504    const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
37505    const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
37506    const SECONDS_IN_DAY = 24 * 60 * 60;
37507    const SECONDS_IN_HOUR = 60 * 60;
37508    const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
37509
37510    const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
37511    const match = durationRegex.exec(str);
37512    if (!match) {
37513      return 0;
37514    }
37515    const [year, month, day, hour, minute, second] = match.slice(1);
37516    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);
37517  };
37518  const parseDate = str => {
37519    // Date format without timezone according to ISO 8601
37520    // YYY-MM-DDThh:mm:ss.ssssss
37521    const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
37522    // expressed by ending with 'Z'
37523
37524    if (dateRegex.test(str)) {
37525      str += 'Z';
37526    }
37527    return Date.parse(str);
37528  };
37529  const parsers = {
37530    /**
37531     * Specifies the duration of the entire Media Presentation. Format is a duration string
37532     * as specified in ISO 8601
37533     *
37534     * @param {string} value
37535     *        value of attribute as a string
37536     * @return {number}
37537     *         The duration in seconds
37538     */
37539    mediaPresentationDuration(value) {
37540      return parseDuration(value);
37541    },
37542    /**
37543     * Specifies the Segment availability start time for all Segments referred to in this
37544     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
37545     * time. Format is a date string as specified in ISO 8601
37546     *
37547     * @param {string} value
37548     *        value of attribute as a string
37549     * @return {number}
37550     *         The date as seconds from unix epoch
37551     */
37552    availabilityStartTime(value) {
37553      return parseDate(value) / 1000;
37554    },
37555    /**
37556     * Specifies the smallest period between potential changes to the MPD. Format is a
37557     * duration string as specified in ISO 8601
37558     *
37559     * @param {string} value
37560     *        value of attribute as a string
37561     * @return {number}
37562     *         The duration in seconds
37563     */
37564    minimumUpdatePeriod(value) {
37565      return parseDuration(value);
37566    },
37567    /**
37568     * Specifies the suggested presentation delay. Format is a
37569     * duration string as specified in ISO 8601
37570     *
37571     * @param {string} value
37572     *        value of attribute as a string
37573     * @return {number}
37574     *         The duration in seconds
37575     */
37576    suggestedPresentationDelay(value) {
37577      return parseDuration(value);
37578    },
37579    /**
37580     * specifices the type of mpd. Can be either "static" or "dynamic"
37581     *
37582     * @param {string} value
37583     *        value of attribute as a string
37584     *
37585     * @return {string}
37586     *         The type as a string
37587     */
37588    type(value) {
37589      return value;
37590    },
37591    /**
37592     * Specifies the duration of the smallest time shifting buffer for any Representation
37593     * in the MPD. Format is a duration string as specified in ISO 8601
37594     *
37595     * @param {string} value
37596     *        value of attribute as a string
37597     * @return {number}
37598     *         The duration in seconds
37599     */
37600    timeShiftBufferDepth(value) {
37601      return parseDuration(value);
37602    },
37603    /**
37604     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
37605     * Format is a duration string as specified in ISO 8601
37606     *
37607     * @param {string} value
37608     *        value of attribute as a string
37609     * @return {number}
37610     *         The duration in seconds
37611     */
37612    start(value) {
37613      return parseDuration(value);
37614    },
37615    /**
37616     * Specifies the width of the visual presentation
37617     *
37618     * @param {string} value
37619     *        value of attribute as a string
37620     * @return {number}
37621     *         The parsed width
37622     */
37623    width(value) {
37624      return parseInt(value, 10);
37625    },
37626    /**
37627     * Specifies the height of the visual presentation
37628     *
37629     * @param {string} value
37630     *        value of attribute as a string
37631     * @return {number}
37632     *         The parsed height
37633     */
37634    height(value) {
37635      return parseInt(value, 10);
37636    },
37637    /**
37638     * Specifies the bitrate of the representation
37639     *
37640     * @param {string} value
37641     *        value of attribute as a string
37642     * @return {number}
37643     *         The parsed bandwidth
37644     */
37645    bandwidth(value) {
37646      return parseInt(value, 10);
37647    },
37648    /**
37649     * Specifies the frame rate of the representation
37650     *
37651     * @param {string} value
37652     *        value of attribute as a string
37653     * @return {number}
37654     *         The parsed frame rate
37655     */
37656    frameRate(value) {
37657      return parseDivisionValue(value);
37658    },
37659    /**
37660     * Specifies the number of the first Media Segment in this Representation in the Period
37661     *
37662     * @param {string} value
37663     *        value of attribute as a string
37664     * @return {number}
37665     *         The parsed number
37666     */
37667    startNumber(value) {
37668      return parseInt(value, 10);
37669    },
37670    /**
37671     * Specifies the timescale in units per seconds
37672     *
37673     * @param {string} value
37674     *        value of attribute as a string
37675     * @return {number}
37676     *         The parsed timescale
37677     */
37678    timescale(value) {
37679      return parseInt(value, 10);
37680    },
37681    /**
37682     * Specifies the presentationTimeOffset.
37683     *
37684     * @param {string} value
37685     *        value of the attribute as a string
37686     *
37687     * @return {number}
37688     *         The parsed presentationTimeOffset
37689     */
37690    presentationTimeOffset(value) {
37691      return parseInt(value, 10);
37692    },
37693    /**
37694     * Specifies the constant approximate Segment duration
37695     * NOTE: The <Period> element also contains an @duration attribute. This duration
37696     *       specifies the duration of the Period. This attribute is currently not
37697     *       supported by the rest of the parser, however we still check for it to prevent
37698     *       errors.
37699     *
37700     * @param {string} value
37701     *        value of attribute as a string
37702     * @return {number}
37703     *         The parsed duration
37704     */
37705    duration(value) {
37706      const parsedValue = parseInt(value, 10);
37707      if (isNaN(parsedValue)) {
37708        return parseDuration(value);
37709      }
37710      return parsedValue;
37711    },
37712    /**
37713     * Specifies the Segment duration, in units of the value of the @timescale.
37714     *
37715     * @param {string} value
37716     *        value of attribute as a string
37717     * @return {number}
37718     *         The parsed duration
37719     */
37720    d(value) {
37721      return parseInt(value, 10);
37722    },
37723    /**
37724     * Specifies the MPD start time, in @timescale units, the first Segment in the series
37725     * starts relative to the beginning of the Period
37726     *
37727     * @param {string} value
37728     *        value of attribute as a string
37729     * @return {number}
37730     *         The parsed time
37731     */
37732    t(value) {
37733      return parseInt(value, 10);
37734    },
37735    /**
37736     * Specifies the repeat count of the number of following contiguous Segments with the
37737     * same duration expressed by the value of @d
37738     *
37739     * @param {string} value
37740     *        value of attribute as a string
37741     * @return {number}
37742     *         The parsed number
37743     */
37744    r(value) {
37745      return parseInt(value, 10);
37746    },
37747    /**
37748     * Specifies the presentationTime.
37749     *
37750     * @param {string} value
37751     *        value of the attribute as a string
37752     *
37753     * @return {number}
37754     *         The parsed presentationTime
37755     */
37756    presentationTime(value) {
37757      return parseInt(value, 10);
37758    },
37759    /**
37760     * 
37760Default parser for all other attributes. Acts as a no-op and just returns the value
37761     * as a string
37762     *
37763     * @param {string} value
37764     *        value of attribute as a string
37765     * @return {string}
37766     *         Unparsed value
37767     */
37768    DEFAULT(value) {
37769      return value;
37770    }
37771  };
37772  /**
37773   * Gets all the attributes and values of the provided node, parses attributes with known
37774   * types, and returns an object with attribute names mapped to values.
37775   *
37776   * @param {Node} el
37777   *        The node to parse attributes from
37778   * @return {Object}
37779   *         Object with all attributes of el parsed
37780   */
37781
37782  const parseAttributes = el => {
37783    if (!(el && el.attributes)) {
37784      return {};
37785    }
37786    return from(el.attributes).reduce((a, e) => {
37787      const parseFn = parsers[e.name] || parsers.DEFAULT;
37788      a[e.n
vendor: 43,831 bytes, lines 37788-38923
37788ame] = parseFn(e.value);
37789      return a;
37790    }, {});
37791  };
37792  const keySystemsMap = {
37793    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
37794    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
37795    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
37796    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime',
37797    // ISO_IEC 23009-1_2022 5.8.5.2.2 The mp4 Protection Scheme
37798    'urn:mpeg:dash:mp4protection:2011': 'mp4protection'
37799  };
37800  /**
37801   * Builds a list of urls that is the product of the reference urls and BaseURL values
37802   *
37803   * @param {Object[]} references
37804   *        List of objects containing the reference URL as well as its attributes
37805   * @param {Node[]} baseUrlElements
37806   *        List of BaseURL nodes from the mpd
37807   * @return {Object[]}
37808   *         List of objects with resolved urls and attributes
37809   */
37810
37811  const buildBaseUrls = (references, baseUrlElements) => {
37812    if (!baseUrlElements.length) {
37813      return references;
37814    }
37815    return flatten(references.map(function (reference) {
37816      return baseUrlElements.map(function (baseUrlElement) {
37817        const initialBaseUrl = getContent(baseUrlElement);
37818        const resolvedBaseUrl = resolveUrl$1(reference.baseUrl, initialBaseUrl);
37819        const finalBaseUrl = merge$1(parseAttributes(baseUrlElement), {
37820          baseUrl: resolvedBaseUrl
37821        }); // If the URL is resolved, we want to get the serviceLocation from the reference
37822        // assuming there is no serviceLocation on the initialBaseUrl
37823
37824        if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
37825          finalBaseUrl.serviceLocation = reference.serviceLocation;
37826        }
37827        return finalBaseUrl;
37828      });
37829    }));
37830  };
37831  /**
37832   * Contains all Segment information for its containing AdaptationSet
37833   *
37834   * @typedef {Object} SegmentInformation
37835   * @property {Object|undefined} template
37836   *           Contains the attributes for the SegmentTemplate node
37837   * @property {Object[]|undefined} segmentTimeline
37838   *           Contains a list of atrributes for each S node within the SegmentTimeline node
37839   * @property {Object|undefined} list
37840   *           Contains the attributes for the SegmentList node
37841   * @property {Object|undefined} base
37842   *           Contains the attributes for the SegmentBase node
37843   */
37844
37845  /**
37846   * Returns all available Segment information contained within the AdaptationSet node
37847   *
37848   * @param {Node} adaptationSet
37849   *        The AdaptationSet node to get Segment information from
37850   * @return {SegmentInformation}
37851   *         The Segment information contained within the provided AdaptationSet
37852   */
37853
37854  const getSegmentInformation = adaptationSet => {
37855    const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
37856    const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
37857    const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge$1({
37858      tag: 'SegmentURL'
37859    }, parseAttributes(s)));
37860    const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
37861    const segmentTimelineParentNode = segmentList || segmentTemplate;
37862    const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
37863    const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
37864    const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
37865    // @initialization and an <Initialization> node.  @initialization can be templated,
37866    // while the node can have a url and range specified.  If the <SegmentTemplate> has
37867    // both @initialization and an <Initialization> subelement we opt to override with
37868    // the node, as this interaction is not defined in the spec.
37869
37870    const template = segmentTemplate && parseAttributes(segmentTemplate);
37871    if (template && segmentInitialization) {
37872      template.initialization = segmentInitialization && parseAttributes(segmentInitialization);
37873    } else if (template && template.initialization) {
37874      // If it is @initialization we convert it to an object since this is the format that
37875      // later functions will rely on for the initialization segment.  This is only valid
37876      // for <SegmentTemplate>
37877      template.initialization = {
37878        sourceURL: template.initialization
37879      };
37880    }
37881    const segmentInfo = {
37882      template,
37883      segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
37884      list: segmentList && merge$1(parseAttributes(segmentList), {
37885        segmentUrls,
37886        initialization: parseAttributes(segmentInitialization)
37887      }),
37888      base: segmentBase && merge$1(parseAttributes(segmentBase), {
37889        initialization: parseAttributes(segmentInitialization)
37890      })
37891    };
37892    Object.keys(segmentInfo).forEach(key => {
37893      if (!segmentInfo[key]) {
37894        delete segmentInfo[key];
37895      }
37896    });
37897    return segmentInfo;
37898  };
37899  /**
37900   * Contains Segment information and attributes needed to construct a Playlist object
37901   * from a Representation
37902   *
37903   * @typedef {Object} RepresentationInformation
37904   * @property {SegmentInformation} segmentInfo
37905   *           Segment information for this Representation
37906   * @property {Object} attributes
37907   *           Inherited attributes for this Representation
37908   */
37909
37910  /**
37911   * Maps a Representation node to an object containing Segment information and attributes
37912   *
37913   * @name inheritBaseUrlsCallback
37914   * @function
37915   * @param {Node} representation
37916   *        Representation node from the mpd
37917   * @return {RepresentationInformation}
37918   *         Representation information needed to construct a Playlist object
37919   */
37920
37921  /**
37922   * Returns a callback for Array.prototype.map for mapping Representation nodes to
37923   * Segment information and attributes using inherited BaseURL nodes.
37924   *
37925   * @param {Object} adaptationSetAttributes
37926   *        Contains attributes inherited by the AdaptationSet
37927   * @param {Object[]} adaptationSetBaseUrls
37928   *        List of objects containing resolved base URLs and attributes
37929   *        inherited by the AdaptationSet
37930   * @param {SegmentInformation} adaptationSetSegmentInfo
37931   *        Contains Segment information for the AdaptationSet
37932   * @return {inheritBaseUrlsCallback}
37933   *         Callback map function
37934   */
37935
37936  const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
37937    const repBaseUrlElements = findChildren(representation, 'BaseURL');
37938    const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
37939    const attributes = merge$1(adaptationSetAttributes, parseAttributes(representation));
37940    const representationSegmentInfo = getSegmentInformation(representation);
37941    return repBaseUrls.map(baseUrl => {
37942      return {
37943        segmentInfo: merge$1(adaptationSetSegmentInfo, representationSegmentInfo),
37944        attributes: merge$1(attributes, baseUrl)
37945      };
37946    });
37947  };
37948  /**
37949   * Tranforms a series of content protection nodes to
37950   * an object containing pssh data by key system
37951   *
37952   * @param {Node[]} contentProtectionNodes
37953   *        Content protection nodes
37954   * @return {Object}
37955   *        Object containing pssh data by key system
37956   */
37957
37958  const generateKeySystemInformation = contentProtectionNodes => {
37959    return contentProtectionNodes.reduce((acc, node) => {
37960      const attributes = parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated
37961      // as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
37962      // UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
37963      // .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
37964
37965      if (attributes.schemeIdUri) {
37966        attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
37967      }
37968      const keySystem = keySystemsMap[attributes.schemeIdUri];
37969      if (keySystem) {
37970        acc[keySystem] = {
37971          attributes
37972        };
37973        const psshNode = findChildren(node, 'cenc:pssh')[0];
37974        if (psshNode) {
37975          const pssh = getContent(psshNode);
37976          acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh);
37977        }
37978      }
37979      return acc;
37980    }, {});
37981  }; // defined in ANSI_SCTE 214-1 2016
37982
37983  const parseCaptionServiceMetadata = service => {
37984    // 608 captions
37985    if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
37986      const values = typeof service.value !== 'string' ? [] : service.value.split(';');
37987      return values.map(value => {
37988        let channel;
37989        let language; // default language to value
37990
37991        language = value;
37992        if (/^CC\d=/.test(value)) {
37993          [channel, language] = value.split('=');
37994        } else if (/^CC\d$/.test(value)) {
37995          channel = value;
37996        }
37997        return {
37998          channel,
37999          language
38000        };
38001      });
38002    } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
38003      const values = typeof service.value !== 'string' ? [] : service.value.split(';');
38004      return values.map(value => {
38005        const flags = {
38006          // service or channel number 1-63
38007          'channel': undefined,
38008          // language is a 3ALPHA per ISO 639.2/B
38009          // field is required
38010          'language': undefined,
38011          // BIT 1/0 or ?
38012          // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
38013          'aspectRatio': 1,
38014          // BIT 1/0
38015          // easy reader flag indicated the text is tailed to the needs of beginning readers
38016          // default 0, or off
38017          'easyReader': 0,
38018          // BIT 1/0
38019          // If 3d metadata is present (CEA-708.1) then 1
38020          // default 0
38021          '3D': 0
38022        };
38023        if (/=/.test(value)) {
38024          const [channel, opts = ''] = value.split('=');
38025          flags.channel = channel;
38026          flags.language = value;
38027          opts.split(',').forEach(opt => {
38028            const [name, val] = opt.split(':');
38029            if (name === 'lang') {
38030              flags.language = val; // er for easyReadery
38031            } else if (name === 'er') {
38032              flags.easyReader = Number(val); // war for wide aspect ratio
38033            } else if (name === 'war') {
38034              flags.aspectRatio = Number(val);
38035            } else if (name === '3D') {
38036              flags['3D'] = Number(val);
38037            }
38038          });
38039        } else {
38040          flags.language = value;
38041        }
38042        if (flags.channel) {
38043          flags.channel = 'SERVICE' + flags.channel;
38044        }
38045        return flags;
38046      });
38047    }
38048  };
38049  /**
38050   * A map callback that will parse all event stream data for a collection of periods
38051   * DASH ISO_IEC_23009 5.10.2.2
38052   * https://dashif-documents.azurewebsites.net/Events/master/event.html#mpd-event-timing
38053   *
38054   * @param {PeriodInformation} period object containing necessary period information
38055   * @return a collection of parsed eventstream event objects
38056   */
38057
38058  const toEventStream = period => {
38059    // get and flatten all EventStreams tags and parse attributes and children
38060    return flatten(findChildren(period.node, 'EventStream').map(eventStream => {
38061      const eventStreamAttributes = parseAttributes(eventStream);
38062      const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
38063
38064      return findChildren(eventStream, 'Event').map(event => {
38065        const eventAttributes = parseAttributes(event);
38066        const presentationTime = eventAttributes.presentationTime || 0;
38067        const timescale = eventStreamAttributes.timescale || 1;
38068        const duration = eventAttributes.duration || 0;
38069        const start = presentationTime / timescale + period.attributes.start;
38070        return {
38071          schemeIdUri,
38072          value: eventStreamAttributes.value,
38073          id: eventAttributes.id,
38074          start,
38075          end: start + duration / timescale,
38076          messageData: getContent(event) || eventAttributes.messageData,
38077          contentEncoding: eventStreamAttributes.contentEncoding,
38078          presentationTimeOffset: eventStreamAttributes.presentationTimeOffset || 0
38079        };
38080      });
38081    }));
38082  };
38083  /**
38084   * Maps an AdaptationSet node to a list of Representation information objects
38085   *
38086   * @name toRepresentationsCallback
38087   * @function
38088   * @param {Node} adaptationSet
38089   *        AdaptationSet node from the mpd
38090   * @return {RepresentationInformation[]}
38091   *         List of objects containing Representaion information
38092   */
38093
38094  /**
38095   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
38096   * Representation information objects
38097   *
38098   * @param {Object} periodAttributes
38099   *        Contains attributes inherited by the Period
38100   * @param {Object[]} periodBaseUrls
38101   *        Contains list of objects with resolved base urls and attributes
38102   *        inherited by the Period
38103   * @param {string[]} periodSegmentInfo
38104   *        Contains Segment Information at the period level
38105   * @return {toRepresentationsCallback}
38106   *         Callback map function
38107   */
38108
38109  const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
38110    const adaptationSetAttributes = parseAttributes(adaptationSet);
38111    const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
38112    const role = findChildren(adaptationSet, 'Role')[0];
38113    const roleAttributes = {
38114      role: parseAttributes(role)
38115    };
38116    let attrs = merge$1(periodAttributes, adaptationSetAttributes, roleAttributes);
38117    const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
38118    const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
38119    if (captionServices) {
38120      attrs = merge$1(attrs, {
38121        captionServices
38122      });
38123    }
38124    const label = findChildren(adaptationSet, 'Label')[0];
38125    if (label && label.childNodes.length) {
38126      const labelVal = label.childNodes[0].nodeValue.trim();
38127      attrs = merge$1(attrs, {
38128        label: labelVal
38129      });
38130    }
38131    const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
38132    if (Object.keys(contentProtection).length) {
38133      attrs = merge$1(attrs, {
38134        contentProtection
38135      });
38136    }
38137    const segmentInfo = getSegmentInformation(adaptationSet);
38138    const representations = findChildren(adaptationSet, 'Representation');
38139    const adaptationSetSegmentInfo = merge$1(periodSegmentInfo, segmentInfo);
38140    return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
38141  };
38142  /**
38143   * Contains all period information for mapping nodes onto adaptation sets.
38144   *
38145   * @typedef {Object} PeriodInformation
38146   * @property {Node} period.node
38147   *           Period node from the mpd
38148   * @property {Object} period.attributes
38149   *           Parsed period attributes from node plus any added
38150   */
38151
38152  /**
38153   * Maps a PeriodInformation object to a list of Representation information objects for all
38154   * AdaptationSet nodes contained within the Period.
38155   *
38156   * @name toAdaptationSetsCallback
38157   * @function
38158   * @param {PeriodInformation} period
38159   *        Period object containing necessary period information
38160   * @param {number} periodStart
38161   *        Start time of the Period within the mpd
38162   * @return {RepresentationInformation[]}
38163   *         List of objects containing Representaion information
38164   */
38165
38166  /**
38167   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
38168   * Representation information objects
38169   *
38170   * @param {Object} mpdAttributes
38171   *        Contains attributes inherited by the mpd
38172    * @param {Object[]} mpdBaseUrls
38173   *        Contains list of objects with resolved base urls and attributes
38174   *        inherited by the mpd
38175   * @return {toAdaptationSetsCallback}
38176   *         Callback map function
38177   */
38178
38179  const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
38180    const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
38181    const periodAttributes = merge$1(mpdAttributes, {
38182      periodStart: period.attributes.start
38183    });
38184    if (typeof period.attributes.duration === 'number') {
38185      periodAttributes.periodDuration = period.attributes.duration;
38186    }
38187    const adaptationSets = findChildren(period.node, 'AdaptationSet');
38188    const periodSegmentInfo = getSegmentInformation(period.node);
38189    return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
38190  };
38191  /**
38192   * Tranforms an array of content steering nodes into an object
38193   * containing CDN content steering information from the MPD manifest.
38194   *
38195   * For more information on the DASH spec for Content Steering parsing, see:
38196   * https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
38197   *
38198   * @param {Node[]} contentSteeringNodes
38199   *        Content steering nodes
38200   * @param {Function} eventHandler
38201   *        The event handler passed into the parser options to handle warnings
38202   * @return {Object}
38203   *        Object containing content steering data
38204   */
38205
38206  const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => {
38207    // If there are more than one ContentSteering tags, throw an error
38208    if (contentSteeringNodes.length > 1) {
38209      eventHandler({
38210        type: 'warn',
38211        message: 'The MPD manifest should contain no more than one ContentSteering tag'
38212      });
38213    } // Return a null value if there are no ContentSteering tags
38214
38215    if (!contentSteeringNodes.length) {
38216      return null;
38217    }
38218    const infoFromContentSteeringTag = merge$1({
38219      serverURL: getContent(contentSteeringNodes[0])
38220    }, parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value
38221    // to `false` if it doesn't exist
38222
38223    infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true';
38224    return infoFromContentSteeringTag;
38225  };
38226  /**
38227   * Gets Period@start property for a given period.
38228   *
38229   * @param {Object} options
38230   *        Options object
38231   * @param {Object} options.attributes
38232   *        Period attributes
38233   * @param {Object} [options.priorPeriodAttributes]
38234   *        Prior period attributes (if prior period is available)
38235   * @param {string} options.mpdType
38236   *        The MPD@type these periods came from
38237   * @return {number|null}
38238   *         The period start, or null if it's an early available period or error
38239   */
38240
38241  const getPeriodStart = ({
38242    attributes,
38243    priorPeriodAttributes,
38244    mpdType
38245  }) => {
38246    // Summary of period start time calculation from DASH spec section 5.3.2.1
38247    //
38248    // A period's start is the first period's start + time elapsed after playing all
38249    // prior periods to this one. Periods continue one after the other in time (without
38250    // gaps) until the end of the presentation.
38251    //
38252    // The value of Period@start should be:
38253    // 1. if Period@start is present: value of Period@start
38254    // 2. if previous period exists and it has @duration: previous Period@start +
38255    //    previous Period@duration
38256    // 3. if this is first period and MPD@type is 'static': 0
38257    // 4. in all other cases, consider the period an "early available period" (note: not
38258    //    currently supported)
38259    // (1)
38260    if (typeof attributes.start === 'number') {
38261      return attributes.start;
38262    } // (2)
38263
38264    if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
38265      return priorPeriodAttributes.start + priorPeriodAttributes.duration;
38266    } // (3)
38267
38268    if (!priorPeriodAttributes && mpdType === 'static') {
38269      return 0;
38270    } // (4)
38271    // There is currently no logic for calculating the Period@start value if there is
38272    // no Period@start or prior Period@start and Period@duration available. This is not made
38273    // explicit by the DASH interop guidelines or the DASH spec, however, since there's
38274    // nothing about any other resolution strategies, it's implied. Thus, this case should
38275    // be considered an early available period, or error, and null should suffice for both
38276    // of those cases.
38277
38278    return null;
38279  };
38280  /**
38281   * Traverses the mpd xml tree to generate a list of Representation information objects
38282   * that have inherited attributes from parent nodes
38283   *
38284   * @param {Node} mpd
38285   *        The root node of the mpd
38286   * @param {Object} options
38287   *        Available options for inheritAttributes
38288   * @param {string} options.manifestUri
38289   *        The uri source of the mpd
38290   * @param {number} options.NOW
38291   *        Current time per DASH IOP.  Default is current time in ms since epoch
38292   * @param {number} options.clientOffset
38293   *        Client time difference from NOW (in milliseconds)
38294   * @return {RepresentationInformation[]}
38295   *         List of objects containing Representation information
38296   */
38297
38298  const inheritAttributes = (mpd, options = {}) => {
38299    const {
38300      manifestUri = '',
38301      NOW = Date.now(),
38302      clientOffset = 0,
38303      // TODO: For now, we are expecting an eventHandler callback function
38304      // to be passed into the mpd parser as an option.
38305      // In the future, we should enable stream parsing by using the Stream class from vhs-utils.
38306      // This will support new features including a standardized event handler.
38307      // See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing.
38308      // https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9
38309      eventHandler = function () {}
38310    } = options;
38311    const periodNodes = findChildren(mpd, 'Period');
38312    if (!periodNodes.length) {
38313      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
38314    }
38315    const locations = findChildren(mpd, 'Location');
38316    const mpdAttributes = parseAttributes(mpd);
38317    const mpdBaseUrls = buildBaseUrls([{
38318      baseUrl: manifestUri
38319    }], findChildren(mpd, 'BaseURL'));
38320    const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
38321
38322    mpdAttributes.type = mpdAttributes.type || 'static';
38323    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
38324    mpdAttributes.NOW = NOW;
38325    mpdAttributes.clientOffset = clientOffset;
38326    if (locations.length) {
38327      mpdAttributes.locations = locations.map(getContent);
38328    }
38329    const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
38330    // adding properties that require looking at prior periods is to parse attributes and add
38331    // missing ones before toAdaptationSets is called. If more such properties are added, it
38332    // may be better to refactor toAdaptationSets.
38333
38334    periodNodes.forEach((node, index) => {
38335      const attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
38336      // for this period.
38337
38338      const priorPeriod = periods[index - 1];
38339      attributes.start = getPeriodStart({
38340        attributes,
38341        priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
38342        mpdType: mpdAttributes.type
38343      });
38344      periods.push({
38345        node,
38346        attributes
38347      });
38348    });
38349    return {
38350      locations: mpdAttributes.locations,
38351      contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler),
38352      // TODO: There are occurences where this `representationInfo` array contains undesired
38353      // duplicates. This generally occurs when there are multiple BaseURL nodes that are
38354      // direct children of the MPD node. When we attempt to resolve URLs from a combination of the
38355      // parent BaseURL and a child BaseURL, and the value does not resolve,
38356      // we end up returning the child BaseURL multiple times.
38357      // We need to determine a way to remove these duplicates in a safe way.
38358      // See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527
38359      representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))),
38360      eventStream: flatten(periods.map(toEventStream))
38361    };
38362  };
38363  const stringToMpdXml = manifestString => {
38364    if (manifestString === '') {
38365      throw new Error(errors.DASH_EMPTY_MANIFEST);
38366    }
38367    const parser = new DOMParser();
38368    let xml;
38369    let mpd;
38370    try {
38371      xml = parser.parseFromString(manifestString, 'application/xml');
38372      mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
38373    } catch (e) {// ie 11 throws on invalid xml
38374    }
38375    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
38376      throw new Error(errors.DASH_INVALID_XML);
38377    }
38378    return mpd;
38379  };
38380
38381  /**
38382   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38383   *
38384   * @param {string} mpd
38385   *        XML string of the MPD manifest
38386   * @return {Object|null}
38387   *         Attributes of UTCTiming node specified in the manifest. Null if none found
38388   */
38389
38390  const parseUTCTimingScheme = mpd => {
38391    const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
38392    if (!UTCTimingNode) {
38393      return null;
38394    }
38395    const attributes = parseAttributes(UTCTimingNode);
38396    switch (attributes.schemeIdUri) {
38397      case 'urn:mpeg:dash:utc:http-head:2014':
38398      case 'urn:mpeg:dash:utc:http-head:2012':
38399        attributes.method = 'HEAD';
38400        break;
38401      case 'urn:mpeg:dash:utc:http-xsdate:2014':
38402      case 'urn:mpeg:dash:utc:http-iso:2014':
38403      case 'urn:mpeg:dash:utc:http-xsdate:2012':
38404      case 'urn:mpeg:dash:utc:http-iso:2012':
38405        attributes.method = 'GET';
38406        break;
38407      case 'urn:mpeg:dash:utc:direct:2014':
38408      case 'urn:mpeg:dash:utc:direct:2012':
38409        attributes.method = 'DIRECT';
38410        attributes.value = Date.parse(attributes.value);
38411        break;
38412      case 'urn:mpeg:dash:utc:http-ntp:2014':
38413      case 'urn:mpeg:dash:utc:ntp:2014':
38414      case 'urn:mpeg:dash:utc:sntp:2014':
38415      default:
38416        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
38417    }
38418    return attributes;
38419  };
38420  /*
38421   * Given a DASH manifest string and options, parses the DASH manifest into an object in the
38422   * form outputed by m3u8-parser and accepted by videojs/http-streaming.
38423   *
38424   * For live DASH manifests, if `previousManifest` is provided in options, then the newly
38425   * parsed DASH manifest will have its media sequence and discontinuity sequence values
38426   * updated to reflect its position relative to the prior manifest.
38427   *
38428   * @param {string} manifestString - the DASH manifest as a string
38429   * @param {options} [options] - any options
38430   *
38431   * @return {Object} the manifest object
38432   */
38433
38434  const parse = (manifestString, options = {}) => {
38435    const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
38436    const playlists = toPlaylists(parsedManifestInfo.representationInfo);
38437    return toM3u8({
38438      dashPlaylists: playlists,
38439      locations: parsedManifestInfo.locations,
38440      contentSteering: parsedManifestInfo.contentSteeringInfo,
38441      sidxMapping: options.sidxMapping,
38442      previousManifest: options.previousManifest,
38443      eventStream: parsedManifestInfo.eventStream
38444    });
38445  };
38446  /**
38447   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38448   *
38449   * @param {string} manifestString
38450   *        XML string of the MPD manifest
38451   * @return {Object|null}
38452   *         Attributes of UTCTiming node specified in the manifest. Null if none found
38453   */
38454
38455  const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
38456
38457  var MAX_UINT32 = Math.pow(2, 32);
38458  var getUint64$1 = function (uint8) {
38459    var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
38460    var value;
38461    if (dv.getBigUint64) {
38462      value = dv.getBigUint64(0);
38463      if (value < Number.MAX_SAFE_INTEGER) {
38464        return Number(value);
38465      }
38466      return value;
38467    }
38468    return dv.getUint32(0) * MAX_UINT32 + dv.getUint32(4);
38469  };
38470  var numbers = {
38471    getUint64: getUint64$1,
38472    MAX_UINT32: MAX_UINT32
38473  };
38474
38475  var getUint64 = numbers.getUint64;
38476  var parseSidx = function (data) {
38477    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
38478      result = {
38479        version: data[0],
38480        flags: new Uint8Array(data.subarray(1, 4)),
38481        references: [],
38482        referenceId: view.getUint32(4),
38483        timescale: view.getUint32(8)
38484      },
38485      i = 12;
38486    if (result.version === 0) {
38487      result.earliestPresentationTime = view.getUint32(i);
38488      result.firstOffset = view.getUint32(i + 4);
38489      i += 8;
38490    } else {
38491      // read 64 bits
38492      result.earliestPresentationTime = getUint64(data.subarray(i));
38493      result.firstOffset = getUint64(data.subarray(i + 8));
38494      i += 16;
38495    }
38496    i += 2; // reserved
38497
38498    var referenceCount = view.getUint16(i);
38499    i += 2; // start of references
38500
38501    for (; referenceCount > 0; i += 12, referenceCount--) {
38502      result.references.push({
38503        referenceType: (data[i] & 0x80) >>> 7,
38504        referencedSize: view.getUint32(i) & 0x7FFFFFFF,
38505        subsegmentDuration: view.getUint32(i + 4),
38506        startsWithSap: !!(data[i + 8] & 0x80),
38507        sapType: (data[i + 8] & 0x70) >>> 4,
38508        sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
38509      });
38510    }
38511    return result;
38512  };
38513  var parseSidx_1 = parseSidx;
38514
38515  var ID3 = toUint8([0x49, 0x44, 0x33]);
38516  var getId3Size = function getId3Size(bytes, offset) {
38517    if (offset === void 0) {
38518      offset = 0;
38519    }
38520    bytes = toUint8(bytes);
38521    var flags = bytes[offset + 5];
38522    var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
38523    var footerPresent = (flags & 16) >> 4;
38524    if (footerPresent) {
38525      return returnSize + 20;
38526    }
38527    return returnSize + 10;
38528  };
38529  var getId3Offset = function getId3Offset(bytes, offset) {
38530    if (offset === void 0) {
38531      offset = 0;
38532    }
38533    bytes = toUint8(bytes);
38534    if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, {
38535      offset: offset
38536    })) {
38537      return offset;
38538    }
38539    offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
38540    // have multiple ID3 tag sections even though
38541    // they should not.
38542
38543    return getId3Offset(bytes, offset);
38544  };
38545
38546  var normalizePath$1 = function normalizePath(path) {
38547    if (typeof path === 'string') {
38548      return stringToBytes(path);
38549    }
38550    if (typeof path === 'number') {
38551      return path;
38552    }
38553    return path;
38554  };
38555  var normalizePaths$1 = function normalizePaths(paths) {
38556    if (!Array.isArray(paths)) {
38557      return [normalizePath$1(paths)];
38558    }
38559    return paths.map(function (p) {
38560      return normalizePath$1(p);
38561    });
38562  };
38563  /**
38564   * find any number of boxes by name given a path to it in an iso bmff
38565   * such as mp4.
38566   *
38567   * @param {TypedArray} bytes
38568   *        bytes for the iso bmff to search for boxes in
38569   *
38570   * @param {Uint8Array[]|string[]|string|Uint8Array} name
38571   *        An array of paths or a single path representing the name
38572   *        of boxes to search through in bytes. Paths may be
38573   *        uint8 (character codes) or strings.
38574   *
38575   * @param {boolean} [complete=false]
38576   *        Should we search only for complete boxes on the final path.
38577   *        This is very useful when you do not want to get back partial boxes
38578   *        in the case of streaming files.
38579   *
38580   * @return {Uint8Array[]}
38581   *         An array of the end paths that we found.
38582   */
38583
38584  var findBox = function findBox(bytes, paths, complete) {
38585    if (complete === void 0) {
38586      complete = false;
38587    }
38588    paths = normalizePaths$1(paths);
38589    bytes = toUint8(bytes);
38590    var results = [];
38591    if (!paths.length) {
38592      // short-circuit the search for empty paths
38593      return results;
38594    }
38595    var i = 0;
38596    while (i < bytes.length) {
38597      var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
38598      var type = bytes.subarray(i + 4, i + 8); // invalid box format.
38599
38600      if (size === 0) {
38601        break;
38602      }
38603      var end = i + size;
38604      if (end > bytes.length) {
38605        // this box is bigger than the number of bytes we have
38606        // and complete is set, we cannot find any more boxes.
38607        if (complete) {
38608          break;
38609        }
38610        end = bytes.length;
38611      }
38612      var data = bytes.subarray(i + 8, end);
38613      if (bytesMatch(type, paths[0])) {
38614        if (paths.length === 1) {
38615          // this is the end of the path and we've found the box we were
38616          // looking for
38617          results.push(data);
38618        } else {
38619          // recursively search for the next box along the path
38620          results.push.apply(results, findBox(data, paths.slice(1), complete));
38621        }
38622      }
38623      i = end;
38624    } // we've finished searching all of bytes
38625
38626    return results;
38627  };
38628
38629  // https://matroska-org.github.io/libebml/specs.html
38630  // https://www.matroska.org/technical/elements.html
38631  // https://www.webmproject.org/docs/container/
38632
38633  var EBML_TAGS = {
38634    EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]),
38635    DocType: toUint8([0x42, 0x82]),
38636    Segment: toUint8([0x18, 0x53, 0x80, 0x67]),
38637    SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]),
38638    Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]),
38639    Track: toUint8([0xAE]),
38640    TrackNumber: toUint8([0xd7]),
38641    DefaultDuration: toUint8([0x23, 0xe3, 0x83]),
38642    TrackEntry: toUint8([0xAE]),
38643    TrackType: toUint8([0x83]),
38644    FlagDefault: toUint8([0x88]),
38645    CodecID: toUint8([0x86]),
38646    CodecPrivate: toUint8([0x63, 0xA2]),
38647    VideoTrack: toUint8([0xe0]),
38648    AudioTrack: toUint8([0xe1]),
38649    // Not used yet, but will be used for live webm/mkv
38650    // see https://www.matroska.org/technical/basics.html#block-structure
38651    // see https://www.matroska.org/technical/basics.html#simpleblock-structure
38652    Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]),
38653    Timestamp: toUint8([0xE7]),
38654    TimestampScale: toUint8([0x2A, 0xD7, 0xB1]),
38655    BlockGroup: toUint8([0xA0]),
38656    BlockDuration: toUint8([0x9B]),
38657    Block: toUint8([0xA1]),
38658    SimpleBlock: toUint8([0xA3])
38659  };
38660  /**
38661   * This is a simple table to determine the length
38662   * of things in ebml. The length is one based (starts at 1,
38663   * rather than zero) and for every zero bit before a one bit
38664   * we add one to length. We also need this table because in some
38665   * case we have to xor all the length bits from another value.
38666   */
38667
38668  var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
38669  var getLength = function getLength(byte) {
38670    var len = 1;
38671    for (var i = 0; i < LENGTH_TABLE.length; i++) {
38672      if (byte & LENGTH_TABLE[i]) {
38673        break;
38674      }
38675      len++;
38676    }
38677    return len;
38678  }; // length in ebml is stored in the first 4 to 8 bits
38679  // of the first byte. 4 for the id length and 8 for the
38680  // data size length. Length is measured by converting the number to binary
38681  // then 1 + the number of zeros before a 1 is encountered starting
38682  // from the left.
38683
38684  var getvint = function getvint(bytes, offset, removeLength, signed) {
38685    if (removeLength === void 0) {
38686      removeLength = true;
38687    }
38688    if (signed === void 0) {
38689      signed = false;
38690    }
38691    var length = getLength(bytes[offset]);
38692    var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
38693    // as they will be modified below to remove the dataSizeLen bits and we do not
38694    // want to modify the original data. normally we could just call slice on
38695    // uint8array but ie 11 does not support that...
38696
38697    if (removeLength) {
38698      valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
38699      valueBytes[0] ^= LENGTH_TABLE[length - 1];
38700    }
38701    return {
38702      length: length,
38703      value: bytesToNumber(valueBytes, {
38704        signed: signed
38705      }),
38706      bytes: valueBytes
38707    };
38708  };
38709  var normalizePath = function normalizePath(path) {
38710    if (typeof path === 'string') {
38711      return path.match(/.{1,2}/g).map(function (p) {
38712        return normalizePath(p);
38713      });
38714    }
38715    if (typeof path === 'number') {
38716      return numberToBytes(path);
38717    }
38718    return path;
38719  };
38720  var normalizePaths = function normalizePaths(paths) {
38721    if (!Array.isArray(paths)) {
38722      return [normalizePath(paths)];
38723    }
38724    return paths.map(function (p) {
38725      return normalizePath(p);
38726    });
38727  };
38728  var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
38729    if (offset >= bytes.length) {
38730      return bytes.length;
38731    }
38732    var innerid = getvint(bytes, offset, false);
38733    if (bytesMatch(id.bytes, innerid.bytes)) {
38734      return offset;
38735    }
38736    var dataHeader = getvint(bytes, offset + innerid.length);
38737    return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
38738  };
38739  /**
38740   * Notes on the EBLM format.
38741   *
38742   * EBLM uses "vints" tags. Every vint tag contains
38743   * two parts
38744   *
38745   * 1. The length from the first byte. You get this by
38746   *    converting the byte to binary and counting the zeros
38747   *    before a 1. Then you add 1 to that. Examples
38748   *    00011111 = length 4 because there are 3 zeros before a 1.
38749   *    00100000 = length 3 because there are 2 zeros before a 1.
38750   *    00000011 = length 7 because there are 6 zeros before a 1.
38751   *
38752   * 2. The bits used for length are removed from the first byte
38753   *    Then all the bytes are merged into a value. NOTE: this
38754   *    is not the case for id ebml tags as there id includes
38755   *    length bits.
38756   *
38757   */
38758
38759  var findEbml = function findEbml(bytes, paths) {
38760    paths = normalizePaths(paths);
38761    bytes = toUint8(bytes);
38762    var results = [];
38763    if (!paths.length) {
38764      return results;
38765    }
38766    var i = 0;
38767    while (i < bytes.length) {
38768      var id = getvint(bytes, i, false);
38769      var dataHeader = getvint(bytes, i + id.length);
38770      var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
38771
38772      if (dataHeader.value === 0x7f) {
38773        dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
38774        if (dataHeader.value !== bytes.length) {
38775          dataHeader.value -= dataStart;
38776        }
38777      }
38778      var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
38779      var data = bytes.subarray(dataStart, dataEnd);
38780      if (bytesMatch(paths[0], id.bytes)) {
38781        if (paths.length === 1) {
38782          // this is the end of the paths and we've found the tag we were
38783          // looking for
38784          results.push(data);
38785        } else {
38786          // recursively search for the next tag inside of the data
38787          // of this one
38788          results = results.concat(findEbml(data, paths.slice(1)));
38789        }
38790      }
38791      var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
38792
38793      i += totalLength;
38794    }
38795    return results;
38796  }; // see https://www.matroska.org/technical/basics.html#block-structure
38797
38798  var NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]);
38799  var NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]);
38800  var EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]);
38801  /**
38802   * Expunge any "Emulation Prevention" bytes from a "Raw Byte
38803   * Sequence Payload"
38804   *
38805   * @param data {Uint8Array} the bytes of a RBSP from a NAL
38806   * unit
38807   * @return {Uint8Array} the RBSP without any Emulation
38808   * Prevention Bytes
38809   */
38810
38811  var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
38812    var positions = [];
38813    var i = 1; // Find all `Emulation Prevention Bytes`
38814
38815    while (i < bytes.length - 2) {
38816      if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
38817        positions.push(i + 2);
38818        i++;
38819      }
38820      i++;
38821    } // If no Emulation Prevention Bytes were found just return the original
38822    // array
38823
38824    if (positions.length === 0) {
38825      return bytes;
38826    } // Create a new array to hold the NAL unit data
38827
38828    var newLength = bytes.length - positions.length;
38829    var newData = new Uint8Array(newLength);
38830    var sourceIndex = 0;
38831    for (i = 0; i < newLength; sourceIndex++, i++) {
38832      if (sourceIndex === positions[0]) {
38833        // Skip this byte
38834        sourceIndex++; // Remove this position index
38835
38836        positions.shift();
38837      }
38838      newData[i] = bytes[sourceIndex];
38839    }
38840    return newData;
38841  };
38842  var findNal = function findNal(bytes, dataType, types, nalLimit) {
38843    if (nalLimit === void 0) {
38844      nalLimit = Infinity;
38845    }
38846    bytes = toUint8(bytes);
38847    types = [].concat(types);
38848    var i = 0;
38849    var nalStart;
38850    var nalsFound = 0; // keep searching until:
38851    // we reach the end of bytes
38852    // we reach the maximum number of nals they want to seach
38853    // NOTE: that we disregard nalLimit when we have found the start
38854    // of the nal we want so that we can find the end of the nal we want.
38855
38856    while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
38857      var nalOffset = void 0;
38858      if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
38859        nalOffset = 4;
38860      } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
38861        nalOffset = 3;
38862      } // we are unsynced,
38863      // find the next nal unit
38864
38865      if (!nalOffset) {
38866        i++;
38867        continue;
38868      }
38869      nalsFound++;
38870      if (nalStart) {
38871        return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
38872      }
38873      var nalType = void 0;
38874      if (dataType === 'h264') {
38875        nalType = bytes[i + nalOffset] & 0x1f;
38876      } else if (dataType === 'h265') {
38877        nalType = bytes[i + nalOffset] >> 1 & 0x3f;
38878      }
38879      if (types.indexOf(nalType) !== -1) {
38880        nalStart = i + nalOffset;
38881      } // nal header is 1 length for h264, and 2 for h265
38882
38883      i += nalOffset + (dataType === 'h264' ? 1 : 2);
38884    }
38885    return bytes.subarray(0, 0);
38886  };
38887  var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
38888    return findNal(bytes, 'h264', type, nalLimit);
38889  };
38890  var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
38891    return findNal(bytes, 'h265', type, nalLimit);
38892  };
38893
38894  var CONSTANTS = {
38895    // "webm" string literal in hex
38896    'webm': toUint8([0x77, 0x65, 0x62, 0x6d]),
38897    // "matroska" string literal in hex
38898    'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
38899    // "fLaC" string literal in hex
38900    'flac': toUint8([0x66, 0x4c, 0x61, 0x43]),
38901    // "OggS" string literal in hex
38902    'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]),
38903    // ac-3 sync byte, also works for ec-3 as that is simply a codec
38904    // of ac-3
38905    'ac3': toUint8([0x0b, 0x77]),
38906    // "RIFF" string literal in hex used for wav and avi
38907    'riff': toUint8([0x52, 0x49, 0x46, 0x46]),
38908    // "AVI" string literal in hex
38909    'avi': toUint8([0x41, 0x56, 0x49]),
38910    // "WAVE" string literal in hex
38911    'wav': toUint8([0x57, 0x41, 0x56, 0x45]),
38912    // "ftyp3g" string literal in hex
38913    '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
38914    // "ftyp" string literal in hex
38915    'mp4': toUint8([0x66, 0x74, 0x79, 0x70]),
38916    // "styp" string literal in hex
38917    'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]),
38918    // "ftypqt" string literal in hex
38919    'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
38920    // moov string literal in hex
38921    'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]),
38922    // moof string literal in hex
38923    'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66])
38923
38924  };
38925  var _isLikely = {
38926    aac: function aac(bytes) {
38927      var offset = getId3Offset(bytes);
38928      return bytesMatch(bytes, [0xFF, 0x10], {
38929        offset: offset,
38930        mask: [0xFF, 0x16]
38931      });
38932    },
38933    mp3: function mp3(bytes) {
38934      var offset = getId3Offset(bytes);
38935      return bytesMatch(bytes, [0xFF, 0x02], {
38936        offset: offset,
38937        mask: [0xFF, 0x06]
38938      });
38939    },
38940    webm: function webm(bytes) {
vendor: 5,962 bytes, lines 38941-39130
38941      var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
38942
38943      return bytesMatch(docType, CONSTANTS.webm);
38944    },
38945    mkv: function mkv(bytes) {
38946      var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
38947
38948      return bytesMatch(docType, CONSTANTS.matroska);
38949    },
38950    mp4: function mp4(bytes) {
38951      // if this file is another base media file format, it is not mp4
38952      if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
38953        return false;
38954      } // if this file starts with a ftyp or styp box its mp4
38955
38956      if (bytesMatch(bytes, CONSTANTS.mp4, {
38957        offset: 4
38958      }) || bytesMatch(bytes, CONSTANTS.fmp4, {
38959        offset: 4
38960      })) {
38961        return true;
38962      } // if this file starts with a moof/moov box its mp4
38963
38964      if (bytesMatch(bytes, CONSTANTS.moof, {
38965        offset: 4
38966      }) || bytesMatch(bytes, CONSTANTS.moov, {
38967        offset: 4
38968      })) {
38969        return true;
38970      }
38971    },
38972    mov: function mov(bytes) {
38973      return bytesMatch(bytes, CONSTANTS.mov, {
38974        offset: 4
38975      });
38976    },
38977    '3gp': function gp(bytes) {
38978      return bytesMatch(bytes, CONSTANTS['3gp'], {
38979        offset: 4
38980      });
38981    },
38982    ac3: function ac3(bytes) {
38983      var offset = getId3Offset(bytes);
38984      return bytesMatch(bytes, CONSTANTS.ac3, {
38985        offset: offset
38986      });
38987    },
38988    ts: function ts(bytes) {
38989      if (bytes.length < 189 && bytes.length >= 1) {
38990        return bytes[0] === 0x47;
38991      }
38992      var i = 0; // check the first 376 bytes for two matching sync bytes
38993
38994      while (i + 188 < bytes.length && i < 188) {
38995        if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
38996          return true;
38997        }
38998        i += 1;
38999      }
39000      return false;
39001    },
39002    flac: function flac(bytes) {
39003      var offset = getId3Offset(bytes);
39004      return bytesMatch(bytes, CONSTANTS.flac, {
39005        offset: offset
39006      });
39007    },
39008    ogg: function ogg(bytes) {
39009      return bytesMatch(bytes, CONSTANTS.ogg);
39010    },
39011    avi: function avi(bytes) {
39012      return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, {
39013        offset: 8
39014      });
39015    },
39016    wav: function wav(bytes) {
39017      return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, {
39018        offset: 8
39019      });
39020    },
39021    'h264': function h264(bytes) {
39022      // find seq_parameter_set_rbsp
39023      return findH264Nal(bytes, 7, 3).length;
39024    },
39025    'h265': function h265(bytes) {
39026      // find video_parameter_set_rbsp or seq_parameter_set_rbsp
39027      return findH265Nal(bytes, [32, 33], 3).length;
39028    }
39029  }; // get all the isLikely functions
39030  // but make sure 'ts' is above h264 and h265
39031  // but below everything else as it is the least specific
39032
39033  var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
39034  .filter(function (t) {
39035    return t !== 'ts' && t !== 'h264' && t !== 'h265';
39036  }) // add it back to the bottom
39037  .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
39038
39039  isLikelyTypes.forEach(function (type) {
39040    var isLikelyFn = _isLikely[type];
39041    _isLikely[type] = function (bytes) {
39042      return isLikelyFn(toUint8(bytes));
39043    };
39044  }); // export after wrapping
39045
39046  var isLikely = _isLikely; // A useful list of file signatures can be found here
39047  // https://en.wikipedia.org/wiki/List_of_file_signatures
39048
39049  var detectContainerForBytes = function detectContainerForBytes(bytes) {
39050    bytes = toUint8(bytes);
39051    for (var i = 0; i < isLikelyTypes.length; i++) {
39052      var type = isLikelyTypes[i];
39053      if (isLikely[type](bytes)) {
39054        return type;
39055      }
39056    }
39057    return '';
39058  }; // fmp4 is not a container
39059
39060  var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
39061    return findBox(bytes, ['moof']).length > 0;
39062  };
39063
39064  /**
39065   * mux.js
39066   *
39067   * Copyright (c) Brightcove
39068   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39069   */
39070  var ONE_SECOND_IN_TS = 90000,
39071    // 90kHz clock
39072    secondsToVideoTs,
39073    secondsToAudioTs,
39074    videoTsToSeconds,
39075    audioTsToSeconds,
39076    audioTsToVideoTs,
39077    videoTsToAudioTs,
39078    metadataTsToSeconds;
39079  secondsToVideoTs = function (seconds) {
39080    return seconds * ONE_SECOND_IN_TS;
39081  };
39082  secondsToAudioTs = function (seconds, sampleRate) {
39083    return seconds * sampleRate;
39084  };
39085  videoTsToSeconds = function (timestamp) {
39086    return timestamp / ONE_SECOND_IN_TS;
39087  };
39088  audioTsToSeconds = function (timestamp, sampleRate) {
39089    return timestamp / sampleRate;
39090  };
39091  audioTsToVideoTs = function (timestamp, sampleRate) {
39092    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
39093  };
39094  videoTsToAudioTs = function (timestamp, sampleRate) {
39095    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
39096  };
39097
39098  /**
39099   * Adjust ID3 tag or caption timing information by the timeline pts values
39100   * (if keepOriginalTimestamps is false) and convert to seconds
39101   */
39102  metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
39103    return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
39104  };
39105  var clock = {
39106    ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
39107    secondsToVideoTs: secondsToVideoTs,
39108    secondsToAudioTs: secondsToAudioTs,
39109    videoTsToSeconds: videoTsToSeconds,
39110    audioTsToSeconds: audioTsToSeconds,
39111    audioTsToVideoTs: audioTsToVideoTs,
39112    videoTsToAudioTs: videoTsToAudioTs,
39113    metadataTsToSeconds: metadataTsToSeconds
39114  };
39115  var clock_1 = clock.ONE_SECOND_IN_TS;
39116
39117  /*! @name @videojs/http-streaming @version 3.9.1 @license Apache-2.0 */
39118
39119  /**
39120   * @file resolve-url.js - Handling how URLs are resolved and manipulated
39121   */
39122  const resolveUrl = resolveUrl$1;
39123  /**
39124   * If the xhr request was redirected, return the responseURL, otherwise,
39125   * return the original url.
39126   *
39127   * @api private
39128   *
39129   * @param  {string} url - an url being requested
39130   * @param  {XMLHttpRequest}
39130 req - xhr request result
39131   *
39132   * @return {string}
39133   */
39134
39135  const resolveManifestRedirect = (url, req) => {
39136    // To understand how the responseURL below is set and generated:
39137    // - https://fetch.spec.whatwg.org/#concept-response-url
39138    // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
39139    if (req && req.responseURL && url !== req.responseURL) {
39140      return req.responseURL;
39141    }
39142    return url;
39143  };
39144  const logger = source => {
39145    if (videojs.log.debug) {
39146      return videojs.log.debug.bind(videojs, 'VHS:', `${source} >`);
39147    }
39148    return function () {};
39149  };
39150
39151  /**
39152   * Provides a compatibility layer between Video.js 7 and 8 API changes for VHS.
39153   */
39154  /**
39155   * Delegates to videojs.obj.merge (Video.js 8) or
39156   * videojs.mergeOptions (Video.js 7).
39157   */
39158
39159  function merge(...args) {
39160    const context = videojs.obj || videojs;
39161    const fn = context.merge || context.mergeOptions;
39162    return fn.apply(context, args);
39163  }
39164  /**
39165   * Delegates to videojs.time.createTimeRanges (Video.js 8) or
39166   * videojs.createTimeRanges (Video.js 7).
39167   */
39168
39169  function createTimeRanges(...args) {
39170    const context = videojs.time || videojs;
39171    const fn = context.createTimeRanges || context.createTimeRanges;
39172    return fn.apply(context, args)
vendor: 14,013 bytes, lines 39172-39654
39172;
39173  }
39174
39175  /**
39176   * ranges
39177   *
39178   * Utilities for working with TimeRanges.
39179   *
39180   */
39181
39182  const TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
39183  // can be misleading because of precision differences or when the current media has poorly
39184  // aligned audio and video, which can cause values to be slightly off from what you would
39185  // expect. This value is what we consider to be safe to use in such comparisons to account
39186  // for these scenarios.
39187
39188  const SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
39189  const filterRanges = function (timeRanges, predicate) {
39190    const results = [];
39191    let i;
39192    if (timeRanges && timeRanges.length) {
39193      // Search for ranges that match the predicate
39194      for (i = 0; i < timeRanges.length; i++) {
39195        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
39196          results.push([timeRanges.start(i), timeRanges.end(i)]);
39197        }
39198      }
39199    }
39200    return createTimeRanges(results);
39201  };
39202  /**
39203   * Attempts to find the buffered TimeRange that contains the specified
39204   * time.
39205   *
39206   * @param {TimeRanges} buffered - the TimeRanges object to query
39207   * @param {number} time  - the time to filter on.
39208   * @return {TimeRanges} a new TimeRanges object
39209   */
39210
39211  const findRange = function (buffered, time) {
39212    return filterRanges(buffered, function (start, end) {
39213      return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
39214    });
39215  };
39216  /**
39217   * Returns the TimeRanges that begin later than the specified time.
39218   *
39219   * @param {TimeRanges} timeRanges - the TimeRanges object to query
39220   * @param {number} time - the time to filter on.
39221   * @return {TimeRanges} a new TimeRanges object.
39222   */
39223
39224  const findNextRange = function (timeRanges, time) {
39225    return filterRanges(timeRanges, function (start) {
39226      return start - TIME_FUDGE_FACTOR >= time;
39227    });
39228  };
39229  /**
39230   * Returns gaps within a list of TimeRanges
39231   *
39232   * @param {TimeRanges} buffered - the TimeRanges object
39233   * @return {TimeRanges} a TimeRanges object of gaps
39234   */
39235
39236  const findGaps = function (buffered) {
39237    if (buffered.length < 2) {
39238      return createTimeRanges();
39239    }
39240    const ranges = [];
39241    for (let i = 1; i < buffered.length; i++) {
39242      const start = buffered.end(i - 1);
39243      const end = buffered.start(i);
39244      ranges.push([start, end]);
39245    }
39246    return createTimeRanges(ranges);
39247  };
39248  /**
39249   * Calculate the intersection of two TimeRanges
39250   *
39251   * @param {TimeRanges} bufferA
39252   * @param {TimeRanges} bufferB
39253   * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
39254   */
39255
39256  const bufferIntersection = function (bufferA, bufferB) {
39257    let start = null;
39258    let end = null;
39259    let arity = 0;
39260    const extents = [];
39261    const ranges = [];
39262    if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
39263      return createTimeRanges();
39264    } // Handle the case where we have both buffers and create an
39265    // intersection of the two
39266
39267    let count = bufferA.length; // A) Gather up all start and end times
39268
39269    while (count--) {
39270      extents.push({
39271        time: bufferA.start(count),
39272        type: 'start'
39273      });
39274      extents.push({
39275        time: bufferA.end(count),
39276        type: 'end'
39277      });
39278    }
39279    count = bufferB.length;
39280    while (count--) {
39281      extents.push({
39282        time: bufferB.start(count),
39283        type: 'start'
39284      });
39285      extents.push({
39286        time: bufferB.end(count),
39287        type: 'end'
39288      });
39289    } // B) Sort them by time
39290
39291    extents.sort(function (a, b) {
39292      return a.time - b.time;
39293    }); // C) Go along one by one incrementing arity for start and decrementing
39294    //    arity for ends
39295
39296    for (count = 0; count < extents.length; count++) {
39297      if (extents[count].type === 'start') {
39298        arity++; // D) If arity is ever incremented to 2 we are entering an
39299        //    overlapping range
39300
39301        if (arity === 2) {
39302          start = extents[count].time;
39303        }
39304      } else if (extents[count].type === 'end') {
39305        arity--; // E) If arity is ever decremented to 1 we leaving an
39306        //    overlapping range
39307
39308        if (arity === 1) {
39309          end = extents[count].time;
39310        }
39311      } // F) Record overlapping ranges
39312
39313      if (start !== null && end !== null) {
39314        ranges.push([start, end]);
39315        start = null;
39316        end = null;
39317      }
39318    }
39319    return createTimeRanges(ranges);
39320  };
39321  /**
39322   * Gets a human readable string for a TimeRange
39323   *
39324   * @param {TimeRange} range
39325   * @return {string} a human readable string
39326   */
39327
39328  const printableRange = range => {
39329    const strArr = [];
39330    if (!range || !range.length) {
39331      return '';
39332    }
39333    for (let i = 0; i < range.length; i++) {
39334      strArr.push(range.start(i) + ' => ' + range.end(i));
39335    }
39336    return strArr.join(', ');
39337  };
39338  /**
39339   * Calculates the amount of time left in seconds until the player hits the end of the
39340   * buffer and causes a rebuffer
39341   *
39342   * @param {TimeRange} buffered
39343   *        The state of the buffer
39344   * @param {Numnber} currentTime
39345   *        The current time of the player
39346   * @param {number} playbackRate
39347   *        The current playback rate of the player. Defaults to 1.
39348   * @return {number}
39349   *         Time until the player has to start rebuffering in seconds.
39350   * @function timeUntilRebuffer
39351   */
39352
39353  const timeUntilRebuffer = function (buffered, currentTime, playbackRate = 1) {
39354    const bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
39355    return (bufferedEnd - currentTime) / playbackRate;
39356  };
39357  /**
39358   * Converts a TimeRanges object into an array representation
39359   *
39360   * @param {TimeRanges} timeRanges
39361   * @return {Array}
39362   */
39363
39364  const timeRangesToArray = timeRanges => {
39365    const timeRangesList = [];
39366    for (let i = 0; i < timeRanges.length; i++) {
39367      timeRangesList.push({
39368        start: timeRanges.start(i),
39369        end: timeRanges.end(i)
39370      });
39371    }
39372    return timeRangesList;
39373  };
39374  /**
39375   * Determines if two time range objects are different.
39376   *
39377   * @param {TimeRange} a
39378   *        the first time range object to check
39379   *
39380   * @param {TimeRange} b
39381   *        the second time range object to check
39382   *
39383   * @return {Boolean}
39384   *         Whether the time range objects differ
39385   */
39386
39387  const isRangeDifferent = function (a, b) {
39388    // same object
39389    if (a === b) {
39390      return false;
39391    } // one or the other is undefined
39392
39393    if (!a && b || !b && a) {
39394      return true;
39395    } // length is different
39396
39397    if (a.length !== b.length) {
39398      return true;
39399    } // see if any start/end pair is different
39400
39401    for (let i = 0; i < a.length; i++) {
39402      if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
39403        return true;
39404      }
39405    } // if the length and every pair is the same
39406    // this is the same time range
39407
39408    return false;
39409  };
39410  const lastBufferedEnd = function (a) {
39411    if (!a || !a.length || !a.end) {
39412      return;
39413    }
39414    return a.end(a.length - 1);
39415  };
39416  /**
39417   * A utility function to add up the amount of time in a timeRange
39418   * after a specified startTime.
39419   * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
39420   *     would return 40 as there are 40s seconds after 0 in the timeRange
39421   *
39422   * @param {TimeRange} range
39423   *        The range to check against
39424   * @param {number} startTime
39425   *        The time in the time range that you should start counting from
39426   *
39427   * @return {number}
39428   *          The number of seconds in the buffer passed the specified time.
39429   */
39430
39431  const timeAheadOf = function (range, startTime) {
39432    let time = 0;
39433    if (!range || !range.length) {
39434      return time;
39435    }
39436    for (let i = 0; i < range.length; i++) {
39437      const start = range.start(i);
39438      const end = range.end(i); // startTime is after this range entirely
39439
39440      if (startTime > end) {
39441        continue;
39442      } // startTime is within this range
39443
39444      if (startTime > start && startTime <= end) {
39445        time += end - startTime;
39446        continue;
39447      } // startTime is before this range.
39448
39449      time += end - start;
39450    }
39451    return time;
39452  };
39453
39454  /**
39455   * @file playlist.js
39456   *
39457   * Playlist related utilities.
39458   */
39459  /**
39460   * Get the duration of a segment, with special cases for
39461   * llhls segments that do not have a duration yet.
39462   *
39463   * @param {Object} playlist
39464   *        the playlist that the segment belongs to.
39465   * @param {Object} segment
39466   *        the segment to get a duration for.
39467   *
39468   * @return {number}
39469   *          the segment duration
39470   */
39471
39472  const segmentDurationWithParts = (playlist, segment) => {
39473    // if this isn't a preload segment
39474    // then we will have a segment duration that is accurate.
39475    if (!segment.preload) {
39476      return segment.duration;
39477    } // otherwise we have to add up parts and preload hints
39478    // to get an up to date duration.
39479
39480    let result = 0;
39481    (segment.parts || []).forEach(function (p) {
39482      result += p.duration;
39483    }); // for preload hints we have to use partTargetDuration
39484    // as they won't even have a duration yet.
39485
39486    (segment.preloadHints || []).forEach(function (p) {
39487      if (p.type === 'PART') {
39488        result += playlist.partTargetDuration;
39489      }
39490    });
39491    return result;
39492  };
39493  /**
39494   * A function to get a combined list of parts and segments with durations
39495   * and indexes.
39496   *
39497   * @param {Playlist} playlist the playlist to get the list for.
39498   *
39499   * @return {Array} The part/segment list.
39500   */
39501
39502  const getPartsAndSegments = playlist => (playlist.segments || []).reduce((acc, segment, si) => {
39503    if (segment.parts) {
39504      segment.parts.forEach(function (part, pi) {
39505        acc.push({
39506          duration: part.duration,
39507          segmentIndex: si,
39508          partIndex: pi,
39509          part,
39510          segment
39511        });
39512      });
39513    } else {
39514      acc.push({
39515        duration: segment.duration,
39516        segmentIndex: si,
39517        partIndex: null,
39518        segment,
39519        part: null
39520      });
39521    }
39522    return acc;
39523  }, []);
39524  const getLastParts = media => {
39525    const lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
39526    return lastSegment && lastSegment.parts || [];
39527  };
39528  const getKnownPartCount = ({
39529    preloadSegment
39530  }) => {
39531    if (!preloadSegment) {
39532      return;
39533    }
39534    const {
39535      parts,
39536      preloadHints
39537    } = preloadSegment;
39538    let partCount = (preloadHints || []).reduce((count, hint) => count + (hint.type === 'PART' ? 1 : 0), 0);
39539    partCount += parts && parts.length ? parts.length : 0;
39540    return partCount;
39541  };
39542  /**
39543   * Get the number of seconds to delay from the end of a
39544   * live playlist.
39545   *
39546   * @param {Playlist} main the main playlist
39547   * @param {Playlist} media the media playlist
39548   * @return {number} the hold back in seconds.
39549   */
39550
39551  const liveEdgeDelay = (main, media) => {
39552    if (media.endList) {
39553      return 0;
39554    } // dash suggestedPresentationDelay trumps everything
39555
39556    if (main && main.suggestedPresentationDelay) {
39557      return main.suggestedPresentationDelay;
39558    }
39559    const hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
39560
39561    if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
39562      return media.serverControl.partHoldBack;
39563    } else if (hasParts && media.partTargetDuration) {
39564      return media.partTargetDuration * 3; // finally look for full segment delays
39565    } else if (media.serverControl && media.serverControl.holdBack) {
39566      return media.serverControl.holdBack;
39567    } else if (media.targetDuration) {
39568      return media.targetDuration * 3;
39569    }
39570    return 0;
39571  };
39572  /**
39573   * walk backward until we find a duration we can use
39574   * or return a failure
39575   *
39576   * @param {Playlist} playlist the playlist to walk through
39577   * @param {Number} endSequence the mediaSequence to stop walking on
39578   */
39579
39580  const backwardDuration = function (playlist, endSequence) {
39581    let result = 0;
39582    let i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
39583    // the interval, use it
39584
39585    let segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
39586    // information that is earlier than endSequence
39587
39588    if (segment) {
39589      if (typeof segment.start !== 'undefined') {
39590        return {
39591          result: segment.start,
39592          precise: true
39593        };
39594      }
39595      if (typeof segment.end !== 'undefined') {
39596        return {
39597          result: segment.end - segment.duration,
39598          precise: true
39599        };
39600      }
39601    }
39602    while (i--) {
39603      segment = playlist.segments[i];
39604      if (typeof segment.end !== 'undefined') {
39605        return {
39606          result: result + segment.end,
39607          precise: true
39608        };
39609      }
39610      result += segmentDurationWithParts(playlist, segment);
39611      if (typeof segment.start !== 'undefined') {
39612        return {
39613          result: result + segment.start,
39614          precise: true
39615        };
39616      }
39617    }
39618    return {
39619      result,
39620      precise: false
39621    };
39622  };
39623  /**
39624   * walk forward until we find a duration we can use
39625   * or return a failure
39626   *
39627   * @param {Playlist} playlist the playlist to walk through
39628   * @param {number} endSequence the mediaSequence to stop walking on
39629   */
39630
39631  const forwardDuration = function (playlist, endSequence) {
39632    let result = 0;
39633    let segment;
39634    let i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
39635    // information
39636
39637    for (; i < playlist.segments.length; i++) {
39638      segment = playlist.segments[i];
39639      if (typeof segment.start !== 'undefined') {
39640        return {
39641          result: segment.start - result,
39642          precise: true
39643        };
39644      }
39645      result += segmentDurationWithParts(playlist, segment);
39646      if (typeof segment.end !== 'undefined') {
39647        return {
39648          result: segment.end - result,
39649          precise: true
39650        };
39651      }
39652    } // indicate we didn't find a useful duration estimate
39653
39654    return {
vendor: 5,055 bytes, lines 39655-39782
39655      result: -1,
39656      precise: false
39657    };
39658  };
39659  /**
39660    * Calculate the media duration from the segments associated with a
39661    * playlist. The duration of a subinterval of the available segments
39662    * may be calculated by specifying an end index.
39663    *
39664    * @param {Object} playlist a media playlist object
39665    * @param {number=} endSequence an exclusive upper boundary
39666    * for the playlist.  Defaults to playlist length.
39667    * @param {number} expired the amount of time that has dropped
39668    * off the front of the playlist in a live scenario
39669    * @return {number} the duration between the first available segment
39670    * and end index.
39671    */
39672
39673  const intervalDuration = function (playlist, endSequence, expired) {
39674    if (typeof endSequence === 'undefined') {
39675      endSequence = playlist.mediaSequence + playlist.segments.length;
39676    }
39677    if (endSequence < playlist.mediaSequence) {
39678      return 0;
39679    } // do a backward walk to estimate the duration
39680
39681    const backward = backwardDuration(playlist, endSequence);
39682    if (backward.precise) {
39683      // if we were able to base our duration estimate on timing
39684      // information provided directly from the Media Source, return
39685      // it
39686      return backward.result;
39687    } // walk forward to see if a precise duration estimate can be made
39688    // that way
39689
39690    const forward = forwardDuration(playlist, endSequence);
39691    if (forward.precise) {
39692      // we found a segment that has been buffered and so it's
39693      // position is known precisely
39694      return forward.result;
39695    } // return the less-precise, playlist-based duration estimate
39696
39697    return backward.result + expired;
39698  };
39699  /**
39700    * Calculates the duration of a playlist. If a start and end index
39701    * are specified, the duration will be for the subset of the media
39702    * timeline between those two indices. The total duration for live
39703    * playlists is always Infinity.
39704    *
39705    * @param {Object} playlist a media playlist object
39706    * @param {number=} endSequence an exclusive upper
39707    * boundary for the playlist. Defaults to the playlist media
39708    * sequence number plus its length.
39709    * @param {number=} expired the amount of time that has
39710    * dropped off the front of the playlist in a live scenario
39711    * @return {number} the duration between the start index and end
39712    * index.
39713    */
39714
39715  const duration = function (playlist, endSequence, expired) {
39716    if (!playlist) {
39717      return 0;
39718    }
39719    if (typeof expired !== 'number') {
39720      expired = 0;
39721    } // if a slice of the total duration is not requested, use
39722    // playlist-level duration indicators when they're present
39723
39724    if (typeof endSequence === 'undefined') {
39725      // if present, use the duration specified in the playlist
39726      if (playlist.totalDuration) {
39727        return playlist.totalDuration;
39728      } // duration should be Infinity for live playlists
39729
39730      if (!playlist.endList) {
39731        return window.Infinity;
39732      }
39733    } // calculate the total duration based on the segment durations
39734
39735    return intervalDuration(playlist, endSequence, expired);
39736  };
39737  /**
39738    * Calculate the time between two indexes in the current playlist
39739    * neight the start- nor the end-index need to be within the current
39740    * playlist in which case, the targetDuration of the playlist is used
39741    * to approximate the durations of the segments
39742    *
39743    * @param {Array} options.durationList list to iterate over for durations.
39744    * @param {number} options.defaultDuration duration to use for elements before or after the durationList
39745    * @param {number} options.startIndex partsAndSegments index to start
39746    * @param {number} options.endIndex partsAndSegments index to end.
39747    * @return {number} the number of seconds between startIndex and endIndex
39748    */
39749
39750  const sumDurations = function ({
39751    defaultDuration,
39752    durationList,
39753    startIndex,
39754    endIndex
39755  }) {
39756    let durations = 0;
39757    if (startIndex > endIndex) {
39758      [startIndex, endIndex] = [endIndex, startIndex];
39759    }
39760    if (startIndex < 0) {
39761      for (let i = startIndex; i < Math.min(0, endIndex); i++) {
39762        durations += defaultDuration;
39763      }
39764      startIndex = 0;
39765    }
39766    for (let i = startIndex; i < endIndex; i++) {
39767      durations += durationList[i].duration;
39768    }
39769    return durations;
39770  };
39771  /**
39772   * Calculates the playlist end time
39773   *
39774   * @param {Object} playlist a media playlist object
39775   * @param {number=} expired the amount of time that has
39776   *                  dropped off the front of the playlist in a live scenario
39777   * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
39778   *                        playlist end calculation should consider the safe live end
39779   *                        (truncate the playlist end by three segments). This is normally
39780   *                        used for calculating the end of the playlist's seekable range.
39781   *                        This takes into account the value of liveEdgePadding.
39782   *                        Setting liveEdgePadding to 0 is equivalent to setting this to fal
vendor: 19,363 bytes, lines 39782-40330
39782se.
39783   * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
39784   *                 If this is provided, it is used in the safe live end calculation.
39785   *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
39786   *                 Corresponds to suggestedPresentationDelay in DASH manifests.
39787   * @return {number} the end time of playlist
39788   * @function playlistEnd
39789   */
39790
39791  const playlistEnd = function (playlist, expired, useSafeLiveEnd, liveEdgePadding) {
39792    if (!playlist || !playlist.segments) {
39793      return null;
39794    }
39795    if (playlist.endList) {
39796      return duration(playlist);
39797    }
39798    if (expired === null) {
39799      return null;
39800    }
39801    expired = expired || 0;
39802    let lastSegmentEndTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
39803    if (useSafeLiveEnd) {
39804      liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
39805      lastSegmentEndTime -= liveEdgePadding;
39806    } // don't return a time less than zero
39807
39808    return Math.max(0, lastSegmentEndTime);
39809  };
39810  /**
39811    * Calculates the interval of time that is currently seekable in a
39812    * playlist. The returned time ranges are relative to the earliest
39813    * moment in the specified playlist that is still available. A full
39814    * seekable implementation for live streams would need to offset
39815    * these values by the duration of content that has expired from the
39816    * stream.
39817    *
39818    * @param {Object} playlist a media playlist object
39819    * dropped off the front of the playlist in a live scenario
39820    * @param {number=} expired the amount of time that has
39821    * dropped off the front of the playlist in a live scenario
39822    * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
39823    *        Corresponds to suggestedPresentationDelay in DASH manifests.
39824    * @return {TimeRanges} the periods of time that are valid targets
39825    * for seeking
39826    */
39827
39828  const seekable = function (playlist, expired, liveEdgePadding) {
39829    const useSafeLiveEnd = true;
39830    const seekableStart = expired || 0;
39831    let seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
39832    if (seekableEnd === null) {
39833      return createTimeRanges();
39834    } // Clamp seekable end since it can not be less than the seekable start
39835
39836    if (seekableEnd < seekableStart) {
39837      seekableEnd = seekableStart;
39838    }
39839    return createTimeRanges(seekableStart, seekableEnd);
39840  };
39841  /**
39842   * Determine the index and estimated starting time of the segment that
39843   * contains a specified playback position in a media playlist.
39844   *
39845   * @param {Object} options.playlist the media playlist to query
39846   * @param {number} options.currentTime The number of seconds since the earliest
39847   * possible position to determine the containing segment for
39848   * @param {number} options.startTime the time when the segment/part starts
39849   * @param {number} options.startingSegmentIndex the segment index to start looking at.
39850   * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
39851   *
39852   * @return {Object} an object with partIndex, segmentIndex, and startTime.
39853   */
39854
39855  const getMediaInfoForTime = function ({
39856    playlist,
39857    currentTime,
39858    startingSegmentIndex,
39859    startingPartIndex,
39860    startTime,
39861    exactManifestTimings
39862  }) {
39863    let time = currentTime - startTime;
39864    const partsAndSegments = getPartsAndSegments(playlist);
39865    let startIndex = 0;
39866    for (let i = 0; i < partsAndSegments.length; i++) {
39867      const partAndSegment = partsAndSegments[i];
39868      if (startingSegmentIndex !== partAndSegment.segmentIndex) {
39869        continue;
39870      } // skip this if part index does not match.
39871
39872      if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
39873        continue;
39874      }
39875      startIndex = i;
39876      break;
39877    }
39878    if (time < 0) {
39879      // Walk backward from startIndex in the playlist, adding durations
39880      // until we find a segment that contains `time` and return it
39881      if (startIndex > 0) {
39882        for (let i = startIndex - 1; i >= 0; i--) {
39883          const partAndSegment = partsAndSegments[i];
39884          time += partAndSegment.duration;
39885          if (exactManifestTimings) {
39886            if (time < 0) {
39887              continue;
39888            }
39889          } else if (time + TIME_FUDGE_FACTOR <= 0) {
39890            continue;
39891          }
39892          return {
39893            partIndex: partAndSegment.partIndex,
39894            segmentIndex: partAndSegment.segmentIndex,
39895            startTime: startTime - sumDurations({
39896              defaultDuration: playlist.targetDuration,
39897              durationList: partsAndSegments,
39898              startIndex,
39899              endIndex: i
39900            })
39901          };
39902        }
39903      } // We were unable to find a good segment within the playlist
39904      // so select the first segment
39905
39906      return {
39907        partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
39908        segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
39909        startTime: currentTime
39910      };
39911    } // When startIndex is negative, we first walk forward to first segment
39912    // adding target durations. If we "run out of time" before getting to
39913    // the first segment, return the first segment
39914
39915    if (startIndex < 0) {
39916      for (let i = startIndex; i < 0; i++) {
39917        time -= playlist.targetDuration;
39918        if (time < 0) {
39919          return {
39920            partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
39921            segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
39922            startTime: currentTime
39923          };
39924        }
39925      }
39926      startIndex = 0;
39927    } // Walk forward from startIndex in the playlist, subtracting durations
39928    // until we find a segment that contains `time` and return it
39929
39930    for (let i = startIndex; i < partsAndSegments.length; i++) {
39931      const partAndSegment = partsAndSegments[i];
39932      time -= partAndSegment.duration;
39933      const canUseFudgeFactor = partAndSegment.duration > TIME_FUDGE_FACTOR;
39934      const isExactlyAtTheEnd = time === 0;
39935      const isExtremelyCloseToTheEnd = canUseFudgeFactor && time + TIME_FUDGE_FACTOR >= 0;
39936      if (isExactlyAtTheEnd || isExtremelyCloseToTheEnd) {
39937        // 1) We are exactly at the end of the current segment.
39938        // 2) We are extremely close to the end of the current segment (The difference is less than  1 / 30).
39939        //    We may encounter this situation when
39940        //    we don't have exact match between segment duration info in the manifest and the actual duration of the segment
39941        //    For example:
39942        //    We appended 3 segments 10 seconds each, meaning we should have 30 sec buffered,
39943        //    but we the actual buffered is 29.99999
39944        //
39945        // In both cases:
39946        // if we passed current time -> it means that we already played current segment
39947        // if we passed buffered.end -> it means that this segment is already loaded and buffered
39948        // we should select the next segment if we have one:
39949        if (i !== partsAndSegments.length - 1) {
39950          continue;
39951        }
39952      }
39953      if (exactManifestTimings) {
39954        if (time > 0) {
39955          continue;
39956        }
39957      } else if (time - TIME_FUDGE_FACTOR >= 0) {
39958        continue;
39959      }
39960      return {
39961        partIndex: partAndSegment.partIndex,
39962        segmentIndex: partAndSegment.segmentIndex,
39963        startTime: startTime + sumDurations({
39964          defaultDuration: playlist.targetDuration,
39965          durationList: partsAndSegments,
39966          startIndex,
39967          endIndex: i
39968        })
39969      };
39970    } // We are out of possible candidates so load the last one...
39971
39972    return {
39973      segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
39974      partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
39975      startTime: currentTime
39976    };
39977  };
39978  /**
39979   * Check whether the playlist is excluded or not.
39980   *
39981   * @param {Object} playlist the media playlist object
39982   * @return {boolean} whether the playlist is excluded or not
39983   * @function isExcluded
39984   */
39985
39986  const isExcluded = function (playlist) {
39987    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
39988  };
39989  /**
39990   * Check whether the playlist is compatible with current playback configuration or has
39991   * been excluded permanently for being incompatible.
39992   *
39993   * @param {Object} playlist the media playlist object
39994   * @return {boolean} whether the playlist is incompatible or not
39995   * @function isIncompatible
39996   */
39997
39998  const isIncompatible = function (playlist) {
39999    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
40000  };
40001  /**
40002   * Check whether the playlist is enabled or not.
40003   *
40004   * @param {Object} playlist the media playlist object
40005   * @return {boolean} whether the playlist is enabled or not
40006   * @function isEnabled
40007   */
40008
40009  const isEnabled = function (playlist) {
40010    const excluded = isExcluded(playlist);
40011    return !playlist.disabled && !excluded;
40012  };
40013  /**
40014   * Check whether the playlist has been manually disabled through the representations api.
40015   *
40016   * @param {Object} playlist the media playlist object
40017   * @return {boolean} whether the playlist is disabled manually or not
40018   * @function isDisabled
40019   */
40020
40021  const isDisabled = function (playlist) {
40022    return playlist.disabled;
40023  };
40024  /**
40025   * Returns whether the current playlist is an AES encrypted HLS stream
40026   *
40027   * @return {boolean} true if it's an AES encrypted HLS stream
40028   */
40029
40030  const isAes = function (media) {
40031    for (let i = 0; i < media.segments.length; i++) {
40032      if (media.segments[i].key) {
40033        return true;
40034      }
40035    }
40036    return false;
40037  };
40038  /**
40039   * Checks if the playlist has a value for the specified attribute
40040   *
40041   * @param {string} attr
40042   *        Attribute to check for
40043   * @param {Object} playlist
40044   *        The media playlist object
40045   * @return {boolean}
40046   *         Whether the playlist contains a value for the attribute or not
40047   * @function hasAttribute
40048   */
40049
40050  const hasAttribute = function (attr, playlist) {
40051    return playlist.attributes && playlist.attributes[attr];
40052  };
40053  /**
40054   * Estimates the time required to complete a segment download from the specified playlist
40055   *
40056   * @param {number} segmentDuration
40057   *        Duration of requested segment
40058   * @param {number} bandwidth
40059   *        Current measured bandwidth of the player
40060   * @param {Object} playlist
40061   *        The media playlist object
40062   * @param {number=} bytesReceived
40063   *        Number of bytes already received for the request. Defaults to 0
40064   * @return {number|NaN}
40065   *         The estimated time to request the segment. NaN if bandwidth information for
40066   *         the given playlist is unavailable
40067   * @function estimateSegmentRequestTime
40068   */
40069
40070  const estimateSegmentRequestTime = function (segmentDuration, bandwidth, playlist, bytesReceived = 0) {
40071    if (!hasAttribute('BANDWIDTH', playlist)) {
40072      return NaN;
40073    }
40074    const size = segmentDuration * playlist.attributes.BANDWIDTH;
40075    return (size - bytesReceived * 8) / bandwidth;
40076  };
40077  /*
40078   * Returns whether the current playlist is the lowest rendition
40079   *
40080   * @return {Boolean} true if on lowest rendition
40081   */
40082
40083  const isLowestEnabledRendition = (main, media) => {
40084    if (main.playlists.length === 1) {
40085      return true;
40086    }
40087    const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
40088    return main.playlists.filter(playlist => {
40089      if (!isEnabled(playlist)) {
40090        return false;
40091      }
40092      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
40093    }).length === 0;
40094  };
40095  const playlistMatch = (a, b) => {
40096    // both playlits are null
40097    // or only one playlist is non-null
40098    // no match
40099    if (!a && !b || !a && b || a && !b) {
40100      return false;
40101    } // playlist objects are the same, match
40102
40103    if (a === b) {
40104      return true;
40105    } // first try to use id as it should be the most
40106    // accurate
40107
40108    if (a.id && b.id && a.id === b.id) {
40109      return true;
40110    } // next try to use reslovedUri as it should be the
40111    // second most accurate.
40112
40113    if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
40114      return true;
40115    } // finally try to use uri as it should be accurate
40116    // but might miss a few cases for relative uris
40117
40118    if (a.uri && b.uri && a.uri === b.uri) {
40119      return true;
40120    }
40121    return false;
40122  };
40123  const someAudioVariant = function (main, callback) {
40124    const AUDIO = main && main.mediaGroups && main.mediaGroups.AUDIO || {};
40125    let found = false;
40126    for (const groupName in AUDIO) {
40127      for (const label in AUDIO[groupName]) {
40128        found = callback(AUDIO[groupName][label]);
40129        if (found) {
40130          break;
40131        }
40132      }
40133      if (found) {
40134        break;
40135      }
40136    }
40137    return !!found;
40138  };
40139  const isAudioOnly = main => {
40140    // we are audio only if we have no main playlists but do
40141    // have media group playlists.
40142    if (!main || !main.playlists || !main.playlists.length) {
40143      // without audio variants or playlists this
40144      // is not an audio only main.
40145      const found = someAudioVariant(main, variant => variant.playlists && variant.playlists.length || variant.uri);
40146      return found;
40147    } // if every playlist has only an audio codec it is audio only
40148
40149    for (let i = 0; i < main.playlists.length; i++) {
40150      const playlist = main.playlists[i];
40151      const CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
40152
40153      if (CODECS && CODECS.split(',').every(c => isAudioCodec(c))) {
40154        continue;
40155      } // playlist is in an audio group it is audio only
40156
40157      const found = someAudioVariant(main, variant => playlistMatch(playlist, variant));
40158      if (found) {
40159        continue;
40160      } // if we make it here this playlist isn't audio and we
40161      // are not audio only
40162
40163      return false;
40164    } // if we make it past every playlist without returning, then
40165    // this is an audio only playlist.
40166
40167    return true;
40168  }; // exports
40169
40170  var Playlist = {
40171    liveEdgeDelay,
40172    duration,
40173    seekable,
40174    getMediaInfoForTime,
40175    isEnabled,
40176    isDisabled,
40177    isExcluded,
40178    isIncompatible,
40179    playlistEnd,
40180    isAes,
40181    hasAttribute,
40182    estimateSegmentRequestTime,
40183    isLowestEnabledRendition,
40184    isAudioOnly,
40185    playlistMatch,
40186    segmentDurationWithParts
40187  };
40188  const {
40189    log
40190  } = videojs;
40191  const createPlaylistID = (index, uri) => {
40192    return `${index}-${uri}`;
40193  }; // default function for creating a group id
40194
40195  const groupID = (type, group, label) => {
40196    return `placeholder-uri-${type}-${group}-${label}`;
40197  };
40198  /**
40199   * Parses a given m3u8 playlist
40200   *
40201   * @param {Function} [onwarn]
40202   *        a function to call when the parser triggers a warning event.
40203   * @param {Function} [oninfo]
40204   *        a function to call when the parser triggers an info event.
40205   * @param {string} manifestString
40206   *        The downloaded manifest string
40207   * @param {Object[]} [customTagParsers]
40208   *        An array of custom tag parsers for the m3u8-parser instance
40209   * @param {Object[]} [customTagMappers]
40210   *        An array of custom tag mappers for the m3u8-parser instance
40211   * @param {boolean} [llhls]
40212   *        Whether to keep ll-hls features in the manifest after parsing.
40213   * @return {Object}
40214   *         The manifest object
40215   */
40216
40217  const parseManifest = ({
40218    onwarn,
40219    oninfo,
40220    manifestString,
40221    customTagParsers = [],
40222    customTagMappers = [],
40223    llhls
40224  }) => {
40225    const parser = new Parser();
40226    if (onwarn) {
40227      parser.on('warn', onwarn);
40228    }
40229    if (oninfo) {
40230      parser.on('info', oninfo);
40231    }
40232    customTagParsers.forEach(customParser => parser.addParser(customParser));
40233    customTagMappers.forEach(mapper => parser.addTagMapper(mapper));
40234    parser.push(manifestString);
40235    parser.end();
40236    const manifest = parser.manifest; // remove llhls features from the parsed manifest
40237    // if we don't want llhls support.
40238
40239    if (!llhls) {
40240      ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
40241        if (manifest.hasOwnProperty(k)) {
40242          delete manifest[k];
40243        }
40244      });
40245      if (manifest.segments) {
40246        manifest.segments.forEach(function (segment) {
40247          ['parts', 'preloadHints'].forEach(function (k) {
40248            if (segment.hasOwnProperty(k)) {
40249              delete segment[k];
40250            }
40251          });
40252        });
40253      }
40254    }
40255    if (!manifest.targetDuration) {
40256      let targetDuration = 10;
40257      if (manifest.segments && manifest.segments.length) {
40258        targetDuration = manifest.segments.reduce((acc, s) => Math.max(acc, s.duration), 0);
40259      }
40260      if (onwarn) {
40261        onwarn({
40262          message: `manifest has no targetDuration defaulting to ${targetDuration}`
40263        });
40264      }
40265      manifest.targetDuration = targetDuration;
40266    }
40267    const parts = getLastParts(manifest);
40268    if (parts.length && !manifest.partTargetDuration) {
40269      const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
40270      if (onwarn) {
40271        onwarn({
40272          message: `manifest has no partTargetDuration defaulting to ${partTargetDuration}`
40273        });
40274        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.');
40275      }
40276      manifest.partTargetDuration = partTargetDuration;
40277    }
40278    return manifest;
40279  };
40280  /**
40281   * Loops through all supported media groups in main and calls the provided
40282   * callback for each group
40283   *
40284   * @param {Object} main
40285   *        The parsed main manifest object
40286   * @param {Function} callback
40287   *        Callback to call for each media group
40288   */
40289
40290  const forEachMediaGroup = (main, callback) => {
40291    if (!main.mediaGroups) {
40292      return;
40293    }
40294    ['AUDIO', 'SUBTITLES'].forEach(mediaType => {
40295      if (!main.mediaGroups[mediaType]) {
40296        return;
40297      }
40298      for (const groupKey in main.mediaGroups[mediaType]) {
40299        for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
40300          const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
40301          callback(mediaProperties, mediaType, groupKey, labelKey);
40302        }
40303      }
40304    });
40305  };
40306  /**
40307   * Adds properties and attributes to the playlist to keep consistent functionality for
40308   * playlists throughout VHS.
40309   *
40310   * @param {Object} config
40311   *        Arguments object
40312   * @param {Object} config.playlist
40313   *        The media playlist
40314   * @param {string} [config.uri]
40315   *        The uri to the media playlist (if media playlist is not from within a main
40316   *        playlist)
40317   * @param {string} id
40318   *        ID to use for the playlist
40319   */
40320
40321  const setupMediaPlaylist = ({
40322    playlist,
40323    uri,
40324    id
40325  }) => {
40326    playlist.id = id;
40327    playlist.playlistErrors_ = 0;
40328    if (uri) {
40329      // For media playlists, m3u8-parser does not have access to a URI, as HLS media
40330      // playlists do not contain their own source URI, but one is needed for c
vendor: 9,212 bytes, lines 40330-40580
40330onsistency in
40331      // VHS.
40332      playlist.uri = uri;
40333    } // For HLS main playlists, even though certain attributes MUST be defined, the
40334    // stream may still be played without them.
40335    // For HLS media playlists, m3u8-parser does not attach an attributes object to the
40336    // manifest.
40337    //
40338    // To avoid undefined reference errors through the project, and make the code easier
40339    // to write/read, add an empty attributes object for these cases.
40340
40341    playlist.attributes = playlist.attributes || {};
40342  };
40343  /**
40344   * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
40345   * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
40346   * playlist references to the playlists array.
40347   *
40348   * @param {Object} main
40349   *        The main playlist
40350   */
40351
40352  const setupMediaPlaylists = main => {
40353    let i = main.playlists.length;
40354    while (i--) {
40355      const playlist = main.playlists[i];
40356      setupMediaPlaylist({
40357        playlist,
40358        id: createPlaylistID(i, playlist.uri)
40359      });
40360      playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
40361      main.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
40362
40363      main.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
40364      // the stream can be played without it. Although an attributes property may have been
40365      // added to the playlist to prevent undefined references, issue a warning to fix the
40366      // manifest.
40367
40368      if (!playlist.attributes.BANDWIDTH) {
40369        log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
40370      }
40371    }
40372  };
40373  /**
40374   * Adds resolvedUri properties to each media group.
40375   *
40376   * @param {Object} main
40377   *        The main playlist
40378   */
40379
40380  const resolveMediaGroupUris = main => {
40381    forEachMediaGroup(main, properties => {
40382      if (properties.uri) {
40383        properties.resolvedUri = resolveUrl(main.uri, properties.uri);
40384      }
40385    });
40386  };
40387  /**
40388   * Creates a main playlist wrapper to insert a sole media playlist into.
40389   *
40390   * @param {Object} media
40391   *        Media playlist
40392   * @param {string} uri
40393   *        The media URI
40394   *
40395   * @return {Object}
40396   *         main playlist
40397   */
40398
40399  const mainForMedia = (media, uri) => {
40400    const id = createPlaylistID(0, uri);
40401    const main = {
40402      mediaGroups: {
40403        'AUDIO': {},
40404        'VIDEO': {},
40405        'CLOSED-CAPTIONS': {},
40406        'SUBTITLES': {}
40407      },
40408      uri: window.location.href,
40409      resolvedUri: window.location.href,
40410      playlists: [{
40411        uri,
40412        id,
40413        resolvedUri: uri,
40414        // m3u8-parser does not attach an attributes property to media playlists so make
40415        // sure that the property is attached to avoid undefined reference errors
40416        attributes: {}
40417      }]
40418    }; // set up ID reference
40419
40420    main.playlists[id] = main.playlists[0]; // URI reference added for backwards compatibility
40421
40422    main.playlists[uri] = main.playlists[0];
40423    return main;
40424  };
40425  /**
40426   * Does an in-place update of the main manifest to add updated playlist URI references
40427   * as well as other properties needed by VHS that aren't included by the parser.
40428   *
40429   * @param {Object} main
40430   *        main manifest object
40431   * @param {string} uri
40432   *        The source URI
40433   * @param {function} createGroupID
40434   *        A function to determine how to create the groupID for mediaGroups
40435   */
40436
40437  const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
40438    main.uri = uri;
40439    for (let i = 0; i < main.playlists.length; i++) {
40440      if (!main.playlists[i].uri) {
40441        // Set up phony URIs for the playlists since playlists are referenced by their URIs
40442        // throughout VHS, but some formats (e.g., DASH) don't have external URIs
40443        // TODO: consider adding dummy URIs in mpd-parser
40444        const phonyUri = `placeholder-uri-${i}`;
40445        main.playlists[i].uri = phonyUri;
40446      }
40447    }
40448    const audioOnlyMain = isAudioOnly(main);
40449    forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
40450      // add a playlist array under properties
40451      if (!properties.playlists || !properties.playlists.length) {
40452        // If the manifest is audio only and this media group does not have a uri, check
40453        // if the media group is located in the main list of playlists. If it is, don't add
40454        // placeholder properties as it shouldn't be considered an alternate audio track.
40455        if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
40456          for (let i = 0; i < main.playlists.length; i++) {
40457            const p = main.playlists[i];
40458            if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
40459              return;
40460            }
40461          }
40462        }
40463        properties.playlists = [_extends$1({}, properties)];
40464      }
40465      properties.playlists.forEach(function (p, i) {
40466        const groupId = createGroupID(mediaType, groupKey, labelKey, p);
40467        const id = createPlaylistID(i, groupId);
40468        if (p.uri) {
40469          p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
40470        } else {
40471          // DEPRECATED, this has been added to prevent a breaking change.
40472          // previously we only ever had a single media group playlist, so
40473          // we mark the first playlist uri without prepending the index as we used to
40474          // ideally we would do all of the playlists the same way.
40475          p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
40476          // the placeholder again
40477
40478          p.resolvedUri = p.uri;
40479        }
40480        p.id = p.id || id; // add an empty attributes object, all playlists are
40481        // expected to have this.
40482
40483        p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
40484
40485        main.playlists[p.id] = p;
40486        main.playlists[p.uri] = p;
40487      });
40488    });
40489    setupMediaPlaylists(main);
40490    resolveMediaGroupUris(main);
40491  };
40492  class DateRangesStorage {
40493    constructor() {
40494      this.offset_ = null;
40495      this.pendingDateRanges_ = new Map();
40496      this.processedDateRanges_ = new Map();
40497    }
40498    setOffset(segments = []) {
40499      // already set
40500      if (this.offset_ !== null) {
40501        return;
40502      } // no segment to process
40503
40504      if (!segments.length) {
40505        return;
40506      }
40507      const [firstSegment] = segments; // no program date time
40508
40509      if (firstSegment.programDateTime === undefined) {
40510        return;
40511      } // Set offset as ProgramDateTime for the very first segment of the very first playlist load:
40512
40513      this.offset_ = firstSegment.programDateTime / 1000;
40514    }
40515    setPendingDateRanges(dateRanges = []) {
40516      if (!dateRanges.length) {
40517        return;
40518      }
40519      const [dateRange] = dateRanges;
40520      const startTime = dateRange.startDate.getTime();
40521      this.trimProcessedDateRanges_(startTime);
40522      this.pendingDateRanges_ = dateRanges.reduce((map, pendingDateRange) => {
40523        map.set(pendingDateRange.id, pendingDateRange);
40524        return map;
40525      }, new Map());
40526    }
40527    processDateRange(dateRange) {
40528      this.pendingDateRanges_.delete(dateRange.id);
40529      this.processedDateRanges_.set(dateRange.id, dateRange);
40530    }
40531    getDateRangesToProcess() {
40532      if (this.offset_ === null) {
40533        return [];
40534      }
40535      const dateRangeClasses = {};
40536      const dateRangesToProcess = [];
40537      this.pendingDateRanges_.forEach((dateRange, id) => {
40538        if (this.processedDateRanges_.has(id)) {
40539          return;
40540        }
40541        dateRange.startTime = dateRange.startDate.getTime() / 1000 - this.offset_;
40542        dateRange.processDateRange = () => this.processDateRange(dateRange);
40543        dateRangesToProcess.push(dateRange);
40544        if (!dateRange.class) {
40545          return;
40546        }
40547        if (dateRangeClasses[dateRange.class]) {
40548          const length = dateRangeClasses[dateRange.class].push(dateRange);
40549          dateRange.classListIndex = length - 1;
40550        } else {
40551          dateRangeClasses[dateRange.class] = [dateRange];
40552          dateRange.classListIndex = 0;
40553        }
40554      });
40555      for (const dateRange of dateRangesToProcess) {
40556        const classList = dateRangeClasses[dateRange.class] || [];
40557        if (dateRange.endDate) {
40558          dateRange.endTime = dateRange.endDate.getTime() / 1000 - this.offset_;
40559        } else if (dateRange.endOnNext && classList[dateRange.classListIndex + 1]) {
40560          dateRange.endTime = classList[dateRange.classListIndex + 1].startTime;
40561        } else if (dateRange.duration) {
40562          dateRange.endTime = dateRange.startTime + dateRange.duration;
40563        } else if (dateRange.plannedDuration) {
40564          dateRange.endTime = dateRange.startTime + dateRange.plannedDuration;
40565        } else {
40566          dateRange.endTime = dateRange.startTime;
40567        }
40568      }
40569      return dateRangesToProcess;
40570    }
40571    trimProcessedDateRanges_(startTime) {
40572      const copy = new Map(this.processedDateRanges_);
40573      copy.forEach((dateRange, id) => {
40574        if (dateRange.startDate.getTime() < startTime) {
40575          this.processedDateRanges_.delete(id);
40576        }
40577      });
40578    }
40579  }
40580  const 
40580{
40581    EventTarget: EventTarget$1
40582  } = videojs;
40583  const addLLHLSQueryDirectives = (uri, media) => {
40584    if (media.endList || !media.serverControl) {
40585      return uri;
40586    }
40587    const parameters = {};
40588    if (media.serverControl.canBlockReload) {
40589      const {
40590        preloadSegment
40591      } = media; // next msn is a zero based value, length is not.
40592
40593      let nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
40594      // that we are going to request a part of that preload segment.
40595      // the logic below is used to determine that.
40596
40597      if (preloadSegment) {
40598        const parts = preloadSegment.parts || []; // _HLS_part is a zero based index
40599
40600        const nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
vendor: 26,146 bytes, lines 40601-41309
40601        // length of parts, then we know we had part preload hints
40602        // and we need to add the _HLS_part= query
40603
40604        if (nextPart > -1 && nextPart !== parts.length - 1) {
40605          // add existing parts to our preload hints
40606          // eslint-disable-next-line
40607          parameters._HLS_part = nextPart;
40608        } // this if statement makes sure that we request the msn
40609        // of the preload segment if:
40610        // 1. the preload segment had parts (and was not yet a full segment)
40611        //    but was added to our segments array
40612        // 2. the preload segment had preload hints for parts that are not in
40613        //    the manifest yet.
40614        // in all other cases we want the segment after the preload segment
40615        // which will be given by using media.segments.length because it is 1 based
40616        // rather than 0 based.
40617
40618        if (nextPart > -1 || parts.length) {
40619          nextMSN--;
40620        }
40621      } // add _HLS_msn= in front of any _HLS_part query
40622      // eslint-disable-next-line
40623
40624      parameters._HLS_msn = nextMSN;
40625    }
40626    if (media.serverControl && media.serverControl.canSkipUntil) {
40627      // add _HLS_skip= infront of all other queries.
40628      // eslint-disable-next-line
40629      parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
40630    }
40631    if (Object.keys(parameters).length) {
40632      const parsedUri = new window.URL(uri);
40633      ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
40634        if (!parameters.hasOwnProperty(name)) {
40635          return;
40636        }
40637        parsedUri.searchParams.set(name, parameters[name]);
40638      });
40639      uri = parsedUri.toString();
40640    }
40641    return uri;
40642  };
40643  /**
40644   * Returns a new segment object with properties and
40645   * the parts array merged.
40646   *
40647   * @param {Object} a the old segment
40648   * @param {Object} b the new segment
40649   *
40650   * @return {Object} the merged segment
40651   */
40652
40653  const updateSegment = (a, b) => {
40654    if (!a) {
40655      return b;
40656    }
40657    const result = merge(a, b); // if only the old segment has preload hints
40658    // and the new one does not, remove preload hints.
40659
40660    if (a.preloadHints && !b.preloadHints) {
40661      delete result.preloadHints;
40662    } // if only the old segment has parts
40663    // then the parts are no longer valid
40664
40665    if (a.parts && !b.parts) {
40666      delete result.parts; // if both segments have parts
40667      // copy part propeties from the old segment
40668      // to the new one.
40669    } else if (a.parts && b.parts) {
40670      for (let i = 0; i < b.parts.length; i++) {
40671        if (a.parts && a.parts[i]) {
40672          result.parts[i] = merge(a.parts[i], b.parts[i]);
40673        }
40674      }
40675    } // set skipped to false for segments that have
40676    // have had information merged from the old segment.
40677
40678    if (!a.skipped && b.skipped) {
40679      result.skipped = false;
40680    } // set preload to false for segments that have
40681    // had information added in the new segment.
40682
40683    if (a.preload && !b.preload) {
40684      result.preload = false;
40685    }
40686    return result;
40687  };
40688  /**
40689   * Returns a new array of segments that is the result of merging
40690   * properties from an older list of segments onto an updated
40691   * list. No properties on the updated playlist will be ovewritten.
40692   *
40693   * @param {Array} original the outdated list of segments
40694   * @param {Array} update the updated list of segments
40695   * @param {number=} offset the index of the first update
40696   * segment in the original segment list. For non-live playlists,
40697   * this should always be zero and does not need to be
40698   * specified. For live playlists, it should be the difference
40699   * between the media sequence numbers in the original and updated
40700   * playlists.
40701   * @return {Array} a list of merged segment objects
40702   */
40703
40704  const updateSegments = (original, update, offset) => {
40705    const oldSegments = original.slice();
40706    const newSegments = update.slice();
40707    offset = offset || 0;
40708    const result = [];
40709    let currentMap;
40710    for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
40711      const oldSegment = oldSegments[newIndex + offset];
40712      const newSegment = newSegments[newIndex];
40713      if (oldSegment) {
40714        currentMap = oldSegment.map || currentMap;
40715        result.push(updateSegment(oldSegment, newSegment));
40716      } else {
40717        // carry over map to new segment if it is missing
40718        if (currentMap && !newSegment.map) {
40719          newSegment.map = currentMap;
40720        }
40721        result.push(newSegment);
40722      }
40723    }
40724    return result;
40725  };
40726  const resolveSegmentUris = (segment, baseUri) => {
40727    // preloadSegment will not have a uri at all
40728    // as the segment isn't actually in the manifest yet, only parts
40729    if (!segment.resolvedUri && segment.uri) {
40730      segment.resolvedUri = resolveUrl(baseUri, segment.uri);
40731    }
40732    if (segment.key && !segment.key.resolvedUri) {
40733      segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
40734    }
40735    if (segment.map && !segment.map.resolvedUri) {
40736      segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
40737    }
40738    if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
40739      segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
40740    }
40741    if (segment.parts && segment.parts.length) {
40742      segment.parts.forEach(p => {
40743        if (p.resolvedUri) {
40744          return;
40745        }
40746        p.resolvedUri = resolveUrl(baseUri, p.uri);
40747      });
40748    }
40749    if (segment.preloadHints && segment.preloadHints.length) {
40750      segment.preloadHints.forEach(p => {
40751        if (p.resolvedUri) {
40752          return;
40753        }
40754        p.resolvedUri = resolveUrl(baseUri, p.uri);
40755      });
40756    }
40757  };
40758  const getAllSegments = function (media) {
40759    const segments = media.segments || [];
40760    const preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
40761    // a usable segment, only include a preloadSegment that has
40762    // parts.
40763
40764    if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
40765      // if preloadHints has a MAP that means that the
40766      // init segment is going to change. We cannot use any of the parts
40767      // from this preload segment.
40768      if (preloadSegment.preloadHints) {
40769        for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
40770          if (preloadSegment.preloadHints[i].type === 'MAP') {
40771            return segments;
40772          }
40773        }
40774      } // set the duration for our preload segment to target duration.
40775
40776      preloadSegment.duration = media.targetDuration;
40777      preloadSegment.preload = true;
40778      segments.push(preloadSegment);
40779    }
40780    return segments;
40781  }; // consider the playlist unchanged if the playlist object is the same or
40782  // the number of segments is equal, the media sequence number is unchanged,
40783  // and this playlist hasn't become the end of the playlist
40784
40785  const isPlaylistUnchanged = (a, b) => a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence && a.preloadSegment === b.preloadSegment;
40786  /**
40787    * Returns a new main playlist that is the result of merging an
40788    * updated media playlist into the original version. If the
40789    * updated media playlist does not match any of the playlist
40790    * entries in the original main playlist, null is returned.
40791    *
40792    * @param {Object} main a parsed main M3U8 object
40793    * @param {Object} media a parsed media M3U8 object
40794    * @return {Object} a new object that represents the original
40795    * main playlist with the updated media playlist merged in, or
40796    * null if the merge produced no change.
40797    */
40798
40799  const updateMain$1 = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
40800    const result = merge(main, {});
40801    const oldMedia = result.playlists[newMedia.id];
40802    if (!oldMedia) {
40803      return null;
40804    }
40805    if (unchangedCheck(oldMedia, newMedia)) {
40806      return null;
40807    }
40808    newMedia.segments = getAllSegments(newMedia);
40809    const mergedPlaylist = merge(oldMedia, newMedia); // always use the new media's preload segment
40810
40811    if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
40812      delete mergedPlaylist.preloadSegment;
40813    } // if the update could overlap existing segment information, merge the two segment lists
40814
40815    if (oldMedia.segments) {
40816      if (newMedia.skip) {
40817        newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
40818        // old properties into the new segments
40819
40820        for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
40821          newMedia.segments.unshift({
40822            skipped: true
40823          });
40824        }
40825      }
40826      mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
40827    } // resolve any segment URIs to prevent us from having to do it later
40828
40829    mergedPlaylist.segments.forEach(segment => {
40830      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
40831    }); // TODO Right now in the playlists array there are two references to each playlist, one
40832    // that is referenced by index, and one by URI. The index reference may no longer be
40833    // necessary.
40834
40835    for (let i = 0; i < result.playlists.length; i++) {
40836      if (result.playlists[i].id === newMedia.id) {
40837        result.playlists[i] = mergedPlaylist;
40838      }
40839    }
40840    result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
40841
40842    result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
40843
40844    forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
40845      if (!properties.playlists) {
40846        return;
40847      }
40848      for (let i = 0; i < properties.playlists.length; i++) {
40849        if (newMedia.id === properties.playlists[i].id) {
40850          properties.playlists[i] = mergedPlaylist;
40851        }
40852      }
40853    });
40854    return result;
40855  };
40856  /**
40857   * Calculates the time to wait before refreshing a live playlist
40858   *
40859   * @param {Object} media
40860   *        The current media
40861   * @param {boolean} update
40862   *        True if there were any updates from the last refresh, false otherwise
40863   * @return {number}
40864   *         The time in ms to wait before refreshing the live playlist
40865   */
40866
40867  const refreshDelay = (media, update) => {
40868    const segments = media.segments || [];
40869    const lastSegment = segments[segments.length - 1];
40870    const lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
40871    const lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
40872    if (update && lastDuration) {
40873      return lastDuration * 1000;
40874    } // if the playlist is unchanged since the last reload or last segment duration
40875    // cannot be determined, try again after half the target duration
40876
40877    return (media.partTargetDuration || media.targetDuration || 10) * 500;
40878  };
40879  /**
40880   * Load a playlist from a remote location
40881   *
40882   * @class PlaylistLoader
40883   * @extends Stream
40884   * @param {string|Object} src url or object of manifest
40885   * @param {boolean} withCredentials the withCredentials xhr option
40886   * @class
40887   */
40888
40889  class PlaylistLoader extends EventTarget$1 {
40890    constructor(src, vhs, options = {}) {
40891      super();
40892      if (!src) {
40893        throw new Error('A non-empty playlist URL or object is required');
40894      }
40895      this.logger_ = logger('PlaylistLoader');
40896      const {
40897        withCredentials = false
40898      } = options;
40899      this.src = src;
40900      this.vhs_ = vhs;
40901      this.withCredentials = withCredentials;
40902      this.addDateRangesToTextTrack_ = options.addDateRangesToTextTrack;
40903      const vhsOptions = vhs.options_;
40904      this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
40905      this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
40906      this.llhls = vhsOptions && vhsOptions.llhls;
40907      this.dateRangesStorage_ = new DateRangesStorage(); // initialize the loader state
40908
40909      this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
40910
40911      this.handleMediaupdatetimeout_ = this.handleMediaupdatetimeout_.bind(this);
40912      this.on('mediaupdatetimeout', this.handleMediaupdatetimeout_);
40913      this.on('loadedplaylist', this.handleLoadedPlaylist_.bind(this));
40914    }
40915    handleLoadedPlaylist_() {
40916      const mediaPlaylist = this.media();
40917      if (!mediaPlaylist) {
40918        return;
40919      }
40920      this.dateRangesStorage_.setOffset(mediaPlaylist.segments);
40921      this.dateRangesStorage_.setPendingDateRanges(mediaPlaylist.dateRanges);
40922      const availableDateRanges = this.dateRangesStorage_.getDateRangesToProcess();
40923      if (!availableDateRanges.length || !this.addDateRangesToTextTrack_) {
40924        return;
40925      }
40926      this.addDateRangesToTextTrack_(availableDateRanges);
40927    }
40928    handleMediaupdatetimeout_() {
40929      if (this.state !== 'HAVE_METADATA') {
40930        // only refresh the media playlist if no other activity is going on
40931        return;
40932      }
40933      const media = this.media();
40934      let uri = resolveUrl(this.main.uri, media.uri);
40935      if (this.llhls) {
40936        uri = addLLHLSQueryDirectives(uri, media);
40937      }
40938      this.state = 'HAVE_CURRENT_METADATA';
40939      this.request = this.vhs_.xhr({
40940        uri,
40941        withCredentials: this.withCredentials
40942      }, (error, req) => {
40943        // disposed
40944        if (!this.request) {
40945          return;
40946        }
40947        if (error) {
40948          return this.playlistRequestError(this.request, this.media(), 'HAVE_METADATA');
40949        }
40950        this.haveMetadata({
40951          playlistString: this.request.responseText,
40952          url: this.media().uri,
40953          id: this.media().id
40954        });
40955      });
40956    }
40957    playlistRequestError(xhr, playlist, startingState) {
40958      const {
40959        uri,
40960        id
40961      } = playlist; // any in-flight request is now finished
40962
40963      this.request = null;
40964      if (startingState) {
40965        this.state = startingState;
40966      }
40967      this.error = {
40968        playlist: this.main.playlists[id],
40969        status: xhr.status,
40970        message: `HLS playlist request error at URL: ${uri}.`,
40971        responseText: xhr.responseText,
40972        code: xhr.status >= 500 ? 4 : 2
40973      };
40974      this.trigger('error');
40975    }
40976    parseManifest_({
40977      url,
40978      manifestString
40979    }) {
40980      return parseManifest({
40981        onwarn: ({
40982          message
40983        }) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
40984        oninfo: ({
40985          message
40986        }) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
40987        manifestString,
40988        customTagParsers: this.customTagParsers,
40989        customTagMappers: this.customTagMappers,
40990        llhls: this.llhls
40991      });
40992    }
40993    /**
40994     * Update the playlist loader's state in response to a new or updated playlist.
40995     *
40996     * @param {string} [playlistString]
40997     *        Playlist string (if playlistObject is not provided)
40998     * @param {Object} [playlistObject]
40999     *        Playlist object (if playlistString is not provided)
41000     * @param {string} url
41001     *        URL of playlist
41002     * @param {string} id
41003     *        ID to use for playlist
41004     */
41005
41006    haveMetadata({
41007      playlistString,
41008      playlistObject,
41009      url,
41010      id
41011    }) {
41012      // any in-flight request is now finished
41013      this.request = null;
41014      this.state = 'HAVE_METADATA';
41015      const playlist = playlistObject || this.parseManifest_({
41016        url,
41017        manifestString: playlistString
41018      });
41019      playlist.lastRequest = Date.now();
41020      setupMediaPlaylist({
41021        playlist,
41022        uri: url,
41023        id
41024      }); // merge this playlist into the main manifest
41025
41026      const update = updateMain$1(this.main, playlist);
41027      this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
41028      this.pendingMedia_ = null;
41029      if (update) {
41030        this.main = update;
41031        this.media_ = this.main.playlists[id];
41032      } else {
41033        this.trigger('playlistunchanged');
41034      }
41035      this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
41036      this.trigger('loadedplaylist');
41037    }
41038    /**
41039      * Abort any outstanding work and clean up.
41040      */
41041
41042    dispose() {
41043      this.trigger('dispose');
41044      this.stopRequest();
41045      window.clearTimeout(this.mediaUpdateTimeout);
41046      window.clearTimeout(this.finalRenditionTimeout);
41047      this.dateRangesStorage_ = new DateRangesStorage();
41048      this.off();
41049    }
41050    stopRequest() {
41051      if (this.request) {
41052        const oldRequest = this.request;
41053        this.request = null;
41054        oldRequest.onreadystatechange = null;
41055        oldRequest.abort();
41056      }
41057    }
41058    /**
41059      * When called without any arguments, returns the currently
41060      * active media playlist. When called with a single argument,
41061      * triggers the playlist loader to asynchronously switch to the
41062      * specified media playlist. Calling this method while the
41063      * loader is in the HAVE_NOTHING causes an error to be emitted
41064      * but otherwise has no effect.
41065      *
41066      * @param {Object=} playlist the parsed media playlist
41067      * object to switch to
41068      * @param {boolean=} shouldDelay whether we should delay the request by half target duration
41069      *
41070      * @return {Playlist} the current loaded media
41071      */
41072
41073    media(playlist, shouldDelay) {
41074      // getter
41075      if (!playlist) {
41076        return this.media_;
41077      } // setter
41078
41079      if (this.state === 'HAVE_NOTHING') {
41080        throw new Error('Cannot switch media playlist from ' + this.state);
41081      } // find the playlist object if the target playlist has been
41082      // specified by URI
41083
41084      if (typeof playlist === 'string') {
41085        if (!this.main.playlists[playlist]) {
41086          throw new Error('Unknown playlist URI: ' + playlist);
41087        }
41088        playlist = this.main.playlists[playlist];
41089      }
41090      window.clearTimeout(this.finalRenditionTimeout);
41091      if (shouldDelay) {
41092        const delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
41093        this.finalRenditionTimeout = window.setTimeout(this.media.bind(this, playlist, false), delay);
41094        return;
41095      }
41096      const startingState = this.state;
41097      const mediaChange = !this.media_ || playlist.id !== this.media_.id;
41098      const mainPlaylistRef = this.main.playlists[playlist.id]; // switch to fully loaded playlists immediately
41099
41100      if (mainPlaylistRef && mainPlaylistRef.endList ||
41101      // handle the case of a playlist object (e.g., if using vhs-json with a resolved
41102      // media playlist or, for the case of demuxed audio, a resolved audio media group)
41103      playlist.endList && playlist.segments.length) {
41104        // abort outstanding playlist requests
41105        if (this.request) {
41106          this.request.onreadystatechange = null;
41107          this.request.abort();
41108          this.request = null;
41109        }
41110        this.state = 'HAVE_METADATA';
41111        this.media_ = playlist; // trigger media change if the active media has been updated
41112
41113        if (mediaChange) {
41114          this.trigger('mediachanging');
41115          if (startingState === 'HAVE_MAIN_MANIFEST') {
41116            // The initial playlist was a main manifest, and the first media selected was
41117            // also provided (in the form of a resolved playlist object) as part of the
41118            // source object (rather than just a URL). Therefore, since the media playlist
41119            // doesn't need to be requested, loadedmetadata won't trigger as part of the
41120            // normal flow, and needs an explicit trigger here.
41121            this.trigger('loadedmetadata');
41122          } else {
41123            this.trigger('mediachange');
41124          }
41125        }
41126        return;
41127      } // We update/set the timeout here so that live playlists
41128      // that are not a media change will "start" the loader as expected.
41129      // We expect that this function will start the media update timeout
41130      // cycle again. This also prevents a playlist switch failure from
41131      // causing us to stall during live.
41132
41133      this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
41134
41135      if (!mediaChange) {
41136        return;
41137      }
41138      this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
41139
41140      if (this.request) {
41141        if (playlist.resolvedUri === this.request.url) {
41142          // requesting to switch to the same playlist multiple times
41143          // has no effect after the first
41144          return;
41145        }
41146        this.request.onreadystatechange = null;
41147        this.request.abort();
41148        this.request = null;
41149      } // request the new playlist
41150
41151      if (this.media_) {
41152        this.trigger('mediachanging');
41153      }
41154      this.pendingMedia_ = playlist;
41155      this.request = this.vhs_.xhr({
41156        uri: playlist.resolvedUri,
41157        withCredentials: this.withCredentials
41158      }, (error, req) => {
41159        // disposed
41160        if (!this.request) {
41161          return;
41162        }
41163        playlist.lastRequest = Date.now();
41164        playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
41165        if (error) {
41166          return this.playlistRequestError(this.request, playlist, startingState);
41167        }
41168        this.haveMetadata({
41169          playlistString: req.responseText,
41170          url: playlist.uri,
41171          id: playlist.id
41172        }); // fire loadedmetadata the first time a media playlist is loaded
41173
41174        if (startingState === 'HAVE_MAIN_MANIFEST') {
41175          this.trigger('loadedmetadata');
41176        } else {
41177          this.trigger('mediachange');
41178        }
41179      });
41180    }
41181    /**
41182     * pause loading of the playlist
41183     */
41184
41185    pause() {
41186      if (this.mediaUpdateTimeout) {
41187        window.clearTimeout(this.mediaUpdateTimeout);
41188        this.mediaUpdateTimeout = null;
41189      }
41190      this.stopRequest();
41191      if (this.state === 'HAVE_NOTHING') {
41192        // If we pause the loader before any data has been retrieved, its as if we never
41193        // started, so reset to an unstarted state.
41194        this.started = false;
41195      } // Need to restore state now that no activity is happening
41196
41197      if (this.state === 'SWITCHING_MEDIA') {
41198        // if the loader was in the process of switching media, it should either return to
41199        // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
41200        // playlist yet. This is determined by the existence of loader.media_
41201        if (this.media_) {
41202          this.state = 'HAVE_METADATA';
41203        } else {
41204          this.state = 'HAVE_MAIN_MANIFEST';
41205        }
41206      } else if (this.state === 'HAVE_CURRENT_METADATA') {
41207        this.state = 'HAVE_METADATA';
41208      }
41209    }
41210    /**
41211     * start loading of the playlist
41212     */
41213
41214    load(shouldDelay) {
41215      if (this.mediaUpdateTimeout) {
41216        window.clearTimeout(this.mediaUpdateTimeout);
41217        this.mediaUpdateTimeout = null;
41218      }
41219      const media = this.media();
41220      if (shouldDelay) {
41221        const delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
41222        this.mediaUpdateTimeout = window.setTimeout(() => {
41223          this.mediaUpdateTimeout = null;
41224          this.load();
41225        }, delay);
41226        return;
41227      }
41228      if (!this.started) {
41229        this.start();
41230        return;
41231      }
41232      if (media && !media.endList) {
41233        this.trigger('mediaupdatetimeout');
41234      } else {
41235        this.trigger('loadedplaylist');
41236      }
41237    }
41238    updateMediaUpdateTimeout_(delay) {
41239      if (this.mediaUpdateTimeout) {
41240        window.clearTimeout(this.mediaUpdateTimeout);
41241        this.mediaUpdateTimeout = null;
41242      } // we only have use mediaupdatetimeout for live playlists.
41243
41244      if (!this.media() || this.media().endList) {
41245        return;
41246      }
41247      this.mediaUpdateTimeout = window.setTimeout(() => {
41248        this.mediaUpdateTimeout = null;
41249        this.trigger('mediaupdatetimeout');
41250        this.updateMediaUpdateTimeout_(delay);
41251      }, delay);
41252    }
41253    /**
41254     * start loading of the playlist
41255     */
41256
41257    start() {
41258      this.started = true;
41259      if (typeof this.src === 'object') {
41260        // in the case of an entirely constructed manifest object (meaning there's no actual
41261        // manifest on a server), default the uri to the page's href
41262        if (!this.src.uri) {
41263          this.src.uri = window.location.href;
41264        } // resolvedUri is added on internally after the initial request. Since there's no
41265        // request for pre-resolved manifests, add on resolvedUri here.
41266
41267        this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
41268        // request can be skipped (since the top level of the manifest, at a minimum, is
41269        // already available as a parsed manifest object). However, if the manifest object
41270        // represents a main playlist, some media playlists may need to be resolved before
41271        // the starting segment list is available. Therefore, go directly to setup of the
41272        // initial playlist, and let the normal flow continue from there.
41273        //
41274        // Note that the call to setup is asynchronous, as other sections of VHS may assume
41275        // that the first request is asynchronous.
41276
41277        setTimeout(() => {
41278          this.setupInitialPlaylist(this.src);
41279        }, 0);
41280        return;
41281      } // request the specified URL
41282
41283      this.request = this.vhs_.xhr({
41284        uri: this.src,
41285        withCredentials: this.withCredentials
41286      }, (error, req) => {
41287        // disposed
41288        if (!this.request) {
41289          return;
41290        } // clear the loader's request reference
41291
41292        this.request = null;
41293        if (error) {
41294          this.error = {
41295            status: req.status,
41296            message: `HLS playlist request error at URL: ${this.src}.`,
41297            responseText: req.responseText,
41298            // MEDIA_ERR_NETWORK
41299            code: 2
41300          };
41301          if (this.state === 'HAVE_NOTHING') {
41302            this.started = false;
41303          }
41304          return this.trigger('error');
41305        }
41306        this.src = resolveManifestRedirect(this.src, req);
41307        const manifest = this.parseManifest_({
41308          manifestString: req.responseText,
41309          url: this.src
vendor: 12,224 bytes, lines 41309-41606
41309
41310        });
41311        this.setupInitialPlaylist(manifest);
41312      });
41313    }
41314    srcUri() {
41315      return typeof this.src === 'string' ? this.src : this.src.uri;
41316    }
41317    /**
41318     * Given a manifest object that's either a main or media playlist, trigger the proper
41319     * events and set the state of the playlist loader.
41320     *
41321     * If the manifest object represents a main playlist, `loadedplaylist` will be
41322     * triggered to allow listeners to select a playlist. If none is selected, the loader
41323     * will default to the first one in the playlists array.
41324     *
41325     * If the manifest object represents a media playlist, `loadedplaylist` will be
41326     * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
41327     *
41328     * In the case of a media playlist, a main playlist object wrapper with one playlist
41329     * will be created so that all logic can handle playlists in the same fashion (as an
41330     * assumed manifest object schema).
41331     *
41332     * @param {Object} manifest
41333     *        The parsed manifest object
41334     */
41335
41336    setupInitialPlaylist(manifest) {
41337      this.state = 'HAVE_MAIN_MANIFEST';
41338      if (manifest.playlists) {
41339        this.main = manifest;
41340        addPropertiesToMain(this.main, this.srcUri()); // If the initial main playlist has playlists wtih segments already resolved,
41341        // then resolve URIs in advance, as they are usually done after a playlist request,
41342        // which may not happen if the playlist is resolved.
41343
41344        manifest.playlists.forEach(playlist => {
41345          playlist.segments = getAllSegments(playlist);
41346          playlist.segments.forEach(segment => {
41347            resolveSegmentUris(segment, playlist.resolvedUri);
41348          });
41349        });
41350        this.trigger('loadedplaylist');
41351        if (!this.request) {
41352          // no media playlist was specifically selected so start
41353          // from the first listed one
41354          this.media(this.main.playlists[0]);
41355        }
41356        return;
41357      } // In order to support media playlists passed in as vhs-json, the case where the uri
41358      // is not provided as part of the manifest should be considered, and an appropriate
41359      // default used.
41360
41361      const uri = this.srcUri() || window.location.href;
41362      this.main = mainForMedia(manifest, uri);
41363      this.haveMetadata({
41364        playlistObject: manifest,
41365        url: uri,
41366        id: this.main.playlists[0].id
41367      });
41368      this.trigger('loadedmetadata');
41369    }
41370    /**
41371     * Updates or deletes a preexisting pathway clone.
41372     * Ensures that all playlists related to the old pathway clone are
41373     * either updated or deleted.
41374     *
41375     * @param {Object} clone On update, the pathway clone object for the newly updated pathway clone.
41376     *        On delete, the old pathway clone object to be deleted.
41377     * @param {boolean} isUpdate True if the pathway is to be updated,
41378     *        false if it is meant to be deleted.
41379     */
41380
41381    updateOrDeleteClone(clone, isUpdate) {
41382      const main = this.main;
41383      const pathway = clone.ID;
41384      let i = main.playlists.length; // Iterate backwards through the playlist so we can remove playlists if necessary.
41385
41386      while (i--) {
41387        const p = main.playlists[i];
41388        if (p.attributes['PATHWAY-ID'] === pathway) {
41389          const oldPlaylistUri = p.resolvedUri;
41390          const oldPlaylistId = p.id; // update the indexed playlist and add new playlists by ID and URI
41391
41392          if (isUpdate) {
41393            const newPlaylistUri = this.createCloneURI_(p.resolvedUri, clone);
41394            const newPlaylistId = createPlaylistID(pathway, newPlaylistUri);
41395            const attributes = this.createCloneAttributes_(pathway, p.attributes);
41396            const updatedPlaylist = this.createClonePlaylist_(p, newPlaylistId, clone, attributes);
41397            main.playlists[i] = updatedPlaylist;
41398            main.playlists[newPlaylistId] = updatedPlaylist;
41399            main.playlists[newPlaylistUri] = updatedPlaylist;
41400          } else {
41401            // Remove the indexed playlist.
41402            main.playlists.splice(i, 1);
41403          } // Remove playlists by the old ID and URI.
41404
41405          delete main.playlists[oldPlaylistId];
41406          delete main.playlists[oldPlaylistUri];
41407        }
41408      }
41409      this.updateOrDeleteCloneMedia(clone, isUpdate);
41410    }
41411    /**
41412     * Updates or deletes media data based on the pathway clone object.
41413     * Due to the complexity of the media groups and playlists, in all cases
41414     * we remove all of the old media groups and playlists.
41415     * On updates, we then create new media groups and playlists based on the
41416     * new pathway clone object.
41417     *
41418     * @param {Object} clone The pathway clone object for the newly updated pathway clone.
41419     * @param {boolean} isUpdate True if the pathway is to be updated,
41420     *        false if it is meant to be deleted.
41421     */
41422
41423    updateOrDeleteCloneMedia(clone, isUpdate) {
41424      const main = this.main;
41425      const id = clone.ID;
41426      ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41427        if (!main.mediaGroups[mediaType] || !main.mediaGroups[mediaType][id]) {
41428          return;
41429        }
41430        for (const groupKey in main.mediaGroups[mediaType]) {
41431          // Remove all media playlists for the media group for this pathway clone.
41432          if (groupKey === id) {
41433            for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
41434              const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
41435              oldMedia.playlists.forEach((p, i) => {
41436                const oldMediaPlaylist = main.playlists[p.id];
41437                const oldPlaylistId = oldMediaPlaylist.id;
41438                const oldPlaylistUri = oldMediaPlaylist.resolvedUri;
41439                delete main.playlists[oldPlaylistId];
41440                delete main.playlists[oldPlaylistUri];
41441              });
41442            } // Delete the old media group.
41443
41444            delete main.mediaGroups[mediaType][groupKey];
41445          }
41446        }
41447      }); // Create the new media groups and playlists if there is an update.
41448
41449      if (isUpdate) {
41450        this.createClonedMediaGroups_(clone);
41451      }
41452    }
41453    /**
41454     * Given a pathway clone object, clones all necessary playlists.
41455     *
41456     * @param {Object} clone The pathway clone object.
41457     * @param {Object} basePlaylist The original playlist to clone from.
41458     */
41459
41460    addClonePathway(clone, basePlaylist = {}) {
41461      const main = this.main;
41462      const index = main.playlists.length;
41463      const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
41464      const playlistId = createPlaylistID(clone.ID, uri);
41465      const attributes = this.createCloneAttributes_(clone.ID, basePlaylist.attributes);
41466      const playlist = this.createClonePlaylist_(basePlaylist, playlistId, clone, attributes);
41467      main.playlists[index] = playlist; // add playlist by ID and URI
41468
41469      main.playlists[playlistId] = playlist;
41470      main.playlists[uri] = playlist;
41471      this.createClonedMediaGroups_(clone);
41472    }
41473    /**
41474     * Given a pathway clone object we create clones of all media.
41475     * In this function, all necessary information and updated playlists
41476     * are added to the `mediaGroup` object.
41477     * Playlists are also added to the `playlists` array so the media groups
41478     * will be properly linked.
41479     *
41480     * @param {Object} clone The pathway clone object.
41481     */
41482
41483    createClonedMediaGroups_(clone) {
41484      const id = clone.ID;
41485      const baseID = clone['BASE-ID'];
41486      const main = this.main;
41487      ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41488        // If the media type doesn't exist, or there is already a clone, skip
41489        // to the next media type.
41490        if (!main.mediaGroups[mediaType] || main.mediaGroups[mediaType][id]) {
41491          return;
41492        }
41493        for (const groupKey in main.mediaGroups[mediaType]) {
41494          if (groupKey === baseID) {
41495            // Create the group.
41496            main.mediaGroups[mediaType][id] = {};
41497          } else {
41498            // There is no need to iterate over label keys in this case.
41499            continue;
41500          }
41501          for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
41502            const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
41503            main.mediaGroups[mediaType][id][labelKey] = _extends$1({}, oldMedia);
41504            const newMedia = main.mediaGroups[mediaType][id][labelKey]; // update URIs on the media
41505
41506            const newUri = this.createCloneURI_(oldMedia.resolvedUri, clone);
41507            newMedia.resolvedUri = newUri;
41508            newMedia.uri = newUri; // Reset playlists in the new media group.
41509
41510            newMedia.playlists = []; // Create new playlists in the newly cloned media group.
41511
41512            oldMedia.playlists.forEach((p, i) => {
41513              const oldMediaPlaylist = main.playlists[p.id];
41514              const group = groupID(mediaType, id, labelKey);
41515              const newPlaylistID = createPlaylistID(id, group); // Check to see if it already exists
41516
41517              if (oldMediaPlaylist && !main.playlists[newPlaylistID]) {
41518                const newMediaPlaylist = this.createClonePlaylist_(oldMediaPlaylist, newPlaylistID, clone);
41519                const newPlaylistUri = newMediaPlaylist.resolvedUri;
41520                main.playlists[newPlaylistID] = newMediaPlaylist;
41521                main.playlists[newPlaylistUri] = newMediaPlaylist;
41522              }
41523              newMedia.playlists[i] = this.createClonePlaylist_(p, newPlaylistID, clone);
41524            });
41525          }
41526        }
41527      });
41528    }
41529    /**
41530     * Using the original playlist to be cloned, and the pathway clone object
41531     * information, we create a new playlist.
41532     *
41533     * @param {Object} basePlaylist  The original playlist to be cloned from.
41534     * @param {string} id The desired id of the newly cloned playlist.
41535     * @param {Object} clone The pathway clone object.
41536     * @param {Object} attributes An optional object to populate the `attributes` property in the playlist.
41537     *
41538     * @return {Object} The combined cloned playlist.
41539     */
41540
41541    createClonePlaylist_(basePlaylist, id, clone, attributes) {
41542      const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
41543      const newProps = {
41544        resolvedUri: uri,
41545        uri,
41546        id
41547      }; // Remove all segments from previous playlist in the clone.
41548
41549      if (basePlaylist.segments) {
41550        newProps.segments = [];
41551      }
41552      if (attributes) {
41553        newProps.attributes = attributes;
41554      }
41555      return merge(basePlaylist, newProps);
41556    }
41557    /**
41558     * Generates an updated URI for a cloned pathway based on the original
41559     * pathway's URI and the paramaters from the pathway clone object in the
41560     * content steering server response.
41561     *
41562     * @param {string} baseUri URI to be updated in the cloned pathway.
41563     * @param {Object} clone The pathway clone object.
41564     *
41565     * @return {string} The updated URI for the cloned pathway.
41566     */
41567
41568    createCloneURI_(baseURI, clone) {
41569      const uri = new URL(baseURI);
41570      uri.hostname = clone['URI-REPLACEMENT'].HOST;
41571      const params = clone['URI-REPLACEMENT'].PARAMS; // Add params to the cloned URL.
41572
41573      for (const key of Object.keys(params)) {
41574        uri.searchParams.set(key, params[key]);
41575      }
41576      return uri.href;
41577    }
41578    /**
41579     * Helper function to create the attributes needed for the new clone.
41580     * This mainly adds the necessary media attributes.
41581     *
41582     * @param {string} id The pathway clone object ID.
41583     * @param {Object} oldAttributes The old attributes to compare to.
41584     * @return {Object} The new attributes to add to the playlist.
41585     */
41586
41587    createCloneAttributes_(id, oldAttributes) {
41588      const attributes = {
41589        ['PATHWAY-ID']: id
41590      };
41591      ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41592        if (oldAttributes[mediaType]) {
41593          attributes[mediaType] = id;
41594        }
41595      });
41596      return attributes;
41597    }
41598    /**
41599     * Returns the key ID set from a playlist
41600     *
41601     * @param {playlist} playlist to fetch the key ID set from.
41602     * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
41603     */
41604
41605    getKeyIdSet(playlist) {
41606      
41606if (playlist.contentProtection) {
41607        const keyIds = new Set();
41608        for (const keysystem in playlist.contentProtection) {
41609          const keyId = playlist.contentProtection[keysystem].attributes.keyId;
41610          if (keyId) {
41611            keyIds.add(keyId.toLowerCase());
41612          }
41613        }
41614        return keyIds;
41615      }
41616    }
41617  }
41618
41619  /**
41620   * @file xhr.js
41621   */
41622  const {
41623    xhr: videojsXHR
41624  } = videojs;
41625  const callbackWrapper = function (request, error, response, callback) {
41626    const reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
41627    if (!error && reqResponse) {
41628      request.responseTime = Date.now();
41629      request.roundTripTime = request.responseTime - request.requestTime;
41630      request.bytesReceived = reqResponse.byteLength || reqResponse.length;
41631      if (!request.bandwidth) {
41632        request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
41633      }
41634    }
41635    if (response.headers) {
41636      request.responseHeaders = response.headers;
41637    } // videojs.xhr now uses a specific code on the error
vendor: 4,803 bytes, lines 41638-41744
41638    // object to signal that a request has timed out instead
41639    // of setting a boolean on the request object
41640
41641    if (error && error.code === 'ETIMEDOUT') {
41642      request.timedout = true;
41643    } // videojs.xhr no longer considers status codes outside of 200 and 0
41644    // (for file uris) to be errors, but the old XHR did, so emulate that
41645    // behavior. Status 206 may be used in response to byterange requests.
41646
41647    if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
41648      error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
41649    }
41650    callback(error, request);
41651  };
41652  /**
41653   * Iterates over the request hooks Set and calls them in order
41654   *
41655   * @param {Set} hooks the hook Set to iterate over
41656   * @param {Object} options the request options to pass to the xhr wrapper
41657   * @return the callback hook function return value, the modified or new options Object.
41658   */
41659
41660  const callAllRequestHooks = (requestSet, options) => {
41661    if (!requestSet || !requestSet.size) {
41662      return;
41663    }
41664    let newOptions = options;
41665    requestSet.forEach(requestCallback => {
41666      newOptions = requestCallback(newOptions);
41667    });
41668    return newOptions;
41669  };
41670  /**
41671   * Iterates over the response hooks Set and calls them in order.
41672   *
41673   * @param {Set} hooks the hook Set to iterate over
41674   * @param {Object} request the xhr request object
41675   * @param {Object} error the xhr error object
41676   * @param {Object} response the xhr response object
41677   */
41678
41679  const callAllResponseHooks = (responseSet, request, error, response) => {
41680    if (!responseSet || !responseSet.size) {
41681      return;
41682    }
41683    responseSet.forEach(responseCallback => {
41684      responseCallback(request, error, response);
41685    });
41686  };
41687  const xhrFactory = function () {
41688    const xhr = function XhrFunction(options, callback) {
41689      // Add a default timeout
41690      options = merge({
41691        timeout: 45e3
41692      }, options); // Allow an optional user-specified function to modify the option
41693      // object before we construct the xhr request
41694      // TODO: Remove beforeRequest in the next major release.
41695
41696      const beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest; // onRequest and onResponse hooks as a Set, at either the player or global level.
41697      // TODO: new Set added here for beforeRequest alias. Remove this when beforeRequest is removed.
41698
41699      const _requestCallbackSet = XhrFunction._requestCallbackSet || videojs.Vhs.xhr._requestCallbackSet || new Set();
41700      const _responseCallbackSet = XhrFunction._responseCallbackSet || videojs.Vhs.xhr._responseCallbackSet;
41701      if (beforeRequest && typeof beforeRequest === 'function') {
41702        videojs.log.warn('beforeRequest is deprecated, use onRequest instead.');
41703        _requestCallbackSet.add(beforeRequest);
41704      } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
41705      // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
41706
41707      const xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr; // call all registered onRequest hooks, assign new options.
41708
41709      const beforeRequestOptions = callAllRequestHooks(_requestCallbackSet, options); // Remove the beforeRequest function from the hooks set so stale beforeRequest functions are not called.
41710
41711      _requestCallbackSet.delete(beforeRequest); // xhrMethod will call XMLHttpRequest.open and XMLHttpRequest.send
41712
41713      const request = xhrMethod(beforeRequestOptions || options, function (error, response) {
41714        // call all registered onResponse hooks
41715        callAllResponseHooks(_responseCallbackSet, request, error, response);
41716        return callbackWrapper(request, error, response, callback);
41717      });
41718      const originalAbort = request.abort;
41719      request.abort = function () {
41720        request.aborted = true;
41721        return originalAbort.apply(request, arguments);
41722      };
41723      request.uri = options.uri;
41724      request.requestTime = Date.now();
41725      return request;
41726    };
41727    xhr.original = true;
41728    return xhr;
41729  };
41730  /**
41731   * Turns segment byterange into a string suitable for use in
41732   * HTTP Range requests
41733   *
41734   * @param {Object} byterange - an object with two values defining the start and end
41735   *                             of a byte-range
41736   */
41737
41738  const byterangeStr = function (byterange) {
41739    // `byterangeEnd` is one less than `offset + length` because the HTTP range
41740    // header uses inclusive ranges
41741    let byterangeEnd;
41742    const byterangeStart = byterange.offset;
41743    if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
41744      byterangeEnd = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window
vendor: 17,434 bytes, lines 41744-42227
41744.BigInt(1);
41745    } else {
41746      byterangeEnd = byterange.offset + byterange.length - 1;
41747    }
41748    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
41749  };
41750  /**
41751   * Defines headers for use in the xhr request for a particular segment.
41752   *
41753   * @param {Object} segment - a simplified copy of the segmentInfo object
41754   *                           from SegmentLoader
41755   */
41756
41757  const segmentXhrHeaders = function (segment) {
41758    const headers = {};
41759    if (segment.byterange) {
41760      headers.Range = byterangeStr(segment.byterange);
41761    }
41762    return headers;
41763  };
41764
41765  /**
41766   * @file bin-utils.js
41767   */
41768
41769  /**
41770   * convert a TimeRange to text
41771   *
41772   * @param {TimeRange} range the timerange to use for conversion
41773   * @param {number} i the iterator on the range to convert
41774   * @return {string} the range in string format
41775   */
41776
41777  const textRange = function (range, i) {
41778    return range.start(i) + '-' + range.end(i);
41779  };
41780  /**
41781   * format a number as hex string
41782   *
41783   * @param {number} e The number
41784   * @param {number} i the iterator
41785   * @return {string} the hex formatted number as a string
41786   */
41787
41788  const formatHexString = function (e, i) {
41789    const value = e.toString(16);
41790    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
41791  };
41792  const formatAsciiString = function (e) {
41793    if (e >= 0x20 && e < 0x7e) {
41794      return String.fromCharCode(e);
41795    }
41796    return '.';
41797  };
41798  /**
41799   * Creates an object for sending to a web worker modifying properties that are TypedArrays
41800   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
41801   *
41802   * @param {Object} message
41803   *        Object of properties and values to send to the web worker
41804   * @return {Object}
41805   *         Modified message with TypedArray values expanded
41806   * @function createTransferableMessage
41807   */
41808
41809  const createTransferableMessage = function (message) {
41810    const transferable = {};
41811    Object.keys(message).forEach(key => {
41812      const value = message[key];
41813      if (isArrayBufferView(value)) {
41814        transferable[key] = {
41815          bytes: value.buffer,
41816          byteOffset: value.byteOffset,
41817          byteLength: value.byteLength
41818        };
41819      } else {
41820        transferable[key] = value;
41821      }
41822    });
41823    return transferable;
41824  };
41825  /**
41826   * Returns a unique string identifier for a media initialization
41827   * segment.
41828   *
41829   * @param {Object} initSegment
41830   *        the init segment object.
41831   *
41832   * @return {string} the generated init segment id
41833   */
41834
41835  const initSegmentId = function (initSegment) {
41836    const byterange = initSegment.byterange || {
41837      length: Infinity,
41838      offset: 0
41839    };
41840    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
41841  };
41842  /**
41843   * Returns a unique string identifier for a media segment key.
41844   *
41845   * @param {Object} key the encryption key
41846   * @return {string} the unique id for the media segment key.
41847   */
41848
41849  const segmentKeyId = function (key) {
41850    return key.resolvedUri;
41851  };
41852  /**
41853   * utils to help dump binary data to the console
41854   *
41855   * @param {Array|TypedArray} data
41856   *        data to dump to a string
41857   *
41858   * @return {string} the data as a hex string.
41859   */
41860
41861  const hexDump = data => {
41862    const bytes = Array.prototype.slice.call(data);
41863    const step = 16;
41864    let result = '';
41865    let hex;
41866    let ascii;
41867    for (let j = 0; j < bytes.length / step; j++) {
41868      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
41869      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
41870      result += hex + ' ' + ascii + '\n';
41871    }
41872    return result;
41873  };
41874  const tagDump = ({
41875    bytes
41876  }) => hexDump(bytes);
41877  const textRanges = ranges => {
41878    let result = '';
41879    let i;
41880    for (i = 0; i < ranges.length; i++) {
41881      result += textRange(ranges, i) + ' ';
41882    }
41883    return result;
41884  };
41885  var utils = /*#__PURE__*/Object.freeze({
41886    __proto__: null,
41887    createTransferableMessage: createTransferableMessage,
41888    initSegmentId: initSegmentId,
41889    segmentKeyId: segmentKeyId,
41890    hexDump: hexDump,
41891    tagDump: tagDump,
41892    textRanges: textRanges
41893  });
41894
41895  // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
41896  // 25% was arbitrarily chosen, and may need to be refined over time.
41897
41898  const SEGMENT_END_FUDGE_PERCENT = 0.25;
41899  /**
41900   * Converts a player time (any time that can be gotten/set from player.currentTime(),
41901   * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
41902   * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
41903   *
41904   * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
41905   * point" (a point where we have a mapping from program time to player time, with player
41906   * time being the post transmux start of the segment).
41907   *
41908   * For more details, see [this doc](../../docs/program-time-from-player-time.md).
41909   *
41910   * @param {number} playerTime the player time
41911   * @param {Object} segment the segment which contains the player time
41912   * @return {Date} program time
41913   */
41914
41915  const playerTimeToProgramTime = (playerTime, segment) => {
41916    if (!segment.dateTimeObject) {
41917      // Can't convert without an "anchor point" for the program time (i.e., a time that can
41918      // be used to map the start of a segment with a real world time).
41919      return null;
41920    }
41921    const transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
41922    const transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
41923
41924    const startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
41925    const offsetFromSegmentStart = playerTime - startOfSegment;
41926    return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
41927  };
41928  const originalSegmentVideoDuration = videoTimingInfo => {
41929    return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
41930  };
41931  /**
41932   * Finds a segment that contains the time requested given as an ISO-8601 string. The
41933   * returned segment might be an estimate or an accurate match.
41934   *
41935   * @param {string} programTime The ISO-8601 programTime to find a match for
41936   * @param {Object} playlist A playlist object to search within
41937   */
41938
41939  const findSegmentForProgramTime = (programTime, playlist) => {
41940    // Assumptions:
41941    //  - verifyProgramDateTimeTags has already been run
41942    //  - live streams have been started
41943    let dateTimeObject;
41944    try {
41945      dateTimeObject = new Date(programTime);
41946    } catch (e) {
41947      return null;
41948    }
41949    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41950      return null;
41951    }
41952    let segment = playlist.segments[0];
41953    if (dateTimeObject < new Date(segment.dateTimeObject)) {
41954      // Requested time is before stream start.
41955      return null;
41956    }
41957    for (let i = 0; i < playlist.segments.length - 1; i++) {
41958      segment = playlist.segments[i];
41959      const nextSegmentStart = new Date(playlist.segments[i + 1].dateTimeObject);
41960      if (dateTimeObject < nextSegmentStart) {
41961        break;
41962      }
41963    }
41964    const lastSegment = playlist.segments[playlist.segments.length - 1];
41965    const lastSegmentStart = lastSegment.dateTimeObject;
41966    const lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
41967    const lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
41968    if (dateTimeObject > lastSegmentEnd) {
41969      // Beyond the end of the stream, or our best guess of the end of the stream.
41970      return null;
41971    }
41972    if (dateTimeObject > new Date(lastSegmentStart)) {
41973      segment = lastSegment;
41974    }
41975    return {
41976      segment,
41977      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
41978      // Although, given that all segments have accurate date time objects, the segment
41979      // selected should be accurate, unless the video has been transmuxed at some point
41980      // (determined by the presence of the videoTimingInfo object), the segment's "player
41981      // time" (the start time in the player) can't be considered accurate.
41982      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
41983    };
41984  };
41985  /**
41986   * Finds a segment that contains the given player time(in seconds).
41987   *
41988   * @param {number} time The player time to find a match for
41989   * @param {Object} playlist A playlist object to search within
41990   */
41991
41992  const findSegmentForPlayerTime = (time, playlist) => {
41993    // Assumptions:
41994    // - there will always be a segment.duration
41995    // - we can start from zero
41996    // - segments are in time order
41997    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41998      return null;
41999    }
42000    let segmentEnd = 0;
42001    let segment;
42002    for (let i = 0; i < playlist.segments.length; i++) {
42003      segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
42004      // should contain the most accurate values we have for the segment's player times.
42005      //
42006      // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
42007      // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
42008      // calculate an end value.
42009
42010      segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
42011      if (time <= segmentEnd) {
42012        break;
42013      }
42014    }
42015    const lastSegment = playlist.segments[playlist.segments.length - 1];
42016    if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
42017      // The time requested is beyond the stream end.
42018      return null;
42019    }
42020    if (time > segmentEnd) {
42021      // The time is within or beyond the last segment.
42022      //
42023      // Check to see if the time is beyond a reasonable guess of the end of the stream.
42024      if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
42025        // Technically, because the duration value is only an estimate, the time may still
42026        // exist in the last segment, however, there isn't enough information to make even
42027        // a reasonable estimate.
42028        return null;
42029      }
42030      segment = lastSegment;
42031    }
42032    return {
42033      segment,
42034      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
42035      // Because videoTimingInfo is only set after transmux, it is the only way to get
42036      // accurate timing values.
42037      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
42038    };
42039  };
42040  /**
42041   * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
42042   * If the offset returned is positive, the programTime occurs after the
42043   * comparisonTimestamp.
42044   * If the offset is negative, the programTime occurs before the comparisonTimestamp.
42045   *
42046   * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
42047   * @param {string} programTime The programTime as an ISO-8601 string
42048   * @return {number} offset
42049   */
42050
42051  const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
42052    let segmentDateTime;
42053    let programDateTime;
42054    try {
42055      segmentDateTime = new Date(comparisonTimeStamp);
42056      programDateTime = new Date(programTime);
42057    } catch (e) {// TODO handle error
42058    }
42059    const segmentTimeEpoch = segmentDateTime.getTime();
42060    const programTimeEpoch = programDateTime.getTime();
42061    return (programTimeEpoch - segmentTimeEpoch) / 1000;
42062  };
42063  /**
42064   * Checks that all segments in this playlist have programDateTime tags.
42065   *
42066   * @param {Object} playlist A playlist object
42067   */
42068
42069  const verifyProgramDateTimeTags = playlist => {
42070    if (!playlist.segments || playlist.segments.length === 0) {
42071      return false;
42072    }
42073    for (let i = 0; i < playlist.segments.length; i++) {
42074      const segment = playlist.segments[i];
42075      if (!segment.dateTimeObject) {
42076        return false;
42077      }
42078    }
42079    return true;
42080  };
42081  /**
42082   * Returns the programTime of the media given a playlist and a playerTime.
42083   * The playlist must have programDateTime tags for a programDateTime tag to be returned.
42084   * If the segments containing the time requested have not been buffered yet, an estimate
42085   * may be returned to the callback.
42086   *
42087   * @param {Object} args
42088   * @param {Object} args.playlist A playlist object to search within
42089   * @param {number} time A playerTime in seconds
42090   * @param {Function} callback(err, programTime)
42091   * @return {string} err.message A detailed error message
42092   * @return {Object} programTime
42093   * @return {number} programTime.mediaSeconds The streamTime in seconds
42094   * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
42095   */
42096
42097  const getProgramTime = ({
42098    playlist,
42099    time = undefined,
42100    callback
42101  }) => {
42102    if (!callback) {
42103      throw new Error('getProgramTime: callback must be provided');
42104    }
42105    if (!playlist || time === undefined) {
42106      return callback({
42107        message: 'getProgramTime: playlist and time must be provided'
42108      });
42109    }
42110    const matchedSegment = findSegmentForPlayerTime(time, playlist);
42111    if (!matchedSegment) {
42112      return callback({
42113        message: 'valid programTime was not found'
42114      });
42115    }
42116    if (matchedSegment.type === 'estimate') {
42117      return callback({
42118        message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
42119        seekTime: matchedSegment.estimatedStart
42120      });
42121    }
42122    const programTimeObject = {
42123      mediaSeconds: time
42124    };
42125    const programTime = playerTimeToProgramTime(time, matchedSegment.segment);
42126    if (programTime) {
42127      programTimeObject.programDateTime = programTime.toISOString();
42128    }
42129    return callback(null, programTimeObject);
42130  };
42131  /**
42132   * Seeks in the player to a time that matches the given programTime ISO-8601 string.
42133   *
42134   * @param {Object} args
42135   * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
42136   * @param {Object} args.playlist A playlist to look within
42137   * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
42138   * @param {Function} args.seekTo A method to perform a seek
42139   * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
42140   * @param {Object} args.tech The tech to seek on
42141   * @param {Function} args.callback(err, newTime) A callback to return the new time to
42142   * @return {string} err.message A detailed error message
42143   * @return {number} newTime The exact time that was seeked to in seconds
42144   */
42145
42146  const seekToProgramTime = ({
42147    programTime,
42148    playlist,
42149    retryCount = 2,
42150    seekTo,
42151    pauseAfterSeek = true,
42152    tech,
42153    callback
42154  }) => {
42155    if (!callback) {
42156      throw new Error('seekToProgramTime: callback must be provided');
42157    }
42158    if (typeof programTime === 'undefined' || !playlist || !seekTo) {
42159      return callback({
42160        message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
42161      });
42162    }
42163    if (!playlist.endList && !tech.hasStarted_) {
42164      return callback({
42165        message: 'player must be playing a live stream to start buffering'
42166      });
42167    }
42168    if (!verifyProgramDateTimeTags(playlist)) {
42169      return callback({
42170        message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
42171      });
42172    }
42173    const matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
42174
42175    if (!matchedSegment) {
42176      return callback({
42177        message: `${programTime} was not found in the stream`
42178      });
42179    }
42180    const segment = matchedSegment.segment;
42181    const mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
42182    if (matchedSegment.type === 'estimate') {
42183      // we've run out of retries
42184      if (retryCount === 0) {
42185        return callback({
42186          message: `${programTime} is not buffered yet. Try again`
42187        });
42188      }
42189      seekTo(matchedSegment.estimatedStart + mediaOffset);
42190      tech.one('seeked', () => {
42191        seekToProgramTime({
42192          programTime,
42193          playlist,
42194          retryCount: retryCount - 1,
42195          seekTo,
42196          pauseAfterSeek,
42197          tech,
42198          callback
42199        });
42200      });
42201      return;
42202    } // Since the segment.start value is determined from the buffered end or ending time
42203    // of the prior segment, the seekToTime doesn't need to account for any transmuxer
42204    // modifications.
42205
42206    const seekToTime = segment.start + mediaOffset;
42207    const seekedCallback = () => {
42208      return callback(null, tech.currentTime());
42209    }; // listen for seeked event
42210
42211    tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
42212
42213    if (pauseAfterSeek) {
42214      tech.pause();
42215    }
42216    seekTo(seekToTime);
42217  };
42218
42219  // which will only happen if the request is complete.
42220
42221  const callbackOnCompleted = (request, cb) => {
42222    if (request.readyState === 4) {
42223      return cb();
42224    }
42225    return;
42226  };
42227  const containerRequest = (uri, xhr, cb
42227) => {
42228    let bytes = [];
42229    let id3Offset;
42230    let finished = false;
vendor: 25,162 bytes, lines 42231-42902
42231    const endRequestAndCallback = function (err, req, type, _bytes) {
42232      req.abort();
42233      finished = true;
42234      return cb(err, req, type, _bytes);
42235    };
42236    const progressListener = function (error, request) {
42237      if (finished) {
42238        return;
42239      }
42240      if (error) {
42241        return endRequestAndCallback(error, request, '', bytes);
42242      } // grap the new part of content that was just downloaded
42243
42244      const newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
42245
42246      bytes = concatTypedArrays(bytes, stringToBytes(newPart, true));
42247      id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type
42248      // or we need at least two bytes after an id3Offset
42249
42250      if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
42251        return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42252      }
42253      const type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
42254      // to see the second sync byte, wait until we have enough data
42255      // before declaring it ts
42256
42257      if (type === 'ts' && bytes.length < 188) {
42258        return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42259      } // this may be an unsynced ts segment
42260      // wait for 376 bytes before detecting no container
42261
42262      if (!type && bytes.length < 376) {
42263        return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42264      }
42265      return endRequestAndCallback(null, request, type, bytes);
42266    };
42267    const options = {
42268      uri,
42269      beforeSend(request) {
42270        // this forces the browser to pass the bytes to us unprocessed
42271        request.overrideMimeType('text/plain; charset=x-user-defined');
42272        request.addEventListener('progress', function ({
42273          total,
42274          loaded
42275        }) {
42276          return callbackWrapper(request, null, {
42277            statusCode: request.status
42278          }, progressListener);
42279        });
42280      }
42281    };
42282    const request = xhr(options, function (error, response) {
42283      return callbackWrapper(request, error, response, progressListener);
42284    });
42285    return request;
42286  };
42287  const {
42288    EventTarget
42289  } = videojs;
42290  const dashPlaylistUnchanged = function (a, b) {
42291    if (!isPlaylistUnchanged(a, b)) {
42292      return false;
42293    } // for dash the above check will often return true in scenarios where
42294    // the playlist actually has changed because mediaSequence isn't a
42295    // dash thing, and we often set it to 1. So if the playlists have the same amount
42296    // of segments we return true.
42297    // So for dash we need to make sure that the underlying segments are different.
42298    // if sidx changed then the playlists are different.
42299
42300    if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
42301      return false;
42302    } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
42303      return false;
42304    } // one or the other does not have segments
42305    // there was a change.
42306
42307    if (a.segments && !b.segments || !a.segments && b.segments) {
42308      return false;
42309    } // neither has segments nothing changed
42310
42311    if (!a.segments && !b.segments) {
42312      return true;
42313    } // check segments themselves
42314
42315    for (let i = 0; i < a.segments.length; i++) {
42316      const aSegment = a.segments[i];
42317      const bSegment = b.segments[i]; // if uris are different between segments there was a change
42318
42319      if (aSegment.uri !== bSegment.uri) {
42320        return false;
42321      } // neither segment has a byterange, there will be no byterange change.
42322
42323      if (!aSegment.byterange && !bSegment.byterange) {
42324        continue;
42325      }
42326      const aByterange = aSegment.byterange;
42327      const bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
42328
42329      if (aByterange && !bByterange || !aByterange && bByterange) {
42330        return false;
42331      } // if both segments have byterange with different offsets, there was a change.
42332
42333      if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
42334        return false;
42335      }
42336    } // if everything was the same with segments, this is the same playlist.
42337
42338    return true;
42339  };
42340  /**
42341   * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
42342   * ID in the parser. This allows for continuous playout across periods with the same representation IDs
42343   * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
42344   * periods without continuous playback this function may need modification as well as the parser.
42345   */
42346
42347  const dashGroupId = (type, group, label, playlist) => {
42348    // If the manifest somehow does not have an ID (non-dash compliant), use the label.
42349    const playlistId = playlist.attributes.NAME || label;
42350    return `placeholder-uri-${type}-${group}-${playlistId}`;
42351  };
42352  /**
42353   * Parses the main XML string and updates playlist URI references.
42354   *
42355   * @param {Object} config
42356   *        Object of arguments
42357   * @param {string} config.mainXml
42358   *        The mpd XML
42359   * @param {string} config.srcUrl
42360   *        The mpd URL
42361   * @param {Date} config.clientOffset
42362   *         A time difference between server and client
42363   * @param {Object} config.sidxMapping
42364   *        SIDX mappings for moof/mdat URIs and byte ranges
42365   * @return {Object}
42366   *         The parsed mpd manifest object
42367   */
42368
42369  const parseMainXml = ({
42370    mainXml,
42371    srcUrl,
42372    clientOffset,
42373    sidxMapping,
42374    previousManifest
42375  }) => {
42376    const manifest = parse(mainXml, {
42377      manifestUri: srcUrl,
42378      clientOffset,
42379      sidxMapping,
42380      previousManifest
42381    });
42382    addPropertiesToMain(manifest, srcUrl, dashGroupId);
42383    return manifest;
42384  };
42385  /**
42386   * Removes any mediaGroup labels that no longer exist in the newMain
42387   *
42388   * @param {Object} update
42389   *         The previous mpd object being updated
42390   * @param {Object} newMain
42391   *         The new mpd object
42392   */
42393
42394  const removeOldMediaGroupLabels = (update, newMain) => {
42395    forEachMediaGroup(update, (properties, type, group, label) => {
42396      if (!(label in newMain.mediaGroups[type][group])) {
42397        delete update.mediaGroups[type][group][label];
42398      }
42399    });
42400  };
42401  /**
42402   * Returns a new main manifest that is the result of merging an updated main manifest
42403   * into the original version.
42404   *
42405   * @param {Object} oldMain
42406   *        The old parsed mpd object
42407   * @param {Object} newMain
42408   *        The updated parsed mpd object
42409   * @return {Object}
42410   *         A new object representing the original main manifest with the updated media
42411   *         playlists merged in
42412   */
42413
42414  const updateMain = (oldMain, newMain, sidxMapping) => {
42415    let noChanges = true;
42416    let update = merge(oldMain, {
42417      // These are top level properties that can be updated
42418      duration: newMain.duration,
42419      minimumUpdatePeriod: newMain.minimumUpdatePeriod,
42420      timelineStarts: newMain.timelineStarts
42421    }); // First update the playlists in playlist list
42422
42423    for (let i = 0; i < newMain.playlists.length; i++) {
42424      const playlist = newMain.playlists[i];
42425      if (playlist.sidx) {
42426        const sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
42427
42428        if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
42429          addSidxSegmentsToPlaylist$1(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
42430        }
42431      }
42432      const playlistUpdate = updateMain$1(update, playlist, dashPlaylistUnchanged);
42433      if (playlistUpdate) {
42434        update = playlistUpdate;
42435        noChanges = false;
42436      }
42437    } // Then update media group playlists
42438
42439    forEachMediaGroup(newMain, (properties, type, group, label) => {
42440      if (properties.playlists && properties.playlists.length) {
42441        const id = properties.playlists[0].id;
42442        const playlistUpdate = updateMain$1(update, properties.playlists[0], dashPlaylistUnchanged);
42443        if (playlistUpdate) {
42444          update = playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
42445
42446          if (!(label in update.mediaGroups[type][group])) {
42447            update.mediaGroups[type][group][label] = properties;
42448          } // update the playlist reference within media groups
42449
42450          update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
42451          noChanges = false;
42452        }
42453      }
42454    }); // remove mediaGroup labels and references that no longer exist in the newMain
42455
42456    removeOldMediaGroupLabels(update, newMain);
42457    if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
42458      noChanges = false;
42459    }
42460    if (noChanges) {
42461      return null;
42462    }
42463    return update;
42464  }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
42465  // If the SIDXs have maps, the two maps should match,
42466  // both `a` and `b` missing SIDXs is considered matching.
42467  // If `a` or `b` but not both have a map, they aren't matching.
42468
42469  const equivalentSidx = (a, b) => {
42470    const neitherMap = Boolean(!a.map && !b.map);
42471    const equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
42472    return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
42473  }; // exported for testing
42474
42475  const compareSidxEntry = (playlists, oldSidxMapping) => {
42476    const newSidxMapping = {};
42477    for (const id in playlists) {
42478      const playlist = playlists[id];
42479      const currentSidxInfo = playlist.sidx;
42480      if (currentSidxInfo) {
42481        const key = generateSidxKey(currentSidxInfo);
42482        if (!oldSidxMapping[key]) {
42483          break;
42484        }
42485        const savedSidxInfo = oldSidxMapping[key].sidxInfo;
42486        if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
42487          newSidxMapping[key] = oldSidxMapping[key];
42488        }
42489      }
42490    }
42491    return newSidxMapping;
42492  };
42493  /**
42494   *  A function that filters out changed items as they need to be requested separately.
42495   *
42496   *  The method is exported for testing
42497   *
42498   *  @param {Object} main the parsed mpd XML returned via mpd-parser
42499   *  @param {Object} oldSidxMapping the SIDX to compare against
42500   */
42501
42502  const filterChangedSidxMappings = (main, oldSidxMapping) => {
42503    const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
42504    let mediaGroupSidx = videoSidx;
42505    forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
42506      if (properties.playlists && properties.playlists.length) {
42507        const playlists = properties.playlists;
42508        mediaGroupSidx = merge(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
42509      }
42510    });
42511    return mediaGroupSidx;
42512  };
42513  class DashPlaylistLoader extends EventTarget {
42514    // DashPlaylistLoader must accept either a src url or a playlist because subsequent
42515    // playlist loader setups from media groups will expect to be able to pass a playlist
42516    // (since there aren't external URLs to media playlists with DASH)
42517    constructor(srcUrlOrPlaylist, vhs, options = {}, mainPlaylistLoader) {
42518      super();
42519      this.mainPlaylistLoader_ = mainPlaylistLoader || this;
42520      if (!mainPlaylistLoader) {
42521        this.isMain_ = true;
42522      }
42523      const {
42524        withCredentials = false
42525      } = options;
42526      this.vhs_ = vhs;
42527      this.withCredentials = withCredentials;
42528      this.addMetadataToTextTrack = options.addMetadataToTextTrack;
42529      if (!srcUrlOrPlaylist) {
42530        throw new Error('A non-empty playlist URL or object is required');
42531      } // event naming?
42532
42533      this.on('minimumUpdatePeriod', () => {
42534        this.refreshXml_();
42535      }); // live playlist staleness timeout
42536
42537      this.on('mediaupdatetimeout', () => {
42538        this.refreshMedia_(this.media().id);
42539      });
42540      this.state = 'HAVE_NOTHING';
42541      this.loadedPlaylists_ = {};
42542      this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
42543      // The mainPlaylistLoader will be created with a string
42544
42545      if (this.isMain_) {
42546        this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
42547        // once multi-period is refactored
42548
42549        this.mainPlaylistLoader_.sidxMapping_ = {};
42550      } else {
42551        this.childPlaylist_ = srcUrlOrPlaylist;
42552      }
42553    }
42554    requestErrored_(err, request, startingState) {
42555      // disposed
42556      if (!this.request) {
42557        return true;
42558      } // pending request is cleared
42559
42560      this.request = null;
42561      if (err) {
42562        // use the provided error object or create one
42563        // based on the request/response
42564        this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
42565          status: request.status,
42566          message: 'DASH request error at URL: ' + request.uri,
42567          response: request.response,
42568          // MEDIA_ERR_NETWORK
42569          code: 2
42570        };
42571        if (startingState) {
42572          this.state = startingState;
42573        }
42574        this.trigger('error');
42575        return true;
42576      }
42577    }
42578    /**
42579     * Verify that the container of the sidx segment can be parsed
42580     * and if it can, get and parse that segment.
42581     */
42582
42583    addSidxSegments_(playlist, startingState, cb) {
42584      const sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
42585
42586      if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
42587        // keep this function async
42588        this.mediaRequest_ = window.setTimeout(() => cb(false), 0);
42589        return;
42590      } // resolve the segment URL relative to the playlist
42591
42592      const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
42593      const fin = (err, request) => {
42594        if (this.requestErrored_(err, request, startingState)) {
42595          return;
42596        }
42597        const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
42598        let sidx;
42599        try {
42600          sidx = parseSidx_1(toUint8(request.response).subarray(8));
42601        } catch (e) {
42602          // sidx parsing failed.
42603          this.requestErrored_(e, request, startingState);
42604          return;
42605        }
42606        sidxMapping[sidxKey] = {
42607          sidxInfo: playlist.sidx,
42608          sidx
42609        };
42610        addSidxSegmentsToPlaylist$1(playlist, sidx, playlist.sidx.resolvedUri);
42611        return cb(true);
42612      };
42613      this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => {
42614        if (err) {
42615          return fin(err, request);
42616        }
42617        if (!container || container !== 'mp4') {
42618          return fin({
42619            status: request.status,
42620            message: `Unsupported ${container || 'unknown'} container type for sidx segment at URL: ${uri}`,
42621            // response is just bytes in this case
42622            // but we really don't want to return that.
42623            response: '',
42624            playlist,
42625            internal: true,
42626            playlistExclusionDuration: Infinity,
42627            // MEDIA_ERR_NETWORK
42628            code: 2
42629          }, request);
42630        } // if we already downloaded the sidx bytes in the container request, use them
42631
42632        const {
42633          offset,
42634          length
42635        } = playlist.sidx.byterange;
42636        if (bytes.length >= length + offset) {
42637          return fin(err, {
42638            response: bytes.subarray(offset, offset + length),
42639            status: request.status,
42640            uri: request.uri
42641          });
42642        } // otherwise request sidx bytes
42643
42644        this.request = this.vhs_.xhr({
42645          uri,
42646          responseType: 'arraybuffer',
42647          headers: segmentXhrHeaders({
42648            byterange: playlist.sidx.byterange
42649          })
42650        }, fin);
42651      });
42652    }
42653    dispose() {
42654      this.trigger('dispose');
42655      this.stopRequest();
42656      this.loadedPlaylists_ = {};
42657      window.clearTimeout(this.minimumUpdatePeriodTimeout_);
42658      window.clearTimeout(this.mediaRequest_);
42659      window.clearTimeout(this.mediaUpdateTimeout);
42660      this.mediaUpdateTimeout = null;
42661      this.mediaRequest_ = null;
42662      this.minimumUpdatePeriodTimeout_ = null;
42663      if (this.mainPlaylistLoader_.createMupOnMedia_) {
42664        this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
42665        this.mainPlaylistLoader_.createMupOnMedia_ = null;
42666      }
42667      this.off();
42668    }
42669    hasPendingRequest() {
42670      return this.request || this.mediaRequest_;
42671    }
42672    stopRequest() {
42673      if (this.request) {
42674        const oldRequest = this.request;
42675        this.request = null;
42676        oldRequest.onreadystatechange = null;
42677        oldRequest.abort();
42678      }
42679    }
42680    media(playlist) {
42681      // getter
42682      if (!playlist) {
42683        return this.media_;
42684      } // setter
42685
42686      if (this.state === 'HAVE_NOTHING') {
42687        throw new Error('Cannot switch media playlist from ' + this.state);
42688      }
42689      const startingState = this.state; // find the playlist object if the target playlist has been specified by URI
42690
42691      if (typeof playlist === 'string') {
42692        if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
42693          throw new Error('Unknown playlist URI: ' + playlist);
42694        }
42695        playlist = this.mainPlaylistLoader_.main.playlists[playlist];
42696      }
42697      const mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
42698
42699      if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
42700        this.state = 'HAVE_METADATA';
42701        this.media_ = playlist; // trigger media change if the active media has been updated
42702
42703        if (mediaChange) {
42704          this.trigger('mediachanging');
42705          this.trigger('mediachange');
42706        }
42707        return;
42708      } // switching to the active playlist is a no-op
42709
42710      if (!mediaChange) {
42711        return;
42712      } // switching from an already loaded playlist
42713
42714      if (this.media_) {
42715        this.trigger('mediachanging');
42716      }
42717      this.addSidxSegments_(playlist, startingState, sidxChanged => {
42718        // everything is ready just continue to haveMetadata
42719        this.haveMetadata({
42720          startingState,
42721          playlist
42722        });
42723      });
42724    }
42725    haveMetadata({
42726      startingState,
42727      playlist
42728    }) {
42729      this.state = 'HAVE_METADATA';
42730      this.loadedPlaylists_[playlist.id] = playlist;
42731      this.mediaRequest_ = null; // This will trigger loadedplaylist
42732
42733      this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
42734      // to resolve setup of media groups
42735
42736      if (startingState === 'HAVE_MAIN_MANIFEST') {
42737        this.trigger('loadedmetadata');
42738      } else {
42739        // trigger media change if the active media has been updated
42740        this.trigger('mediachange');
42741      }
42742    }
42743    pause() {
42744      if (this.mainPlaylistLoader_.createMupOnMedia_) {
42745        this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
42746        this.mainPlaylistLoader_.createMupOnMedia_ = null;
42747      }
42748      this.stopRequest();
42749      window.clearTimeout(this.mediaUpdateTimeout);
42750      this.mediaUpdateTimeout = null;
42751      if (this.isMain_) {
42752        window.clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
42753        this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
42754      }
42755      if (this.state === 'HAVE_NOTHING') {
42756        // If we pause the loader before any data has been retrieved, its as if we never
42757        // started, so reset to an unstarted state.
42758        this.started = false;
42759      }
42760    }
42761    load(isFinalRendition) {
42762      window.clearTimeout(this.mediaUpdateTimeout);
42763      this.mediaUpdateTimeout = null;
42764      const media = this.media();
42765      if (isFinalRendition) {
42766        const delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
42767        this.mediaUpdateTimeout = window.setTimeout(() => this.load(), delay);
42768        return;
42769      } // because the playlists are internal to the manifest, load should either load the
42770      // main manifest, or do nothing but trigger an event
42771
42772      if (!this.started) {
42773        this.start();
42774        return;
42775      }
42776      if (media && !media.endList) {
42777        // Check to see if this is the main loader and the MUP was cleared (this happens
42778        // when the loader was paused). `media` should be set at this point since one is always
42779        // set during `start()`.
42780        if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
42781          // Trigger minimumUpdatePeriod to refresh the main manifest
42782          this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
42783
42784          this.updateMinimumUpdatePeriodTimeout_();
42785        }
42786        this.trigger('mediaupdatetimeout');
42787      } else {
42788        this.trigger('loadedplaylist');
42789      }
42790    }
42791    start() {
42792      this.started = true; // We don't need to request the main manifest again
42793      // Call this asynchronously to match the xhr request behavior below
42794
42795      if (!this.isMain_) {
42796        this.mediaRequest_ = window.setTimeout(() => this.haveMain_(), 0);
42797        return;
42798      }
42799      this.requestMain_((req, mainChanged) => {
42800        this.haveMain_();
42801        if (!this.hasPendingRequest() && !this.media_) {
42802          this.media(this.mainPlaylistLoader_.main.playlists[0]);
42803        }
42804      });
42805    }
42806    requestMain_(cb) {
42807      this.request = this.vhs_.xhr({
42808        uri: this.mainPlaylistLoader_.srcUrl,
42809        withCredentials: this.withCredentials
42810      }, (error, req) => {
42811        if (this.requestErrored_(error, req)) {
42812          if (this.state === 'HAVE_NOTHING') {
42813            this.started = false;
42814          }
42815          return;
42816        }
42817        const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
42818        this.mainPlaylistLoader_.mainXml_ = req.responseText;
42819        if (req.responseHeaders && req.responseHeaders.date) {
42820          this.mainLoaded_ = Date.parse(req.responseHeaders.date);
42821        } else {
42822          this.mainLoaded_ = Date.now();
42823        }
42824        this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(this.mainPlaylistLoader_.srcUrl, req);
42825        if (mainChanged) {
42826          this.handleMain_();
42827          this.syncClientServerClock_(() => {
42828            return cb(req, mainChanged);
42829          });
42830          return;
42831        }
42832        return cb(req, mainChanged);
42833      });
42834    }
42835    /**
42836     * Parses the main xml for UTCTiming node to sync the client clock to the server
42837     * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
42838     *
42839     * @param {Function} done
42840     *        Function to call when clock sync has completed
42841     */
42842
42843    syncClientServerClock_(done) {
42844      const utcTiming = parseUTCTiming(this.mainPlaylistLoader_.mainXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
42845      // server clock
42846
42847      if (utcTiming === null) {
42848        this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
42849        return done();
42850      }
42851      if (utcTiming.method === 'DIRECT') {
42852        this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
42853        return done();
42854      }
42855      this.request = this.vhs_.xhr({
42856        uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
42857        method: utcTiming.method,
42858        withCredentials: this.withCredentials
42859      }, (error, req) => {
42860        // disposed
42861        if (!this.request) {
42862          return;
42863        }
42864        if (error) {
42865          // sync request failed, fall back to using date header from mpd
42866          // TODO: log warning
42867          this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
42868          return done();
42869        }
42870        let serverTime;
42871        if (utcTiming.method === 'HEAD') {
42872          if (!req.responseHeaders || !req.responseHeaders.date) {
42873            // expected date header not preset, fall back to using date header from mpd
42874            // TODO: log warning
42875            serverTime = this.mainLoaded_;
42876          } else {
42877            serverTime = Date.parse(req.responseHeaders.date);
42878          }
42879        } else {
42880          serverTime = Date.parse(req.responseText);
42881        }
42882        this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
42883        done();
42884      });
42885    }
42886    haveMain_() {
42887      this.state = 'HAVE_MAIN_MANIFEST';
42888      if (this.isMain_) {
42889        // We have the main playlist at this point, so
42890        // trigger this to allow PlaylistController
42891        // to make an initial playlist selection
42892        this.trigger('loadedplaylist');
42893      } else if (!this.media_) {
42894        // no media playlist was specifically selected so select
42895        // the one the child playlist loader was created with
42896        this.media(this.childPlaylist_);
42897      }
42898    }
42899    handleMain_() {
42900      // clear media request
42901      this.mediaRequest_ = null;
42902      const oldMain = this.mainPlaylistLoader_.main;
vendor: 6,165 bytes, lines 42903-43059
42903      let newMain = parseMainXml({
42904        mainXml: this.mainPlaylistLoader_.mainXml_,
42905        srcUrl: this.mainPlaylistLoader_.srcUrl,
42906        clientOffset: this.mainPlaylistLoader_.clientOffset_,
42907        sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
42908        previousManifest: oldMain
42909      }); // if we have an old main to compare the new main against
42910
42911      if (oldMain) {
42912        newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
42913      } // only update main if we have a new main
42914
42915      this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
42916      const location = this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
42917      if (location && location !== this.mainPlaylistLoader_.srcUrl) {
42918        this.mainPlaylistLoader_.srcUrl = location;
42919      }
42920      if (!oldMain || newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
42921        this.updateMinimumUpdatePeriodTimeout_();
42922      }
42923      this.addEventStreamToMetadataTrack_(newMain);
42924      return Boolean(newMain);
42925    }
42926    updateMinimumUpdatePeriodTimeout_() {
42927      const mpl = this.mainPlaylistLoader_; // cancel any pending creation of mup on media
42928      // a new one will be added if needed.
42929
42930      if (mpl.createMupOnMedia_) {
42931        mpl.off('loadedmetadata', mpl.createMupOnMedia_);
42932        mpl.createMupOnMedia_ = null;
42933      } // clear any pending timeouts
42934
42935      if (mpl.minimumUpdatePeriodTimeout_) {
42936        window.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
42937        mpl.minimumUpdatePeriodTimeout_ = null;
42938      }
42939      let mup = mpl.main && mpl.main.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
42940      // MPD has no future validity, so a new one will need to be acquired when new
42941      // media segments are to be made available. Thus, we use the target duration
42942      // in this case
42943
42944      if (mup === 0) {
42945        if (mpl.media()) {
42946          mup = mpl.media().targetDuration * 1000;
42947        } else {
42948          mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
42949          mpl.one('loadedmetadata', mpl.createMupOnMedia_);
42950        }
42951      } // if minimumUpdatePeriod is invalid or <= zero, which
42952      // can happen when a live video becomes VOD. skip timeout
42953      // creation.
42954
42955      if (typeof mup !== 'number' || mup <= 0) {
42956        if (mup < 0) {
42957          this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
42958        }
42959        return;
42960      }
42961      this.createMUPTimeout_(mup);
42962    }
42963    createMUPTimeout_(mup) {
42964      const mpl = this.mainPlaylistLoader_;
42965      mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
42966        mpl.minimumUpdatePeriodTimeout_ = null;
42967        mpl.trigger('minimumUpdatePeriod');
42968        mpl.createMUPTimeout_(mup);
42969      }, mup);
42970    }
42971    /**
42972     * Sends request to refresh the main xml and updates the parsed main manifest
42973     */
42974
42975    refreshXml_() {
42976      this.requestMain_((req, mainChanged) => {
42977        if (!mainChanged) {
42978          return;
42979        }
42980        if (this.media_) {
42981          this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
42982        } // This will filter out updated sidx info from the mapping
42983
42984        this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.sidxMapping_);
42985        this.addSidxSegments_(this.media(), this.state, sidxChanged => {
42986          // TODO: do we need to reload the current playlist?
42987          this.refreshMedia_(this.media().id);
42988        });
42989      });
42990    }
42991    /**
42992     * Refreshes the media playlist by re-parsing the main xml and updating playlist
42993     * references. If this is an alternate loader, the updated parsed manifest is retrieved
42994     * from the main loader.
42995     */
42996
42997    refreshMedia_(mediaID) {
42998      if (!mediaID) {
42999        throw new Error('refreshMedia_ must take a media id');
43000      } // for main we have to reparse the main xml
43001      // to re-create segments based on current timing values
43002      // which may change media. We only skip updating the main manifest
43003      // if this is the first time this.media_ is being set.
43004      // as main was just parsed in that case.
43005
43006      if (this.media_ && this.isMain_) {
43007        this.handleMain_();
43008      }
43009      const playlists = this.mainPlaylistLoader_.main.playlists;
43010      const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
43011      if (mediaChanged) {
43012        this.media_ = playlists[mediaID];
43013      } else {
43014        this.trigger('playlistunchanged');
43015      }
43016      if (!this.mediaUpdateTimeout) {
43017        const createMediaUpdateTimeout = () => {
43018          if (this.media().endList) {
43019            return;
43020          }
43021          this.mediaUpdateTimeout = window.setTimeout(() => {
43022            this.trigger('mediaupdatetimeout');
43023            createMediaUpdateTimeout();
43024          }, refreshDelay(this.media(), Boolean(mediaChanged)));
43025        };
43026        createMediaUpdateTimeout();
43027      }
43028      this.trigger('loadedplaylist');
43029    }
43030    /**
43031     * Takes eventstream data from a parsed DASH manifest and adds it to the metadata text track.
43032     *
43033     * @param {manifest} newMain the newly parsed manifest
43034     */
43035
43036    addEventStreamToMetadataTrack_(newMain) {
43037      // Only add new event stream metadata if we have a new manifest.
43038      if (newMain && this.mainPlaylistLoader_.main.eventStream) {
43039        // convert EventStream to ID3-like data.
43040        const metadataArray = this.mainPlaylistLoader_.main.eventStream.map(eventStreamNode => {
43041          return {
43042            cueTime: eventStreamNode.start,
43043            frames: [{
43044              data: eventStreamNode.messageData
43045            }]
43046          };
43047        });
43048        this.addMetadataToTextTrack('EventStream', metadataArray, this.mainPlaylistLoader_.main.duration);
43049      }
43050    }
43051    /**
43052     * Returns the key ID set from a playlist
43053     *
43054     * @param {playlist} playlist to fetch the key ID set from.
43055     * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
43056     */
43057
43058    getKeyIdSet(playlist) {
43059      if (playlist.contentProtection) {
vendor: 32,319 bytes, lines 43060-43971
43060        const keyIds = new Set();
43061        for (const keysystem in playlist.contentProtection) {
43062          const defaultKID = playlist.contentProtection[keysystem].attributes['cenc:default_KID'];
43063          if (defaultKID) {
43064            // DASH keyIds are separated by dashes.
43065            keyIds.add(defaultKID.replace(/-/g, '').toLowerCase());
43066          }
43067        }
43068        return keyIds;
43069      }
43070    }
43071  }
43072  var Config = {
43073    GOAL_BUFFER_LENGTH: 30,
43074    MAX_GOAL_BUFFER_LENGTH: 60,
43075    BACK_BUFFER_LENGTH: 30,
43076    GOAL_BUFFER_LENGTH_RATE: 1,
43077    // 0.5 MB/s
43078    INITIAL_BANDWIDTH: 4194304,
43079    // A fudge factor to apply to advertised playlist bitrates to account for
43080    // temporary flucations in client bandwidth
43081    BANDWIDTH_VARIANCE: 1.2,
43082    // How much of the buffer must be filled before we consider upswitching
43083    BUFFER_LOW_WATER_LINE: 0,
43084    MAX_BUFFER_LOW_WATER_LINE: 30,
43085    // TODO: Remove this when experimentalBufferBasedABR is removed
43086    EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
43087    BUFFER_LOW_WATER_LINE_RATE: 1,
43088    // If the buffer is greater than the high water line, we won't switch down
43089    BUFFER_HIGH_WATER_LINE: 30
43090  };
43091  const stringToArrayBuffer = string => {
43092    const view = new Uint8Array(new ArrayBuffer(string.length));
43093    for (let i = 0; i < string.length; i++) {
43094      view[i] = string.charCodeAt(i);
43095    }
43096    return view.buffer;
43097  };
43098
43099  /* global Blob, BlobBuilder, Worker */
43100  // unify worker interface
43101  const browserWorkerPolyFill = function (workerObj) {
43102    // node only supports on/off
43103    workerObj.on = workerObj.addEventListener;
43104    workerObj.off = workerObj.removeEventListener;
43105    return workerObj;
43106  };
43107  const createObjectURL = function (str) {
43108    try {
43109      return URL.createObjectURL(new Blob([str], {
43110        type: 'application/javascript'
43111      }));
43112    } catch (e) {
43113      const blob = new BlobBuilder();
43114      blob.append(str);
43115      return URL.createObjectURL(blob.getBlob());
43116    }
43117  };
43118  const factory = function (code) {
43119    return function () {
43120      const objectUrl = createObjectURL(code);
43121      const worker = browserWorkerPolyFill(new Worker(objectUrl));
43122      worker.objURL = objectUrl;
43123      const terminate = worker.terminate;
43124      worker.on = worker.addEventListener;
43125      worker.off = worker.removeEventListener;
43126      worker.terminate = function () {
43127        URL.revokeObjectURL(objectUrl);
43128        return terminate.call(this);
43129      };
43130      return worker;
43131    };
43132  };
43133  const transform = function (code) {
43134    return `var browserWorkerPolyFill = ${browserWorkerPolyFill.toString()};\n` + 'browserWorkerPolyFill(self);\n' + code;
43135  };
43136  const getWorkerString = function (fn) {
43137    return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
43138  };
43139
43140  /* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
43141  const workerCode$1 = transform(getWorkerString(function () {
43142    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
43143    /**
43144     * mux.js
43145     *
43146     * Copyright (c) Brightcove
43147     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43148     *
43149     * A lightweight readable stream implemention that handles event dispatching.
43150     * Objects that inherit from streams should call init in their constructors.
43151     */
43152
43153    var Stream$8 = function () {
43154      this.init = function () {
43155        var listeners = {};
43156        /**
43157         * Add a listener for a specified event type.
43158         * @param type {string} the event name
43159         * @param listener {function} the callback to be invoked when an event of
43160         * the specified type occurs
43161         */
43162
43163        this.on = function (type, listener) {
43164          if (!listeners[type]) {
43165            listeners[type] = [];
43166          }
43167          listeners[type] = listeners[type].concat(listener);
43168        };
43169        /**
43170         * Remove a listener for a specified event type.
43171         * @param type {string} the event name
43172         * @param listener {function} a function previously registered for this
43173         * type of event through `on`
43174         */
43175
43176        this.off = function (type, listener) {
43177          var index;
43178          if (!listeners[type]) {
43179            return false;
43180          }
43181          index = listeners[type].indexOf(listener);
43182          listeners[type] = listeners[type].slice();
43183          listeners[type].splice(index, 1);
43184          return index > -1;
43185        };
43186        /**
43187         * Trigger an event of the specified type on this stream. Any additional
43188         * arguments to this function are passed as parameters to event listeners.
43189         * @param type {string} the event name
43190         */
43191
43192        this.trigger = function (type) {
43193          var callbacks, i, length, args;
43194          callbacks = listeners[type];
43195          if (!callbacks) {
43196            return;
43197          } // Slicing the arguments on every invocation of this method
43198          // can add a significant amount of overhead. Avoid the
43199          // intermediate object creation for the common case of a
43200          // single callback argument
43201
43202          if (arguments.length === 2) {
43203            length = callbacks.length;
43204            for (i = 0; i < length; ++i) {
43205              callbacks[i].call(this, arguments[1]);
43206            }
43207          } else {
43208            args = [];
43209            i = arguments.length;
43210            for (i = 1; i < arguments.length; ++i) {
43211              args.push(arguments[i]);
43212            }
43213            length = callbacks.length;
43214            for (i = 0; i < length; ++i) {
43215              callbacks[i].apply(this, args);
43216            }
43217          }
43218        };
43219        /**
43220         * Destroys the stream and cleans up.
43221         */
43222
43223        this.dispose = function () {
43224          listeners = {};
43225        };
43226      };
43227    };
43228    /**
43229     * Forwards all `data` events on this stream to the destination stream. The
43230     * destination stream should provide a method `push` to receive the data
43231     * events as they arrive.
43232     * @param destination {stream} the stream that will receive all `data` events
43233     * @param autoFlush {boolean} if false, we will not call `flush` on the destination
43234     *                            when the current stream emits a 'done' event
43235     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
43236     */
43237
43238    Stream$8.prototype.pipe = function (destination) {
43239      this.on('data', function (data) {
43240        destination.push(data);
43241      });
43242      this.on('done', function (flushSource) {
43243        destination.flush(flushSource);
43244      });
43245      this.on('partialdone', function (flushSource) {
43246        destination.partialFlush(flushSource);
43247      });
43248      this.on('endedtimeline', function (flushSource) {
43249        destination.endTimeline(flushSource);
43250      });
43251      this.on('reset', function (flushSource) {
43252        destination.reset(flushSource);
43253      });
43254      return destination;
43255    }; // Default stream functions that are expected to be overridden to perform
43256    // actual work. These are provided by the prototype as a sort of no-op
43257    // implementation so that we don't have to check for their existence in the
43258    // `pipe` function above.
43259
43260    Stream$8.prototype.push = function (data) {
43261      this.trigger('data', data);
43262    };
43263    Stream$8.prototype.flush = function (flushSource) {
43264      this.trigger('done', flushSource);
43265    };
43266    Stream$8.prototype.partialFlush = function (flushSource) {
43267      this.trigger('partialdone', flushSource);
43268    };
43269    Stream$8.prototype.endTimeline = function (flushSource) {
43270      this.trigger('endedtimeline', flushSource);
43271    };
43272    Stream$8.prototype.reset = function (flushSource) {
43273      this.trigger('reset', flushSource);
43274    };
43275    var stream = Stream$8;
43276    var MAX_UINT32$1 = Math.pow(2, 32);
43277    var getUint64$3 = function (uint8) {
43278      var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
43279      var value;
43280      if (dv.getBigUint64) {
43281        value = dv.getBigUint64(0);
43282        if (value < Number.MAX_SAFE_INTEGER) {
43283          return Number(value);
43284        }
43285        return value;
43286      }
43287      return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
43288    };
43289    var numbers = {
43290      getUint64: getUint64$3,
43291      MAX_UINT32: MAX_UINT32$1
43292    };
43293    /**
43294     * mux.js
43295     *
43296     * Copyright (c) Brightcove
43297     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43298     *
43299     * Functions that generate fragmented MP4s suitable for use with Media
43300     * Source Extensions.
43301     */
43302
43303    var MAX_UINT32 = numbers.MAX_UINT32;
43304    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
43305
43306    (function () {
43307      var i;
43308      types = {
43309        avc1: [],
43310        // codingname
43311        avcC: [],
43312        btrt: [],
43313        dinf: [],
43314        dref: [],
43315        esds: [],
43316        ftyp: [],
43317        hdlr: [],
43318        mdat: [],
43319        mdhd: [],
43320        mdia: [],
43321        mfhd: [],
43322        minf: [],
43323        moof: [],
43324        moov: [],
43325        mp4a: [],
43326        // codingname
43327        mvex: [],
43328        mvhd: [],
43329        pasp: [],
43330        sdtp: [],
43331        smhd: [],
43332        stbl: [],
43333        stco: [],
43334        stsc: [],
43335        stsd: [],
43336        stsz: [],
43337        stts: [],
43338        styp: [],
43339        tfdt: [],
43340        tfhd: [],
43341        traf: [],
43342        trak: [],
43343        trun: [],
43344        trex: [],
43345        tkhd: [],
43346        vmhd: []
43347      }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
43348      // don't throw an error
43349
43350      if (typeof Uint8Array === 'undefined') {
43351        return;
43352      }
43353      for (i in types) {
43354        if (types.hasOwnProperty(i)) {
43355          types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
43356        }
43357      }
43358      MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
43359      AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
43360      MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
43361      VIDEO_HDLR = new Uint8Array([0x00,
43362      // version 0
43363      0x00, 0x00, 0x00,
43364      // flags
43365      0x00, 0x00, 0x00, 0x00,
43366      // pre_defined
43367      0x76, 0x69, 0x64, 0x65,
43368      // handler_type: 'vide'
43369      0x00, 0x00, 0x00, 0x00,
43370      // reserved
43371      0x00, 0x00, 0x00, 0x00,
43372      // reserved
43373      0x00, 0x00, 0x00, 0x00,
43374      // reserved
43375      0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
43376      ]);
43377
43378      AUDIO_HDLR = new Uint8Array([0x00,
43379      // version 0
43380      0x00, 0x00, 0x00,
43381      // flags
43382      0x00, 0x00, 0x00, 0x00,
43383      // pre_defined
43384      0x73, 0x6f, 0x75, 0x6e,
43385      // handler_type: 'soun'
43386      0x00, 0x00, 0x00, 0x00,
43387      // reserved
43388      0x00, 0x00, 0x00, 0x00,
43389      // reserved
43390      0x00, 0x00, 0x00, 0x00,
43391      // reserved
43392      0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
43393      ]);
43394
43395      HDLR_TYPES = {
43396        video: VIDEO_HDLR,
43397        audio: AUDIO_HDLR
43398      };
43399      DREF = new Uint8Array([0x00,
43400      // version 0
43401      0x00, 0x00, 0x00,
43402      // flags
43403      0x00, 0x00, 0x00, 0x01,
43404      // entry_count
43405      0x00, 0x00, 0x00, 0x0c,
43406      // entry_size
43407      0x75, 0x72, 0x6c, 0x20,
43408      // 'url' type
43409      0x00,
43410      // version 0
43411      0x00, 0x00, 0x01 // entry_flags
43412      ]);
43413
43414      SMHD = new Uint8Array([0x00,
43415      // version
43416      0x00, 0x00, 0x00,
43417      // flags
43418      0x00, 0x00,
43419      // balance, 0 means centered
43420      0x00, 0x00 // reserved
43421      ]);
43422
43423      STCO = new Uint8Array([0x00,
43424      // version
43425      0x00, 0x00, 0x00,
43426      // flags
43427      0x00, 0x00, 0x00, 0x00 // entry_count
43428      ]);
43429
43430      STSC = STCO;
43431      STSZ = new Uint8Array([0x00,
43432      // version
43433      0x00, 0x00, 0x00,
43434      // flags
43435      0x00, 0x00, 0x00, 0x00,
43436      // sample_size
43437      0x00, 0x00, 0x00, 0x00 // sample_count
43438      ]);
43439
43440      STTS = STCO;
43441      VMHD = new Uint8Array([0x00,
43442      // version
43443      0x00, 0x00, 0x01,
43444      // flags
43445      0x00, 0x00,
43446      // graphicsmode
43447      0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
43448      ]);
43449    })();
43450
43451    box = function (type) {
43452      var payload = [],
43453        size = 0,
43454        i,
43455        result,
43456        view;
43457      for (i = 1; i < arguments.length; i++) {
43458        payload.push(arguments[i]);
43459      }
43460      i = payload.length; // calculate the total size we need to allocate
43461
43462      while (i--) {
43463        size += payload[i].byteLength;
43464      }
43465      result = new Uint8Array(size + 8);
43466      view = new DataView(result.buffer, result.byteOffset, result.byteLength);
43467      view.setUint32(0, result.byteLength);
43468      result.set(type, 4); // copy the payload into the result
43469
43470      for (i = 0, size = 8; i < payload.length; i++) {
43471        result.set(payload[i], size);
43472        size += payload[i].byteLength;
43473      }
43474      return result;
43475    };
43476    dinf = function () {
43477      return box(types.dinf, box(types.dref, DREF));
43478    };
43479    esds = function (track) {
43480      return box(types.esds, new Uint8Array([0x00,
43481      // version
43482      0x00, 0x00, 0x00,
43483      // flags
43484      // ES_Descriptor
43485      0x03,
43486      // tag, ES_DescrTag
43487      0x19,
43488      // length
43489      0x00, 0x00,
43490      // ES_ID
43491      0x00,
43492      // streamDependenceFlag, URL_flag, reserved, streamPriority
43493      // DecoderConfigDescriptor
43494      0x04,
43495      // tag, DecoderConfigDescrTag
43496      0x11,
43497      // length
43498      0x40,
43499      // object type
43500      0x15,
43501      // streamType
43502      0x00, 0x06, 0x00,
43503      // bufferSizeDB
43504      0x00, 0x00, 0xda, 0xc0,
43505      // maxBitrate
43506      0x00, 0x00, 0xda, 0xc0,
43507      // avgBitrate
43508      // DecoderSpecificInfo
43509      0x05,
43510      // tag, DecoderSpecificInfoTag
43511      0x02,
43512      // length
43513      // ISO/IEC 14496-3, AudioSpecificConfig
43514      // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
43515      track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
43516      ]));
43517    };
43518
43519    ftyp = function () {
43520      return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
43521    };
43522    hdlr = function (type) {
43523      return box(types.hdlr, HDLR_TYPES[type]);
43524    };
43525    mdat = function (data) {
43526      return box(types.mdat, data);
43527    };
43528    mdhd = function (track) {
43529      var result = new Uint8Array([0x00,
43530      // version 0
43531      0x00, 0x00, 0x00,
43532      // flags
43533      0x00, 0x00, 0x00, 0x02,
43534      // creation_time
43535      0x00, 0x00, 0x00, 0x03,
43536      // modification_time
43537      0x00, 0x01, 0x5f, 0x90,
43538      // timescale, 90,000 "ticks" per second
43539      track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF,
43540      // duration
43541      0x55, 0xc4,
43542      // 'und' language (undetermined)
43543      0x00, 0x00]); // Use the sample rate from the track metadata, when it is
43544      // defined. The sample rate can be parsed out of an ADTS header, for
43545      // instance.
43546
43547      if (track.samplerate) {
43548        result[12] = track.samplerate >>> 24 & 0xFF;
43549        result[13] = track.samplerate >>> 16 & 0xFF;
43550        result[14] = track.samplerate >>> 8 & 0xFF;
43551        result[15] = track.samplerate & 0xFF;
43552      }
43553      return box(types.mdhd, result);
43554    };
43555    mdia = function (track) {
43556      return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
43557    };
43558    mfhd = function (sequenceNumber) {
43559      return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
43560      // flags
43561      (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
43562      ]));
43563    };
43564
43565    minf = function (track) {
43566      return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
43567    };
43568    moof = function (sequenceNumber, tracks) {
43569      var trackFragments = [],
43570        i = tracks.length; // build traf boxes for each track fragment
43571
43572      while (i--) {
43573        trackFragments[i] = traf(tracks[i]);
43574      }
43575      return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
43576    };
43577    /**
43578     * Returns a movie box.
43579     * @param tracks {array} the tracks associated with this movie
43580     * @see ISO/IEC 14496-12:2012(E), section 8.2.1
43581     */
43582
43583    moov = function (tracks) {
43584      var i = tracks.length,
43585        boxes = [];
43586      while (i--) {
43587        boxes[i] = trak(tracks[i]);
43588      }
43589      return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
43590    };
43591    mvex = function (tracks) {
43592      var i = tracks.length,
43593        boxes = [];
43594      while (i--) {
43595        boxes[i] = trex(tracks[i]);
43596      }
43597      return box.apply(null, [types.mvex].concat(boxes));
43598    };
43599    mvhd = function (duration) {
43600      var bytes = new Uint8Array([0x00,
43601      // version 0
43602      0x00, 0x00, 0x00,
43603      // flags
43604      0x00, 0x00, 0x00, 0x01,
43605      // creation_time
43606      0x00, 0x00, 0x00, 0x02,
43607      // modification_time
43608      0x00, 0x01, 0x5f, 0x90,
43609      // timescale, 90,000 "ticks" per second
43610      (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF,
43611      // duration
43612      0x00, 0x01, 0x00, 0x00,
43613      // 1.0 rate
43614      0x01, 0x00,
43615      // 1.0 volume
43616      0x00, 0x00,
43617      // reserved
43618      0x00, 0x00, 0x00, 0x00,
43619      // reserved
43620      0x00, 0x00, 0x00, 0x00,
43621      // reserved
43622      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,
43623      // transformation: unity matrix
43624      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43625      // pre_defined
43626      0xff, 0xff, 0xff, 0xff // next_track_ID
43627      ]);
43628
43629      return box(types.mvhd, bytes);
43630    };
43631    sdtp = function (track) {
43632      var samples = track.samples || [],
43633        bytes = new Uint8Array(4 + samples.length),
43634        flags,
43635        i; // leave the full box header (4 bytes) all zero
43636      // write the sample table
43637
43638      for (i = 0; i < samples.length; i++) {
43639        flags = samples[i].flags;
43640        bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
43641      }
43642      return box(types.sdtp, bytes);
43643    };
43644    stbl = function (track) {
43645      return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
43646    };
43647    (function () {
43648      var videoSample, audioSample;
43649      stsd = function (track) {
43650        return box(types.stsd, new Uint8Array([0x00,
43651        // version 0
43652        0x00, 0x00, 0x00,
43653        // flags
43654        0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
43655      };
43656      videoSample = function (track) {
43657        var sps = track.sps || [],
43658          pps = track.pps || [],
43659          sequenceParameterSets = [],
43660          pictureParameterSets = [],
43661          i,
43662          avc1Box; // assemble the SPSs
43663
43664        for (i = 0; i < sps.length; i++) {
43665          sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
43666          sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
43667
43668          sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
43669        } // assemble the PPSs
43670
43671        for (i = 0; i < pps.length; i++) {
43672          pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
43673          pictureParameterSets.push(pps[i].byteLength & 0xFF);
43674          pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
43675        }
43676        avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43677        // reserved
43678        0x00, 0x01,
43679        // data_reference_index
43680        0x00, 0x00,
43681        // pre_defined
43682        0x00, 0x00,
43683        // reserved
43684        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43685        // pre_defined
43686        (track.width & 0xff00) >> 8, track.width & 0xff,
43687        // width
43688        (track.height & 0xff00) >> 8, track.height & 0xff,
43689        // height
43690        0x00, 0x48, 0x00, 0x00,
43691        // horizresolution
43692        0x00, 0x48, 0x00, 0x00,
43693        // vertresolution
43694        0x00, 0x00, 0x00, 0x00,
43695        // reserved
43696        0x00, 0x01,
43697        // frame_count
43698        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,
43699        // compressorname
43700        0x00, 0x18,
43701        // depth = 24
43702        0x11, 0x11 // pre_defined = -1
43703        ]), box(types.avcC, new Uint8Array([0x01,
43704        // configurationVersion
43705        track.profileIdc,
43706        // AVCProfileIndication
43707        track.profileCompatibility,
43708        // profile_compatibility
43709        track.levelIdc,
43710        // AVCLevelIndication
43711        0xff // lengthSizeMinusOne, hard-coded to 4 bytes
43712        ].concat([sps.length],
43713        // numOfSequenceParameterSets
43714        sequenceParameterSets,
43715        // "SPS"
43716        [pps.length],
43717        // numOfPictureParameterSets
43718        pictureParameterSets // "PPS"
43719        ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
43720        // bufferSizeDB
43721        0x00, 0x2d, 0xc6, 0xc0,
43722        // maxBitrate
43723        0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
43724        ]))];
43725
43726        if (track.sarRatio) {
43727          var hSpacing = track.sarRatio[0],
43728            vSpacing = track.sarRatio[1];
43729          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])));
43730        }
43731        return box.apply(null, avc1Box);
43732      };
43733      audioSample = function (track) {
43734        return box(types.mp4a, new Uint8Array([
43735        // SampleEntry, ISO/IEC 14496-12
43736        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43737        // reserved
43738        0x00, 0x01,
43739        // data_reference_index
43740        // AudioSampleEntry, ISO/IEC 14496-12
43741        0x00, 0x00, 0x00, 0x00,
43742        // reserved
43743        0x00, 0x00, 0x00, 0x00,
43744        // reserved
43745        (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff,
43746        // channelcount
43747        (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff,
43748        // samplesize
43749        0x00, 0x00,
43750        // pre_defined
43751        0x00, 0x00,
43752        // reserved
43753        (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
43754        // MP4AudioSampleEntry, ISO/IEC 14496-14
43755        ]), esds(track));
43756      };
43757    })();
43758    tkhd = function (track) {
43759      var result = new Uint8Array([0x00,
43760      // version 0
43761      0x00, 0x00, 0x07,
43762      // flags
43763      0x00, 0x00, 0x00, 0x00,
43764      // creation_time
43765      0x00, 0x00, 0x00, 0x00,
43766      // modification_time
43767      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43768      // track_ID
43769      0x00, 0x00, 0x00, 0x00,
43770      // reserved
43771      (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF,
43772      // duration
43773      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43774      // reserved
43775      0x00, 0x00,
43776      // layer
43777      0x00, 0x00,
43778      // alternate_group
43779      0x01, 0x00,
43780      // non-audio track volume
43781      0x00, 0x00,
43782      // reserved
43783      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,
43784      // transformation: unity matrix
43785      (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00,
43786      // width
43787      (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
43788      ]);
43789
43790      return box(types.tkhd, result);
43791    };
43792    /**
43793     * Generate a track fragment (traf) box. A traf box collects metadata
43794     * about tracks in a movie fragment (moof) box.
43795     */
43796
43797    traf = function (track) {
43798      var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
43799      trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00,
43800      // version 0
43801      0x00, 0x00, 0x3a,
43802      // flags
43803      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43804      // track_ID
43805      0x00, 0x00, 0x00, 0x01,
43806      // sample_description_index
43807      0x00, 0x00, 0x00, 0x00,
43808      // default_sample_duration
43809      0x00, 0x00, 0x00, 0x00,
43810      // default_sample_size
43811      0x00, 0x00, 0x00, 0x00 // default_sample_flags
43812      ]));
43813
43814      upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
43815      lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
43816      trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01,
43817      // version 1
43818      0x00, 0x00, 0x00,
43819      // flags
43820      // baseMediaDecodeTime
43821      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
43822      // the containing moof to the first payload byte of the associated
43823      // mdat
43824
43825      dataOffset = 32 +
43826      // tfhd
43827      20 +
43828      // tfdt
43829      8 +
43830      // traf header
43831      16 +
43832      // mfhd
43833      8 +
43834      // moof header
43835      8; // mdat header
43836      // audio tracks require less metadata
43837
43838      if (track.type === 'audio') {
43839        trackFragmentRun = trun$1(track, dataOffset);
43840        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
43841      } // video tracks should contain an independent and disposable samples
43842      // box (sdtp)
43843      // generate one and adjust offsets to match
43844
43845      sampleDependencyTable = sdtp(track);
43846      trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
43847      return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
43848    };
43849    /**
43850     * Generate a track box.
43851     * @param track {object} a track definition
43852     * @return {Uint8Array} the track box
43853     */
43854
43855    trak = function (track) {
43856      track.duration = track.duration || 0xffffffff;
43857      return box(types.trak, tkhd(track), mdia(track));
43858    };
43859    trex = function (track) {
43860      var result = new Uint8Array([0x00,
43861      // version 0
43862      0x00, 0x00, 0x00,
43863      // flags
43864      (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43865      // track_ID
43866      0x00, 0x00, 0x00, 0x01,
43867      // default_sample_description_index
43868      0x00, 0x00, 0x00, 0x00,
43869      // default_sample_duration
43870      0x00, 0x00, 0x00, 0x00,
43871      // default_sample_size
43872      0x00, 0x01, 0x00, 0x01 // default_sample_flags
43873      ]); // the last two bytes of default_sample_flags is the sample
43874      // degradation priority, a hint about the importance of this sample
43875      // relative to others. Lower the degradation priority for all sample
43876      // types other than video.
43877
43878      if (track.type !== 'video') {
43879        result[result.length - 1] = 0x00;
43880      }
43881      return box(types.trex, result);
43882    };
43883    (function () {
43884      var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
43885      // duration is present for the first sample, it will be present for
43886      // all subsequent samples.
43887      // see ISO/IEC 14496-12:2012, Section 8.8.8.1
43888
43889      trunHeader = function (samples, offset) {
43890        var durationPresent = 0,
43891          sizePresent = 0,
43892          flagsPresent = 0,
43893          compositionTimeOffset = 0; // trun flag constants
43894
43895        if (samples.length) {
43896          if (samples[0].duration !== undefined) {
43897            durationPresent = 0x1;
43898          }
43899          if (samples[0].size !== undefined) {
43900            sizePresent = 0x2;
43901          }
43902          if (samples[0].flags !== undefined) {
43903            flagsPresent = 0x4;
43904          }
43905          if (samples[0].compositionTimeOffset !== undefined) {
43906            compositionTimeOffset = 0x8;
43907          }
43908        }
43909        return [0x00,
43910        // version 0
43911        0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01,
43912        // flags
43913        (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF,
43914        // sample_count
43915        (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
43916        ];
43917      };
43918
43919      videoTrun = function (track, offset) {
43920        var bytesOffest, bytes, header, samples, sample, i;
43921        samples = track.samples || [];
43922        offset += 8 + 12 + 16 * samples.length;
43923        header = trunHeader(samples, offset);
43924        bytes = new Uint8Array(header.length + samples.length * 16);
43925        bytes.set(header);
43926        bytesOffest = header.length;
43927        for (i = 0; i < samples.length; i++) {
43928          sample = samples[i];
43929          bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43930          bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43931          bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43932          bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43933
43934          bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43935          bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43936          bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43937          bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43938
43939          bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
43940          bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
43941          bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
43942          bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
43943
43944          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
43945          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
43946          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
43947          bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
43948        }
43949
43950        return box(types.trun, bytes);
43951      };
43952      audioTrun = function (track, offset) {
43953        var bytes, bytesOffest, header, samples, sample, i;
43954        samples = track.samples || [];
43955        offset += 8 + 12 + 8 * samples.length;
43956        header = trunHeader(samples, offset);
43957        bytes = new Uint8Array(header.length + samples.length * 8);
43958        bytes.set(header);
43959        bytesOffest = header.length;
43960        for (i = 0; i < samples.length; i++) {
43961          sample = samples[i];
43962          bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43963          bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43964          bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43965          bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43966
43967          bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43968          bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43969          bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43970          bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43971        }
vendor: 18,520 bytes, lines 43972-44470
43972
43973        return box(types.trun, bytes);
43974      };
43975      trun$1 = function (track, offset) {
43976        if (track.type === 'audio') {
43977          return audioTrun(track, offset);
43978        }
43979        return videoTrun(track, offset);
43980      };
43981    })();
43982    var mp4Generator = {
43983      ftyp: ftyp,
43984      mdat: mdat,
43985      moof: moof,
43986      moov: moov,
43987      initSegment: function (tracks) {
43988        var fileType = ftyp(),
43989          movie = moov(tracks),
43990          result;
43991        result = new Uint8Array(fileType.byteLength + movie.byteLength);
43992        result.set(fileType);
43993        result.set(movie, fileType.byteLength);
43994        return result;
43995      }
43996    };
43997    /**
43998     * mux.js
43999     *
44000     * Copyright (c) Brightcove
44001     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44002     */
44003    // composed of the nal units that make up that frame
44004    // Also keep track of cummulative data about the frame from the nal units such
44005    // as the frame duration, starting pts, etc.
44006
44007    var groupNalsIntoFrames = function (nalUnits) {
44008      var i,
44009        currentNal,
44010        currentFrame = [],
44011        frames = []; // TODO added for LHLS, make sure this is OK
44012
44013      frames.byteLength = 0;
44014      frames.nalCount = 0;
44015      frames.duration = 0;
44016      currentFrame.byteLength = 0;
44017      for (i = 0; i < nalUnits.length; i++) {
44018        currentNal = nalUnits[i]; // Split on 'aud'-type nal units
44019
44020        if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
44021          // Since the very first nal unit is expected to be an AUD
44022          // only push to the frames array when currentFrame is not empty
44023          if (currentFrame.length) {
44024            currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
44025
44026            frames.byteLength += currentFrame.byteLength;
44027            frames.nalCount += currentFrame.length;
44028            frames.duration += currentFrame.duration;
44029            frames.push(currentFrame);
44030          }
44031          currentFrame = [currentNal];
44032          currentFrame.byteLength = currentNal.data.byteLength;
44033          currentFrame.pts = currentNal.pts;
44034          currentFrame.dts = currentNal.dts;
44035        } else {
44036          // Specifically flag key frames for ease of use later
44037          if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
44038            currentFrame.keyFrame = true;
44039          }
44040          currentFrame.duration = currentNal.dts - currentFrame.dts;
44041          currentFrame.byteLength += currentNal.data.byteLength;
44042          currentFrame.push(currentNal);
44043        }
44044      } // For the last frame, use the duration of the previous frame if we
44045      // have nothing better to go on
44046
44047      if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
44048        currentFrame.duration = frames[frames.length - 1].duration;
44049      } // Push the final frame
44050      // TODO added for LHLS, make sure this is OK
44051
44052      frames.byteLength += currentFrame.byteLength;
44053      frames.nalCount += currentFrame.length;
44054      frames.duration += currentFrame.duration;
44055      frames.push(currentFrame);
44056      return frames;
44057    }; // Convert an array of frames into an array of Gop with each Gop being composed
44058    // of the frames that make up that Gop
44059    // Also keep track of cummulative data about the Gop from the frames such as the
44060    // Gop duration, starting pts, etc.
44061
44062    var groupFramesIntoGops = function (frames) {
44063      var i,
44064        currentFrame,
44065        currentGop = [],
44066        gops = []; // We must pre-set some of the values on the Gop since we
44067      // keep running totals of these values
44068
44069      currentGop.byteLength = 0;
44070      currentGop.nalCount = 0;
44071      currentGop.duration = 0;
44072      currentGop.pts = frames[0].pts;
44073      currentGop.dts = frames[0].dts; // store some metadata about all the Gops
44074
44075      gops.byteLength = 0;
44076      gops.nalCount = 0;
44077      gops.duration = 0;
44078      gops.pts = frames[0].pts;
44079      gops.dts = frames[0].dts;
44080      for (i = 0; i < frames.length; i++) {
44081        currentFrame = frames[i];
44082        if (currentFrame.keyFrame) {
44083          // Since the very first frame is expected to be an keyframe
44084          // only push to the gops array when currentGop is not empty
44085          if (currentGop.length) {
44086            gops.push(currentGop);
44087            gops.byteLength += currentGop.byteLength;
44088            gops.nalCount += currentGop.nalCount;
44089            gops.duration += currentGop.duration;
44090          }
44091          currentGop = [currentFrame];
44092          currentGop.nalCount = currentFrame.length;
44093          currentGop.byteLength = currentFrame.byteLength;
44094          currentGop.pts = currentFrame.pts;
44095          currentGop.dts = currentFrame.dts;
44096          currentGop.duration = currentFrame.duration;
44097        } else {
44098          currentGop.duration += currentFrame.duration;
44099          currentGop.nalCount += currentFrame.length;
44100          currentGop.byteLength += currentFrame.byteLength;
44101          currentGop.push(currentFrame);
44102        }
44103      }
44104      if (gops.length && currentGop.duration <= 0) {
44105        currentGop.duration = gops[gops.length - 1].duration;
44106      }
44107      gops.byteLength += currentGop.byteLength;
44108      gops.nalCount += currentGop.nalCount;
44109      gops.duration += currentGop.duration; // push the final Gop
44110
44111      gops.push(currentGop);
44112      return gops;
44113    };
44114    /*
44115     * Search for the first keyframe in the GOPs and throw away all frames
44116     * until that keyframe. Then extend the duration of the pulled keyframe
44117     * and pull the PTS and DTS of the keyframe so that it covers the time
44118     * range of the frames that were disposed.
44119     *
44120     * @param {Array} gops video GOPs
44121     * @returns {Array} modified video GOPs
44122     */
44123
44124    var extendFirstKeyFrame = function (gops) {
44125      var currentGop;
44126      if (!gops[0][0].keyFrame && gops.length > 1) {
44127        // Remove the first GOP
44128        currentGop = gops.shift();
44129        gops.byteLength -= currentGop.byteLength;
44130        gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
44131        // first gop to cover the time period of the
44132        // frames we just removed
44133
44134        gops[0][0].dts = currentGop.dts;
44135        gops[0][0].pts = currentGop.pts;
44136        gops[0][0].duration += currentGop.duration;
44137      }
44138      return gops;
44139    };
44140    /**
44141     * Default sample object
44142     * see ISO/IEC 14496-12:2012, section 8.6.4.3
44143     */
44144
44145    var createDefaultSample = function () {
44146      return {
44147        size: 0,
44148        flags: {
44149          isLeading: 0,
44150          dependsOn: 1,
44151          isDependedOn: 0,
44152          hasRedundancy: 0,
44153          degradationPriority: 0,
44154          isNonSyncSample: 1
44155        }
44156      };
44157    };
44158    /*
44159     * Collates information from a video frame into an object for eventual
44160     * entry into an MP4 sample table.
44161     *
44162     * @param {Object} frame the video frame
44163     * @param {Number} dataOffset the byte offset to position the sample
44164     * @return {Object} object containing sample table info for a frame
44165     */
44166
44167    var sampleForFrame = function (frame, dataOffset) {
44168      var sample = createDefaultSample();
44169      sample.dataOffset = dataOffset;
44170      sample.compositionTimeOffset = frame.pts - frame.dts;
44171      sample.duration = frame.duration;
44172      sample.size = 4 * frame.length; // Space for nal unit size
44173
44174      sample.size += frame.byteLength;
44175      if (frame.keyFrame) {
44176        sample.flags.dependsOn = 2;
44177        sample.flags.isNonSyncSample = 0;
44178      }
44179      return sample;
44180    }; // generate the track's sample table from an array of gops
44181
44182    var generateSampleTable$1 = function (gops, baseDataOffset) {
44183      var h,
44184        i,
44185        sample,
44186        currentGop,
44187        currentFrame,
44188        dataOffset = baseDataOffset || 0,
44189        samples = [];
44190      for (h = 0; h < gops.length; h++) {
44191        currentGop = gops[h];
44192        for (i = 0; i < currentGop.length; i++) {
44193          currentFrame = currentGop[i];
44194          sample = sampleForFrame(currentFrame, dataOffset);
44195          dataOffset += sample.size;
44196          samples.push(sample);
44197        }
44198      }
44199      return samples;
44200    }; // generate the track's raw mdat data from an array of gops
44201
44202    var concatenateNalData = function (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      return data;
44232    }; // generate the track's sample table from a frame
44233
44234    var generateSampleTableForFrame = function (frame, baseDataOffset) {
44235      var sample,
44236        dataOffset = baseDataOffset || 0,
44237        samples = [];
44238      sample = sampleForFrame(frame, dataOffset);
44239      samples.push(sample);
44240      return samples;
44241    }; // generate the track's raw mdat data from a frame
44242
44243    var concatenateNalDataForFrame = function (frame) {
44244      var i,
44245        currentNal,
44246        dataOffset = 0,
44247        nalsByteLength = frame.byteLength,
44248        numberOfNals = frame.length,
44249        totalByteLength = nalsByteLength + 4 * numberOfNals,
44250        data = new Uint8Array(totalByteLength),
44251        view = new DataView(data.buffer); // For each NAL..
44252
44253      for (i = 0; i < frame.length; i++) {
44254        currentNal = frame[i];
44255        view.setUint32(dataOffset, currentNal.data.byteLength);
44256        dataOffset += 4;
44257        data.set(currentNal.data, dataOffset);
44258        dataOffset += currentNal.data.byteLength;
44259      }
44260      return data;
44261    };
44262    var frameUtils$1 = {
44263      groupNalsIntoFrames: groupNalsIntoFrames,
44264      groupFramesIntoGops: groupFramesIntoGops,
44265      extendFirstKeyFrame: extendFirstKeyFrame,
44266      generateSampleTable: generateSampleTable$1,
44267      concatenateNalData: concatenateNalData,
44268      generateSampleTableForFrame: generateSampleTableForFrame,
44269      concatenateNalDataForFrame: concatenateNalDataForFrame
44270    };
44271    /**
44272     * mux.js
44273     *
44274     * Copyright (c) Brightcove
44275     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44276     */
44277
44278    var highPrefix = [33, 16, 5, 32, 164, 27];
44279    var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
44280    var zeroFill = function (count) {
44281      var a = [];
44282      while (count--) {
44283        a.push(0);
44284      }
44285      return a;
44286    };
44287    var makeTable = function (metaTable) {
44288      return Object.keys(metaTable).reduce(function (obj, key) {
44289        obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
44290          return arr.concat(part);
44291        }, []));
44292        return obj;
44293      }, {});
44294    };
44295    var silence;
44296    var silence_1 = function () {
44297      if (!silence) {
44298        // Frames-of-silence to use for filling in missing AAC frames
44299        var coneOfSilence = {
44300          96000: [highPrefix, [227, 64], zeroFill(154), [56]],
44301          88200: [highPrefix, [231], zeroFill(170), [56]],
44302          64000: [highPrefix, [248, 192], zeroFill(240), [56]],
44303          48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
44304          44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
44305          32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
44306          24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
44307          16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
44308          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]],
44309          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]],
44310          8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
44311        };
44312        silence = makeTable(coneOfSilence);
44313      }
44314      return silence;
44315    };
44316    /**
44317     * mux.js
44318     *
44319     * Copyright (c) Brightcove
44320     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44321     */
44322
44323    var ONE_SECOND_IN_TS$4 = 90000,
44324      // 90kHz clock
44325      secondsToVideoTs,
44326      secondsToAudioTs,
44327      videoTsToSeconds,
44328      audioTsToSeconds,
44329      audioTsToVideoTs,
44330      videoTsToAudioTs,
44331      metadataTsToSeconds;
44332    secondsToVideoTs = function (seconds) {
44333      return seconds * ONE_SECOND_IN_TS$4;
44334    };
44335    secondsToAudioTs = function (seconds, sampleRate) {
44336      return seconds * sampleRate;
44337    };
44338    videoTsToSeconds = function (timestamp) {
44339      return timestamp / ONE_SECOND_IN_TS$4;
44340    };
44341    audioTsToSeconds = function (timestamp, sampleRate) {
44342      return timestamp / sampleRate;
44343    };
44344    audioTsToVideoTs = function (timestamp, sampleRate) {
44345      return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
44346    };
44347    videoTsToAudioTs = function (timestamp, sampleRate) {
44348      return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
44349    };
44350    /**
44351     * Adjust ID3 tag or caption timing information by the timeline pts values
44352     * (if keepOriginalTimestamps is false) and convert to seconds
44353     */
44354
44355    metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
44356      return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
44357    };
44358    var clock$2 = {
44359      ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
44360      secondsToVideoTs: secondsToVideoTs,
44361      secondsToAudioTs: secondsToAudioTs,
44362      videoTsToSeconds: videoTsToSeconds,
44363      audioTsToSeconds: audioTsToSeconds,
44364      audioTsToVideoTs: audioTsToVideoTs,
44365      videoTsToAudioTs: videoTsToAudioTs,
44366      metadataTsToSeconds: metadataTsToSeconds
44367    };
44368    /**
44369     * mux.js
44370     *
44371     * Copyright (c) Brightcove
44372     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44373     */
44374
44375    var coneOfSilence = silence_1;
44376    var clock$1 = clock$2;
44377    /**
44378     * Sum the `byteLength` properties of the data in each AAC frame
44379     */
44380
44381    var sumFrameByteLengths = function (array) {
44382      var i,
44383        currentObj,
44384        sum = 0; // sum the byteLength's all each nal unit in the frame
44385
44386      for (i = 0; i < array.length; i++) {
44387        currentObj = array[i];
44388        sum += currentObj.data.byteLength;
44389      }
44390      return sum;
44391    }; // Possibly pad (prefix) the audio track with silence if appending this track
44392    // would lead to the introduction of a gap in the audio buffer
44393
44394    var prefixWithSilence = function (track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
44395      var baseMediaDecodeTimeTs,
44396        frameDuration = 0,
44397        audioGapDuration = 0,
44398        audioFillFrameCount = 0,
44399        audioFillDuration = 0,
44400        silentFrame,
44401        i,
44402        firstFrame;
44403      if (!frames.length) {
44404        return;
44405      }
44406      baseMediaDecodeTimeTs = clock$1.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
44407
44408      frameDuration = Math.ceil(clock$1.ONE_SECOND_IN_TS / (track.samplerate / 1024));
44409      if (audioAppendStartTs && videoBaseMediaDecodeTime) {
44410        // insert the shortest possible amount (audio gap or audio to video gap)
44411        audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
44412
44413        audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
44414        audioFillDuration = audioFillFrameCount * frameDuration;
44415      } // don't attempt to fill gaps smaller than a single frame or larger
44416      // than a half second
44417
44418      if (audioFillFrameCount < 1 || audioFillDuration > clock$1.ONE_SECOND_IN_TS / 2) {
44419        return;
44420      }
44421      silentFrame = coneOfSilence()[track.samplerate];
44422      if (!silentFrame) {
44423        // we don't have a silent frame pregenerated for the sample rate, so use a frame
44424        // from the content instead
44425        silentFrame = frames[0].data;
44426      }
44427      for (i = 0; i < audioFillFrameCount; i++) {
44428        firstFrame = frames[0];
44429        frames.splice(0, 0, {
44430          data: silentFrame,
44431          dts: firstFrame.dts - frameDuration,
44432          pts: firstFrame.pts - frameDuration
44433        });
44434      }
44435      track.baseMediaDecodeTime -= Math.floor(clock$1.videoTsToAudioTs(audioFillDuration, track.samplerate));
44436      return audioFillDuration;
44437    }; // If the audio segment extends before the earliest allowed dts
44438    // value, remove AAC frames until starts at or after the earliest
44439    // allowed DTS so that we don't end up with a negative baseMedia-
44440    // DecodeTime for the audio track
44441
44442    var trimAdtsFramesByEarliestDts = function (adtsFrames, track, earliestAllowedDts) {
44443      if (track.minSegmentDts >= earliestAllowedDts) {
44444        return adtsFrames;
44445      } // We will need to recalculate the earliest segment Dts
44446
44447      track.minSegmentDts = Infinity;
44448      return adtsFrames.filter(function (currentFrame) {
44449        // If this is an allowed frame, keep it and record it's Dts
44450        if (currentFrame.dts >= earliestAllowedDts) {
44451          track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
44452          track.minSegmentPts = track.minSegmentDts;
44453          return true;
44454        } // Otherwise, discard it
44455
44456        return false;
44457      });
44458    }; // generate the track's raw mdat data from an array of frames
44459
44460    var generateSampleTable = function (frames) {
44461      var i,
44462        currentFrame,
44463        samples = [];
44464      for (i = 0; i < frames.length; i++) {
44465        currentFrame = frames[i];
44466        samples.push({
44467          size: currentFrame.data.byteLength,
44468          duration: 1024 // For AAC audio, all samples contain 1024 samples
44469        });
44470      }
vendor: 11,395 bytes, lines 44471-44790
44471
44472      return samples;
44473    }; // generate the track's sample table from an array of frames
44474
44475    var concatenateFrameData = function (frames) {
44476      var i,
44477        currentFrame,
44478        dataOffset = 0,
44479        data = new Uint8Array(sumFrameByteLengths(frames));
44480      for (i = 0; i < frames.length; i++) {
44481        currentFrame = frames[i];
44482        data.set(currentFrame.data, dataOffset);
44483        dataOffset += currentFrame.data.byteLength;
44484      }
44485      return data;
44486    };
44487    var audioFrameUtils$1 = {
44488      prefixWithSilence: prefixWithSilence,
44489      trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
44490      generateSampleTable: generateSampleTable,
44491      concatenateFrameData: concatenateFrameData
44492    };
44493    /**
44494     * mux.js
44495     *
44496     * Copyright (c) Brightcove
44497     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44498     */
44499
44500    var ONE_SECOND_IN_TS$3 = clock$2.ONE_SECOND_IN_TS;
44501    /**
44502     * Store information about the start and end of the track and the
44503     * duration for each frame/sample we process in order to calculate
44504     * the baseMediaDecodeTime
44505     */
44506
44507    var collectDtsInfo = function (track, data) {
44508      if (typeof data.pts === 'number') {
44509        if (track.timelineStartInfo.pts === undefined) {
44510          track.timelineStartInfo.pts = data.pts;
44511        }
44512        if (track.minSegmentPts === undefined) {
44513          track.minSegmentPts = data.pts;
44514        } else {
44515          track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
44516        }
44517        if (track.maxSegmentPts === undefined) {
44518          track.maxSegmentPts = data.pts;
44519        } else {
44520          track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
44521        }
44522      }
44523      if (typeof data.dts === 'number') {
44524        if (track.timelineStartInfo.dts === undefined) {
44525          track.timelineStartInfo.dts = data.dts;
44526        }
44527        if (track.minSegmentDts === undefined) {
44528          track.minSegmentDts = data.dts;
44529        } else {
44530          track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
44531        }
44532        if (track.maxSegmentDts === undefined) {
44533          track.maxSegmentDts = data.dts;
44534        } else {
44535          track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
44536        }
44537      }
44538    };
44539    /**
44540     * Clear values used to calculate the baseMediaDecodeTime between
44541     * tracks
44542     */
44543
44544    var clearDtsInfo = function (track) {
44545      delete track.minSegmentDts;
44546      delete track.maxSegmentDts;
44547      delete track.minSegmentPts;
44548      delete track.maxSegmentPts;
44549    };
44550    /**
44551     * Calculate the track's baseMediaDecodeTime based on the earliest
44552     * DTS the transmuxer has ever seen and the minimum DTS for the
44553     * current track
44554     * @param track {object} track metadata configuration
44555     * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
44556     *        in the source; false to adjust the first segment to start at 0.
44557     */
44558
44559    var calculateTrackBaseMediaDecodeTime = function (track, keepOriginalTimestamps) {
44560      var baseMediaDecodeTime,
44561        scale,
44562        minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
44563
44564      if (!keepOriginalTimestamps) {
44565        minSegmentDts -= track.timelineStartInfo.dts;
44566      } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
44567      // we want the start of the first segment to be placed
44568
44569      baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
44570
44571      baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
44572
44573      baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
44574      if (track.type === 'audio') {
44575        // Audio has a different clock equal to the sampling_rate so we need to
44576        // scale the PTS values into the clock rate of the track
44577        scale = track.samplerate / ONE_SECOND_IN_TS$3;
44578        baseMediaDecodeTime *= scale;
44579        baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
44580      }
44581      return baseMediaDecodeTime;
44582    };
44583    var trackDecodeInfo$1 = {
44584      clearDtsInfo: clearDtsInfo,
44585      calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
44586      collectDtsInfo: collectDtsInfo
44587    };
44588    /**
44589     * mux.js
44590     *
44591     * Copyright (c) Brightcove
44592     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44593     *
44594     * Reads in-band caption information from a video elementary
44595     * stream. Captions must follow the CEA-708 standard for injection
44596     * into an MPEG-2 transport streams.
44597     * @see https://en.wikipedia.org/wiki/CEA-708
44598     * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
44599     */
44600    // payload type field to indicate how they are to be
44601    // interpreted. CEAS-708 caption content is always transmitted with
44602    // payload type 0x04.
44603
44604    var USER_DATA_REGISTERED_ITU_T_T35 = 4,
44605      RBSP_TRAILING_BITS = 128;
44606    /**
44607      * Parse a supplemental enhancement information (SEI) NAL unit.
44608      * Stops parsing once a message of type ITU T T35 has been found.
44609      *
44610      * @param bytes {Uint8Array} the bytes of a SEI NAL unit
44611      * @return {object} the parsed SEI payload
44612      * @see Rec. ITU-T H.264, 7.3.2.3.1
44613      */
44614
44615    var parseSei = function (bytes) {
44616      var i = 0,
44617        result = {
44618          payloadType: -1,
44619          payloadSize: 0
44620        },
44621        payloadType = 0,
44622        payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
44623
44624      while (i < bytes.byteLength) {
44625        // stop once we have hit the end of the sei_rbsp
44626        if (bytes[i] === RBSP_TRAILING_BITS) {
44627          break;
44628        } // Parse payload type
44629
44630        while (bytes[i] === 0xFF) {
44631          payloadType += 255;
44632          i++;
44633        }
44634        payloadType += bytes[i++]; // Parse payload size
44635
44636        while (bytes[i] === 0xFF) {
44637          payloadSize += 255;
44638          i++;
44639        }
44640        payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
44641        // there can only ever be one caption message in a frame's sei
44642
44643        if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
44644          var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
44645          if (userIdentifier === 'GA94') {
44646            result.payloadType = payloadType;
44647            result.payloadSize = payloadSize;
44648            result.payload = bytes.subarray(i, i + payloadSize);
44649            break;
44650          } else {
44651            result.payload = void 0;
44652          }
44653        } // skip the payload and parse the next message
44654
44655        i += payloadSize;
44656        payloadType = 0;
44657        payloadSize = 0;
44658      }
44659      return result;
44660    }; // see ANSI/SCTE 128-1 (2013), section 8.1
44661
44662    var parseUserData = function (sei) {
44663      // itu_t_t35_contry_code must be 181 (United States) for
44664      // captions
44665      if (sei.payload[0] !== 181) {
44666        return null;
44667      } // itu_t_t35_provider_code should be 49 (ATSC) for captions
44668
44669      if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
44670        return null;
44671      } // the user_identifier should be "GA94" to indicate ATSC1 data
44672
44673      if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
44674        return null;
44675      } // finally, user_data_type_code should be 0x03 for caption data
44676
44677      if (sei.payload[7] !== 0x03) {
44678        return null;
44679      } // return the user_data_type_structure and strip the trailing
44680      // marker bits
44681
44682      return sei.payload.subarray(8, sei.payload.length - 1);
44683    }; // see CEA-708-D, section 4.4
44684
44685    var parseCaptionPackets = function (pts, userData) {
44686      var results = [],
44687        i,
44688        count,
44689        offset,
44690        data; // if this is just filler, return immediately
44691
44692      if (!(userData[0] & 0x40)) {
44693        return results;
44694      } // parse out the cc_data_1 and cc_data_2 fields
44695
44696      count = userData[0] & 0x1f;
44697      for (i = 0; i < count; i++) {
44698        offset = i * 3;
44699        data = {
44700          type: userData[offset + 2] & 0x03,
44701          pts: pts
44702        }; // capture cc data when cc_valid is 1
44703
44704        if (userData[offset + 2] & 0x04) {
44705          data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
44706          results.push(data);
44707        }
44708      }
44709      return results;
44710    };
44711    var discardEmulationPreventionBytes$1 = function (data) {
44712      var length = data.byteLength,
44713        emulationPreventionBytesPositions = [],
44714        i = 1,
44715        newLength,
44716        newData; // Find all `Emulation Prevention Bytes`
44717
44718      while (i < length - 2) {
44719        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
44720          emulationPreventionBytesPositions.push(i + 2);
44721          i += 2;
44722        } else {
44723          i++;
44724        }
44725      } // If no Emulation Prevention Bytes were found just return the original
44726      // array
44727
44728      if (emulationPreventionBytesPositions.length === 0) {
44729        return data;
44730      } // Create a new array to hold the NAL unit data
44731
44732      newLength = length - emulationPreventionBytesPositions.length;
44733      newData = new Uint8Array(newLength);
44734      var sourceIndex = 0;
44735      for (i = 0; i < newLength; sourceIndex++, i++) {
44736        if (sourceIndex === emulationPreventionBytesPositions[0]) {
44737          // Skip this byte
44738          sourceIndex++; // Remove this position index
44739
44740          emulationPreventionBytesPositions.shift();
44741        }
44742        newData[i] = data[sourceIndex];
44743      }
44744      return newData;
44745    }; // exports
44746
44747    var captionPacketParser = {
44748      parseSei: parseSei,
44749      parseUserData: parseUserData,
44750      parseCaptionPackets: parseCaptionPackets,
44751      discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
44752      USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
44753    };
44754    /**
44755     * mux.js
44756     *
44757     * Copyright (c) Brightcove
44758     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44759     *
44760     * Reads in-band caption information from a video elementary
44761     * stream. Captions must follow the CEA-708 standard for injection
44762     * into an MPEG-2 transport streams.
44763     * @see https://en.wikipedia.org/wiki/CEA-708
44764     * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
44765     */
44766    // Link To Transport
44767    // -----------------
44768
44769    var Stream$7 = stream;
44770    var cea708Parser = captionPacketParser;
44771    var CaptionStream$2 = function (options) {
44772      options = options || {};
44773      CaptionStream$2.prototype.init.call(this); // parse708captions flag, default to true
44774
44775      this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
44776      this.captionPackets_ = [];
44777      this.ccStreams_ = [new Cea608Stream(0, 0),
44778      // eslint-disable-line no-use-before-define
44779      new Cea608Stream(0, 1),
44780      // eslint-disable-line no-use-before-define
44781      new Cea608Stream(1, 0),
44782      // eslint-disable-line no-use-before-define
44783      new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
44784      ];
44785
44786      if (this.parse708captions_) {
44787        this.cc708Stream_ = new Cea708Stream({
44788          captionServices: options.captionServices
44789        }); // eslint-disable-line no-use-before-define
44790      }
vendor: 45,910 bytes, lines 44791-46178
44791
44792      this.reset(); // forward data and done events from CCs to this CaptionStream
44793
44794      this.ccStreams_.forEach(function (cc) {
44795        cc.on('data', this.trigger.bind(this, 'data'));
44796        cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
44797        cc.on('done', this.trigger.bind(this, 'done'));
44798      }, this);
44799      if (this.parse708captions_) {
44800        this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
44801        this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
44802        this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
44803      }
44804    };
44805    CaptionStream$2.prototype = new Stream$7();
44806    CaptionStream$2.prototype.push = function (event) {
44807      var sei, userData, newCaptionPackets; // only examine SEI NALs
44808
44809      if (event.nalUnitType !== 'sei_rbsp') {
44810        return;
44811      } // parse the sei
44812
44813      sei = cea708Parser.parseSei(event.escapedRBSP); // no payload data, skip
44814
44815      if (!sei.payload) {
44816        return;
44817      } // ignore everything but user_data_registered_itu_t_t35
44818
44819      if (sei.payloadType !== cea708Parser.USER_DATA_REGISTERED_ITU_T_T35) {
44820        return;
44821      } // parse out the user data payload
44822
44823      userData = cea708Parser.parseUserData(sei); // ignore unrecognized userData
44824
44825      if (!userData) {
44826        return;
44827      } // Sometimes, the same segment # will be downloaded twice. To stop the
44828      // caption data from being processed twice, we track the latest dts we've
44829      // received and ignore everything with a dts before that. However, since
44830      // data for a specific dts can be split across packets on either side of
44831      // a segment boundary, we need to make sure we *don't* ignore the packets
44832      // from the *next* segment that have dts === this.latestDts_. By constantly
44833      // tracking the number of packets received with dts === this.latestDts_, we
44834      // know how many should be ignored once we start receiving duplicates.
44835
44836      if (event.dts < this.latestDts_) {
44837        // We've started getting older data, so set the flag.
44838        this.ignoreNextEqualDts_ = true;
44839        return;
44840      } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
44841        this.numSameDts_--;
44842        if (!this.numSameDts_) {
44843          // We've received the last duplicate packet, time to start processing again
44844          this.ignoreNextEqualDts_ = false;
44845        }
44846        return;
44847      } // parse out CC data packets and save them for later
44848
44849      newCaptionPackets = cea708Parser.parseCaptionPackets(event.pts, userData);
44850      this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
44851      if (this.latestDts_ !== event.dts) {
44852        this.numSameDts_ = 0;
44853      }
44854      this.numSameDts_++;
44855      this.latestDts_ = event.dts;
44856    };
44857    CaptionStream$2.prototype.flushCCStreams = function (flushType) {
44858      this.ccStreams_.forEach(function (cc) {
44859        return flushType === 'flush' ? cc.flush() : cc.partialFlush();
44860      }, this);
44861    };
44862    CaptionStream$2.prototype.flushStream = function (flushType) {
44863      // make sure we actually parsed captions before proceeding
44864      if (!this.captionPackets_.length) {
44865        this.flushCCStreams(flushType);
44866        return;
44867      } // In Chrome, the Array#sort function is not stable so add a
44868      // presortIndex that we can use to ensure we get a stable-sort
44869
44870      this.captionPackets_.forEach(function (elem, idx) {
44871        elem.presortIndex = idx;
44872      }); // sort caption byte-pairs based on their PTS values
44873
44874      this.captionPackets_.sort(function (a, b) {
44875        if (a.pts === b.pts) {
44876          return a.presortIndex - b.presortIndex;
44877        }
44878        return a.pts - b.pts;
44879      });
44880      this.captionPackets_.forEach(function (packet) {
44881        if (packet.type < 2) {
44882          // Dispatch packet to the right Cea608Stream
44883          this.dispatchCea608Packet(packet);
44884        } else {
44885          // Dispatch packet to the Cea708Stream
44886          this.dispatchCea708Packet(packet);
44887        }
44888      }, this);
44889      this.captionPackets_.length = 0;
44890      this.flushCCStreams(flushType);
44891    };
44892    CaptionStream$2.prototype.flush = function () {
44893      return this.flushStream('flush');
44894    }; // Only called if handling partial data
44895
44896    CaptionStream$2.prototype.partialFlush = function () {
44897      return this.flushStream('partialFlush');
44898    };
44899    CaptionStream$2.prototype.reset = function () {
44900      this.latestDts_ = null;
44901      this.ignoreNextEqualDts_ = false;
44902      this.numSameDts_ = 0;
44903      this.activeCea608Channel_ = [null, null];
44904      this.ccStreams_.forEach(function (ccStream) {
44905        ccStream.reset();
44906      });
44907    }; // From the CEA-608 spec:
44908
44909    /*
44910     * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
44911     * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
44912     * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
44913     * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
44914     * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
44915     * to switch to captioning or Text.
44916    */
44917    // With that in mind, we ignore any data between an XDS control code and a
44918    // subsequent closed-captioning control code.
44919
44920    CaptionStream$2.prototype.dispatchCea608Packet = function (packet) {
44921      // NOTE: packet.type is the CEA608 field
44922      if (this.setsTextOrXDSActive(packet)) {
44923        this.activeCea608Channel_[packet.type] = null;
44924      } else if (this.setsChannel1Active(packet)) {
44925        this.activeCea608Channel_[packet.type] = 0;
44926      } else if (this.setsChannel2Active(packet)) {
44927        this.activeCea608Channel_[packet.type] = 1;
44928      }
44929      if (this.activeCea608Channel_[packet.type] === null) {
44930        // If we haven't received anything to set the active channel, or the
44931        // packets are Text/XDS data, discard the data; we don't want jumbled
44932        // captions
44933        return;
44934      }
44935      this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
44936    };
44937    CaptionStream$2.prototype.setsChannel1Active = function (packet) {
44938      return (packet.ccData & 0x7800) === 0x1000;
44939    };
44940    CaptionStream$2.prototype.setsChannel2Active = function (packet) {
44941      return (packet.ccData & 0x7800) === 0x1800;
44942    };
44943    CaptionStream$2.prototype.setsTextOrXDSActive = function (packet) {
44944      return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
44945    };
44946    CaptionStream$2.prototype.dispatchCea708Packet = function (packet) {
44947      if (this.parse708captions_) {
44948        this.cc708Stream_.push(packet);
44949      }
44950    }; // ----------------------
44951    // Session to Application
44952    // ----------------------
44953    // This hash maps special and extended character codes to their
44954    // proper Unicode equivalent. The first one-byte key is just a
44955    // non-standard character code. The two-byte keys that follow are
44956    // the extended CEA708 character codes, along with the preceding
44957    // 0x10 extended character byte to distinguish these codes from
44958    // non-extended character codes. Every CEA708 character code that
44959    // is not in this object maps directly to a standard unicode
44960    // character code.
44961    // The transparent space and non-breaking transparent space are
44962    // technically not fully supported since there is no code to
44963    // make them transparent, so they have normal non-transparent
44964    // stand-ins.
44965    // The special closed caption (CC) character isn't a standard
44966    // unicode character, so a fairly similar unicode character was
44967    // chosen in it's place.
44968
44969    var CHARACTER_TRANSLATION_708 = {
44970      0x7f: 0x266a,
44971      // ♪
44972      0x1020: 0x20,
44973      // Transparent Space
44974      0x1021: 0xa0,
44975      // Nob-breaking Transparent Space
44976      0x1025: 0x2026,
44977      // …
44978      0x102a: 0x0160,
44979      // Å 
44980      0x102c: 0x0152,
44981      // Œ
44982      0x1030: 0x2588,
44983      // █
44984      0x1031: 0x2018,
44985      // ‘
44986      0x1032: 0x2019,
44987      // ’
44988      0x1033: 0x201c,
44989      // “
44990      0x1034: 0x201d,
44991      // ”
44992      0x1035: 0x2022,
44993      // •
44994      0x1039: 0x2122,
44995      // ™
44996      0x103a: 0x0161,
44997      // Å¡
44998      0x103c: 0x0153,
44999      // œ
45000      0x103d: 0x2120,
45001      // ℠
45002      0x103f: 0x0178,
45003      // Ÿ
45004      0x1076: 0x215b,
45005      // ⅛
45006      0x1077: 0x215c,
45007      // ⅜
45008      0x1078: 0x215d,
45009      // ⅝
45010      0x1079: 0x215e,
45011      // ⅞
45012      0x107a: 0x23d0,
45013      // ⏐
45014      0x107b: 0x23a4,
45015      // ⎤
45016      0x107c: 0x23a3,
45017      // ⎣
45018      0x107d: 0x23af,
45019      // ⎯
45020      0x107e: 0x23a6,
45021      // ⎦
45022      0x107f: 0x23a1,
45023      // ⎡
45024      0x10a0: 0x3138 // ㄸ (CC char)
45025    };
45026
45027    var get708CharFromCode = function (code) {
45028      var newCode = CHARACTER_TRANSLATION_708[code] || code;
45029      if (code & 0x1000 && code === newCode) {
45030        // Invalid extended code
45031        return '';
45032      }
45033      return String.fromCharCode(newCode);
45034    };
45035    var within708TextBlock = function (b) {
45036      return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
45037    };
45038    var Cea708Window = function (windowNum) {
45039      this.windowNum = windowNum;
45040      this.reset();
45041    };
45042    Cea708Window.prototype.reset = function () {
45043      this.clearText();
45044      this.pendingNewLine = false;
45045      this.winAttr = {};
45046      this.penAttr = {};
45047      this.penLoc = {};
45048      this.penColor = {}; // These default values are arbitrary,
45049      // defineWindow will usually override them
45050
45051      this.visible = 0;
45052      this.rowLock = 0;
45053      this.columnLock = 0;
45054      this.priority = 0;
45055      this.relativePositioning = 0;
45056      this.anchorVertical = 0;
45057      this.anchorHorizontal = 0;
45058      this.anchorPoint = 0;
45059      this.rowCount = 1;
45060      this.virtualRowCount = this.rowCount + 1;
45061      this.columnCount = 41;
45062      this.windowStyle = 0;
45063      this.penStyle = 0;
45064    };
45065    Cea708Window.prototype.getText = function () {
45066      return this.rows.join('\n');
45067    };
45068    Cea708Window.prototype.clearText = function () {
45069      this.rows = [''];
45070      this.rowIdx = 0;
45071    };
45072    Cea708Window.prototype.newLine = function (pts) {
45073      if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
45074        this.beforeRowOverflow(pts);
45075      }
45076      if (this.rows.length > 0) {
45077        this.rows.push('');
45078        this.rowIdx++;
45079      } // Show all virtual rows since there's no visible scrolling
45080
45081      while (this.rows.length > this.virtualRowCount) {
45082        this.rows.shift();
45083        this.rowIdx--;
45084      }
45085    };
45086    Cea708Window.prototype.isEmpty = function () {
45087      if (this.rows.length === 0) {
45088        return true;
45089      } else if (this.rows.length === 1) {
45090        return this.rows[0] === '';
45091      }
45092      return false;
45093    };
45094    Cea708Window.prototype.addText = function (text) {
45095      this.rows[this.rowIdx] += text;
45096    };
45097    Cea708Window.prototype.backspace = function () {
45098      if (!this.isEmpty()) {
45099        var row = this.rows[this.rowIdx];
45100        this.rows[this.rowIdx] = row.substr(0, row.length - 1);
45101      }
45102    };
45103    var Cea708Service = function (serviceNum, encoding, stream) {
45104      this.serviceNum = serviceNum;
45105      this.text = '';
45106      this.currentWindow = new Cea708Window(-1);
45107      this.windows = [];
45108      this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
45109
45110      if (typeof encoding === 'string') {
45111        this.createTextDecoder(encoding);
45112      }
45113    };
45114    /**
45115     * Initialize service windows
45116     * Must be run before service use
45117     *
45118     * @param  {Integer}  pts               PTS value
45119     * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
45120     */
45121
45122    Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
45123      this.startPts = pts;
45124      for (var win = 0; win < 8; win++) {
45125        this.windows[win] = new Cea708Window(win);
45126        if (typeof beforeRowOverflow === 'function') {
45127          this.windows[win].beforeRowOverflow = beforeRowOverflow;
45128        }
45129      }
45130    };
45131    /**
45132     * Set current window of service to be affected by commands
45133     *
45134     * @param  {Integer} windowNum Window number
45135     */
45136
45137    Cea708Service.prototype.setCurrentWindow = function (windowNum) {
45138      this.currentWindow = this.windows[windowNum];
45139    };
45140    /**
45141     * Try to create a TextDecoder if it is natively supported
45142     */
45143
45144    Cea708Service.prototype.createTextDecoder = function (encoding) {
45145      if (typeof TextDecoder === 'undefined') {
45146        this.stream.trigger('log', {
45147          level: 'warn',
45148          message: 'The `encoding` option is unsupported without TextDecoder support'
45149        });
45150      } else {
45151        try {
45152          this.textDecoder_ = new TextDecoder(encoding);
45153        } catch (error) {
45154          this.stream.trigger('log', {
45155            level: 'warn',
45156            message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
45157          });
45158        }
45159      }
45160    };
45161    var Cea708Stream = function (options) {
45162      options = options || {};
45163      Cea708Stream.prototype.init.call(this);
45164      var self = this;
45165      var captionServices = options.captionServices || {};
45166      var captionServiceEncodings = {};
45167      var serviceProps; // Get service encodings from captionServices option block
45168
45169      Object.keys(captionServices).forEach(serviceName => {
45170        serviceProps = captionServices[serviceName];
45171        if (/^SERVICE/.test(serviceName)) {
45172          captionServiceEncodings[serviceName] = serviceProps.encoding;
45173        }
45174      });
45175      this.serviceEncodings = captionServiceEncodings;
45176      this.current708Packet = null;
45177      this.services = {};
45178      this.push = function (packet) {
45179        if (packet.type === 3) {
45180          // 708 packet start
45181          self.new708Packet();
45182          self.add708Bytes(packet);
45183        } else {
45184          if (self.current708Packet === null) {
45185            // This should only happen at the start of a file if there's no packet start.
45186            self.new708Packet();
45187          }
45188          self.add708Bytes(packet);
45189        }
45190      };
45191    };
45192    Cea708Stream.prototype = new Stream$7();
45193    /**
45194     * Push current 708 packet, create new 708 packet.
45195     */
45196
45197    Cea708Stream.prototype.new708Packet = function () {
45198      if (this.current708Packet !== null) {
45199        this.push708Packet();
45200      }
45201      this.current708Packet = {
45202        data: [],
45203        ptsVals: []
45204      };
45205    };
45206    /**
45207     * Add pts and both bytes from packet into current 708 packet.
45208     */
45209
45210    Cea708Stream.prototype.add708Bytes = function (packet) {
45211      var data = packet.ccData;
45212      var byte0 = data >>> 8;
45213      var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
45214      // that service blocks will always line up with byte pairs.
45215
45216      this.current708Packet.ptsVals.push(packet.pts);
45217      this.current708Packet.data.push(byte0);
45218      this.current708Packet.data.push(byte1);
45219    };
45220    /**
45221     * Parse completed 708 packet into service blocks and push each service block.
45222     */
45223
45224    Cea708Stream.prototype.push708Packet = function () {
45225      var packet708 = this.current708Packet;
45226      var packetData = packet708.data;
45227      var serviceNum = null;
45228      var blockSize = null;
45229      var i = 0;
45230      var b = packetData[i++];
45231      packet708.seq = b >> 6;
45232      packet708.sizeCode = b & 0x3f; // 0b00111111;
45233
45234      for (; i < packetData.length; i++) {
45235        b = packetData[i++];
45236        serviceNum = b >> 5;
45237        blockSize = b & 0x1f; // 0b00011111
45238
45239        if (serviceNum === 7 && blockSize > 0) {
45240          // Extended service num
45241          b = packetData[i++];
45242          serviceNum = b;
45243        }
45244        this.pushServiceBlock(serviceNum, i, blockSize);
45245        if (blockSize > 0) {
45246          i += blockSize - 1;
45247        }
45248      }
45249    };
45250    /**
45251     * Parse service block, execute commands, read text.
45252     *
45253     * Note: While many of these commands serve important purposes,
45254     * many others just parse out the parameters or attributes, but
45255     * nothing is done with them because this is not a full and complete
45256     * implementation of the entire 708 spec.
45257     *
45258     * @param  {Integer} serviceNum Service number
45259     * @param  {Integer} start      Start index of the 708 packet data
45260     * @param  {Integer} size       Block size
45261     */
45262
45263    Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
45264      var b;
45265      var i = start;
45266      var packetData = this.current708Packet.data;
45267      var service = this.services[serviceNum];
45268      if (!service) {
45269        service = this.initService(serviceNum, i);
45270      }
45271      for (; i < start + size && i < packetData.length; i++) {
45272        b = packetData[i];
45273        if (within708TextBlock(b)) {
45274          i = this.handleText(i, service);
45275        } else if (b === 0x18) {
45276          i = this.multiByteCharacter(i, service);
45277        } else if (b === 0x10) {
45278          i = this.extendedCommands(i, service);
45279        } else if (0x80 <= b && b <= 0x87) {
45280          i = this.setCurrentWindow(i, service);
45281        } else if (0x98 <= b && b <= 0x9f) {
45282          i = this.defineWindow(i, service);
45283        } else if (b === 0x88) {
45284          i = this.clearWindows(i, service);
45285        } else if (b === 0x8c) {
45286          i = this.deleteWindows(i, service);
45287        } else if (b === 0x89) {
45288          i = this.displayWindows(i, service);
45289        } else if (b === 0x8a) {
45290          i = this.hideWindows(i, service);
45291        } else if (b === 0x8b) {
45292          i = this.toggleWindows(i, service);
45293        } else if (b === 0x97) {
45294          i = this.setWindowAttributes(i, service);
45295        } else if (b === 0x90) {
45296          i = this.setPenAttributes(i, service);
45297        } else if (b === 0x91) {
45298          i = this.setPenColor(i, service);
45299        } else if (b === 0x92) {
45300          i = this.setPenLocation(i, service);
45301        } else if (b === 0x8f) {
45302          service = this.reset(i, service);
45303        } else if (b === 0x08) {
45304          // BS: Backspace
45305          service.currentWindow.backspace();
45306        } else if (b === 0x0c) {
45307          // FF: Form feed
45308          service.currentWindow.clearText();
45309        } else if (b === 0x0d) {
45310          // CR: Carriage return
45311          service.currentWindow.pendingNewLine = true;
45312        } else if (b === 0x0e) {
45313          // HCR: Horizontal carriage return
45314          service.currentWindow.clearText();
45315        } else if (b === 0x8d) {
45316          // DLY: Delay, nothing to do
45317          i++;
45318        } else ;
45319      }
45320    };
45321    /**
45322     * Execute an extended command
45323     *
45324     * @param  {Integer} i        Current index in the 708 packet
45325     * @param  {Service} service  The service object to be affected
45326     * @return {Integer}          New index after parsing
45327     */
45328
45329    Cea708Stream.prototype.extendedCommands = function (i, service) {
45330      var packetData = this.current708Packet.data;
45331      var b = packetData[++i];
45332      if (within708TextBlock(b)) {
45333        i = this.handleText(i, service, {
45334          isExtended: true
45335        });
45336      }
45337      return i;
45338    };
45339    /**
45340     * Get PTS value of a given byte index
45341     *
45342     * @param  {Integer} byteIndex  Index of the byte
45343     * @return {Integer}            PTS
45344     */
45345
45346    Cea708Stream.prototype.getPts = function (byteIndex) {
45347      // There's 1 pts value per 2 bytes
45348      return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
45349    };
45350    /**
45351     * Initializes a service
45352     *
45353     * @param  {Integer} serviceNum Service number
45354     * @return {Service}            Initialized service object
45355     */
45356
45357    Cea708Stream.prototype.initService = function (serviceNum, i) {
45358      var serviceName = 'SERVICE' + serviceNum;
45359      var self = this;
45360      var serviceName;
45361      var encoding;
45362      if (serviceName in this.serviceEncodings) {
45363        encoding = this.serviceEncodings[serviceName];
45364      }
45365      this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
45366      this.services[serviceNum].init(this.getPts(i), function (pts) {
45367        self.flushDisplayed(pts, self.services[serviceNum]);
45368      });
45369      return this.services[serviceNum];
45370    };
45371    /**
45372     * Execute text writing to current window
45373     *
45374     * @param  {Integer} i        Current index in the 708 packet
45375     * @param  {Service} service  The service object to be affected
45376     * @return {Integer}          New index after parsing
45377     */
45378
45379    Cea708Stream.prototype.handleText = function (i, service, options) {
45380      var isExtended = options && options.isExtended;
45381      var isMultiByte = options && options.isMultiByte;
45382      var packetData = this.current708Packet.data;
45383      var extended = isExtended ? 0x1000 : 0x0000;
45384      var currentByte = packetData[i];
45385      var nextByte = packetData[i + 1];
45386      var win = service.currentWindow;
45387      var char;
45388      var charCodeArray; // Converts an array of bytes to a unicode hex string.
45389
45390      function toHexString(byteArray) {
45391        return byteArray.map(byte => {
45392          return ('0' + (byte & 0xFF).toString(16)).slice(-2);
45393        }).join('');
45394      }
45395      if (isMultiByte) {
45396        charCodeArray = [currentByte, nextByte];
45397        i++;
45398      } else {
45399        charCodeArray = [currentByte];
45400      } // Use the TextDecoder if one was created for this service
45401
45402      if (service.textDecoder_ && !isExtended) {
45403        char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
45404      } else {
45405        // We assume any multi-byte char without a decoder is unicode.
45406        if (isMultiByte) {
45407          const unicode = toHexString(charCodeArray); // Takes a unicode hex string and creates a single character.
45408
45409          char = String.fromCharCode(parseInt(unicode, 16));
45410        } else {
45411          char = get708CharFromCode(extended | currentByte);
45412        }
45413      }
45414      if (win.pendingNewLine && !win.isEmpty()) {
45415        win.newLine(this.getPts(i));
45416      }
45417      win.pendingNewLine = false;
45418      win.addText(char);
45419      return i;
45420    };
45421    /**
45422     * Handle decoding of multibyte character
45423     *
45424     * @param  {Integer} i        Current index in the 708 packet
45425     * @param  {Service} service  The service object to be affected
45426     * @return {Integer}          New index after parsing
45427     */
45428
45429    Cea708Stream.prototype.multiByteCharacter = function (i, service) {
45430      var packetData = this.current708Packet.data;
45431      var firstByte = packetData[i + 1];
45432      var secondByte = packetData[i + 2];
45433      if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
45434        i = this.handleText(++i, service, {
45435          isMultiByte: true
45436        });
45437      }
45438      return i;
45439    };
45440    /**
45441     * Parse and execute the CW# command.
45442     *
45443     * Set the current window.
45444     *
45445     * @param  {Integer} i        Current index in the 708 packet
45446     * @param  {Service} service  The service object to be affected
45447     * @return {Integer}          New index after parsing
45448     */
45449
45450    Cea708Stream.prototype.setCurrentWindow = function (i, service) {
45451      var packetData = this.current708Packet.data;
45452      var b = packetData[i];
45453      var windowNum = b & 0x07;
45454      service.setCurrentWindow(windowNum);
45455      return i;
45456    };
45457    /**
45458     * Parse and execute the DF# command.
45459     *
45460     * Define a window and set it as the current window.
45461     *
45462     * @param  {Integer} i        Current index in the 708 packet
45463     * @param  {Service} service  The service object to be affected
45464     * @return {Integer}          New index after parsing
45465     */
45466
45467    Cea708Stream.prototype.defineWindow = function (i, service) {
45468      var packetData = this.current708Packet.data;
45469      var b = packetData[i];
45470      var windowNum = b & 0x07;
45471      service.setCurrentWindow(windowNum);
45472      var win = service.currentWindow;
45473      b = packetData[++i];
45474      win.visible = (b & 0x20) >> 5; // v
45475
45476      win.rowLock = (b & 0x10) >> 4; // rl
45477
45478      win.columnLock = (b & 0x08) >> 3; // cl
45479
45480      win.priority = b & 0x07; // p
45481
45482      b = packetData[++i];
45483      win.relativePositioning = (b & 0x80) >> 7; // rp
45484
45485      win.anchorVertical = b & 0x7f; // av
45486
45487      b = packetData[++i];
45488      win.anchorHorizontal = b; // ah
45489
45490      b = packetData[++i];
45491      win.anchorPoint = (b & 0xf0) >> 4; // ap
45492
45493      win.rowCount = b & 0x0f; // rc
45494
45495      b = packetData[++i];
45496      win.columnCount = b & 0x3f; // cc
45497
45498      b = packetData[++i];
45499      win.windowStyle = (b & 0x38) >> 3; // ws
45500
45501      win.penStyle = b & 0x07; // ps
45502      // The spec says there are (rowCount+1) "virtual rows"
45503
45504      win.virtualRowCount = win.rowCount + 1;
45505      return i;
45506    };
45507    /**
45508     * Parse and execute the SWA command.
45509     *
45510     * Set attributes of the current window.
45511     *
45512     * @param  {Integer} i        Current index in the 708 packet
45513     * @param  {Service} service  The service object to be affected
45514     * @return {Integer}          New index after parsing
45515     */
45516
45517    Cea708Stream.prototype.setWindowAttributes = function (i, service) {
45518      var packetData = this.current708Packet.data;
45519      var b = packetData[i];
45520      var winAttr = service.currentWindow.winAttr;
45521      b = packetData[++i];
45522      winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
45523
45524      winAttr.fillRed = (b & 0x30) >> 4; // fr
45525
45526      winAttr.fillGreen = (b & 0x0c) >> 2; // fg
45527
45528      winAttr.fillBlue = b & 0x03; // fb
45529
45530      b = packetData[++i];
45531      winAttr.borderType = (b & 0xc0) >> 6; // bt
45532
45533      winAttr.borderRed = (b & 0x30) >> 4; // br
45534
45535      winAttr.borderGreen = (b & 0x0c) >> 2; // bg
45536
45537      winAttr.borderBlue = b & 0x03; // bb
45538
45539      b = packetData[++i];
45540      winAttr.borderType += (b & 0x80) >> 5; // bt
45541
45542      winAttr.wordWrap = (b & 0x40) >> 6; // ww
45543
45544      winAttr.printDirection = (b & 0x30) >> 4; // pd
45545
45546      winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
45547
45548      winAttr.justify = b & 0x03; // j
45549
45550      b = packetData[++i];
45551      winAttr.effectSpeed = (b & 0xf0) >> 4; // es
45552
45553      winAttr.effectDirection = (b & 0x0c) >> 2; // ed
45554
45555      winAttr.displayEffect = b & 0x03; // de
45556
45557      return i;
45558    };
45559    /**
45560     * Gather text from all displayed windows and push a caption to output.
45561     *
45562     * @param  {Integer} i        Current index in the 708 packet
45563     * @param  {Service} service  The service object to be affected
45564     */
45565
45566    Cea708Stream.prototype.flushDisplayed = function (pts, service) {
45567      var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
45568      // display text in the correct order, but sample files so far have not shown any issue.
45569
45570      for (var winId = 0; winId < 8; winId++) {
45571        if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
45572          displayedText.push(service.windows[winId].getText());
45573        }
45574      }
45575      service.endPts = pts;
45576      service.text = displayedText.join('\n\n');
45577      this.pushCaption(service);
45578      service.startPts = pts;
45579    };
45580    /**
45581     * Push a caption to output if the caption contains text.
45582     *
45583     * @param  {Service} service  The service object to be affected
45584     */
45585
45586    Cea708Stream.prototype.pushCaption = function (service) {
45587      if (service.text !== '') {
45588        this.trigger('data', {
45589          startPts: service.startPts,
45590          endPts: service.endPts,
45591          text: service.text,
45592          stream: 'cc708_' + service.serviceNum
45593        });
45594        service.text = '';
45595        service.startPts = service.endPts;
45596      }
45597    };
45598    /**
45599     * Parse and execute the DSW command.
45600     *
45601     * Set visible property of windows based on the parsed bitmask.
45602     *
45603     * @param  {Integer} i        Current index in the 708 packet
45604     * @param  {Service} service  The service object to be affected
45605     * @return {Integer}          New index after parsing
45606     */
45607
45608    Cea708Stream.prototype.displayWindows = function (i, service) {
45609      var packetData = this.current708Packet.data;
45610      var b = packetData[++i];
45611      var pts = this.getPts(i);
45612      this.flushDisplayed(pts, service);
45613      for (var winId = 0; winId < 8; winId++) {
45614        if (b & 0x01 << winId) {
45615          service.windows[winId].visible = 1;
45616        }
45617      }
45618      return i;
45619    };
45620    /**
45621     * Parse and execute the HDW command.
45622     *
45623     * Set visible property of windows based on the parsed bitmask.
45624     *
45625     * @param  {Integer} i        Current index in the 708 packet
45626     * @param  {Service} service  The service object to be affected
45627     * @return {Integer}          New index after parsing
45628     */
45629
45630    Cea708Stream.prototype.hideWindows = function (i, service) {
45631      var packetData = this.current708Packet.data;
45632      var b = packetData[++i];
45633      var pts = this.getPts(i);
45634      this.flushDisplayed(pts, service);
45635      for (var winId = 0; winId < 8; winId++) {
45636        if (b & 0x01 << winId) {
45637          service.windows[winId].visible = 0;
45638        }
45639      }
45640      return i;
45641    };
45642    /**
45643     * Parse and execute the TGW command.
45644     *
45645     * Set visible property of windows based on the parsed bitmask.
45646     *
45647     * @param  {Integer} i        Current index in the 708 packet
45648     * @param  {Service} service  The service object to be affected
45649     * @return {Integer}          New index after parsing
45650     */
45651
45652    Cea708Stream.prototype.toggleWindows = function (i, service) {
45653      var packetData = this.current708Packet.data;
45654      var b = packetData[++i];
45655      var pts = this.getPts(i);
45656      this.flushDisplayed(pts, service);
45657      for (var winId = 0; winId < 8; winId++) {
45658        if (b & 0x01 << winId) {
45659          service.windows[winId].visible ^= 1;
45660        }
45661      }
45662      return i;
45663    };
45664    /**
45665     * Parse and execute the CLW command.
45666     *
45667     * Clear text of windows based on the parsed bitmask.
45668     *
45669     * @param  {Integer} i        Current index in the 708 packet
45670     * @param  {Service} service  The service object to be affected
45671     * @return {Integer}          New index after parsing
45672     */
45673
45674    Cea708Stream.prototype.clearWindows = function (i, service) {
45675      var packetData = this.current708Packet.data;
45676      var b = packetData[++i];
45677      var pts = this.getPts(i);
45678      this.flushDisplayed(pts, service);
45679      for (var winId = 0; winId < 8; winId++) {
45680        if (b & 0x01 << winId) {
45681          service.windows[winId].clearText();
45682        }
45683      }
45684      return i;
45685    };
45686    /**
45687     * Parse and execute the DLW command.
45688     *
45689     * Re-initialize windows based on the parsed bitmask.
45690     *
45691     * @param  {Integer} i        Current index in the 708 packet
45692     * @param  {Service} service  The service object to be affected
45693     * @return {Integer}          New index after parsing
45694     */
45695
45696    Cea708Stream.prototype.deleteWindows = function (i, service) {
45697      var packetData = this.current708Packet.data;
45698      var b = packetData[++i];
45699      var pts = this.getPts(i);
45700      this.flushDisplayed(pts, service);
45701      for (var winId = 0; winId < 8; winId++) {
45702        if (b & 0x01 << winId) {
45703          service.windows[winId].reset();
45704        }
45705      }
45706      return i;
45707    };
45708    /**
45709     * Parse and execute the SPA command.
45710     *
45711     * Set pen attributes of the current window.
45712     *
45713     * @param  {Integer} i        Current index in the 708 packet
45714     * @param  {Service} service  The service object to be affected
45715     * @return {Integer}          New index after parsing
45716     */
45717
45718    Cea708Stream.prototype.setPenAttributes = function (i, service) {
45719      var packetData = this.current708Packet.data;
45720      var b = packetData[i];
45721      var penAttr = service.currentWindow.penAttr;
45722      b = packetData[++i];
45723      penAttr.textTag = (b & 0xf0) >> 4; // tt
45724
45725      penAttr.offset = (b & 0x0c) >> 2; // o
45726
45727      penAttr.penSize = b & 0x03; // s
45728
45729      b = packetData[++i];
45730      penAttr.italics = (b & 0x80) >> 7; // i
45731
45732      penAttr.underline = (b & 0x40) >> 6; // u
45733
45734      penAttr.edgeType = (b & 0x38) >> 3; // et
45735
45736      penAttr.fontStyle = b & 0x07; // fs
45737
45738      return i;
45739    };
45740    /**
45741     * Parse and execute the SPC command.
45742     *
45743     * Set pen color of the current window.
45744     *
45745     * @param  {Integer} i        Current index in the 708 packet
45746     * @param  {Service} service  The service object to be affected
45747     * @return {Integer}          New index after parsing
45748     */
45749
45750    Cea708Stream.prototype.setPenColor = function (i, service) {
45751      var packetData = this.current708Packet.data;
45752      var b = packetData[i];
45753      var penColor = service.currentWindow.penColor;
45754      b = packetData[++i];
45755      penColor.fgOpacity = (b & 0xc0) >> 6; // fo
45756
45757      penColor.fgRed = (b & 0x30) >> 4; // fr
45758
45759      penColor.fgGreen = (b & 0x0c) >> 2; // fg
45760
45761      penColor.fgBlue = b & 0x03; // fb
45762
45763      b = packetData[++i];
45764      penColor.bgOpacity = (b & 0xc0) >> 6; // bo
45765
45766      penColor.bgRed = (b & 0x30) >> 4; // br
45767
45768      penColor.bgGreen = (b & 0x0c) >> 2; // bg
45769
45770      penColor.bgBlue = b & 0x03; // bb
45771
45772      b = packetData[++i];
45773      penColor.edgeRed = (b & 0x30) >> 4; // er
45774
45775      penColor.edgeGreen = (b & 0x0c) >> 2; // eg
45776
45777      penColor.edgeBlue = b & 0x03; // eb
45778
45779      return i;
45780    };
45781    /**
45782     * Parse and execute the SPL command.
45783     *
45784     * Set pen location of the current window.
45785     *
45786     * @param  {Integer} i        Current index in the 708 packet
45787     * @param  {Service} service  The service object to be affected
45788     * @return {Integer}          New index after parsing
45789     */
45790
45791    Cea708Stream.prototype.setPenLocation = function (i, service) {
45792      var packetData = this.current708Packet.data;
45793      var b = packetData[i];
45794      var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
45795
45796      service.currentWindow.pendingNewLine = true;
45797      b = packetData[++i];
45798      penLoc.row = b & 0x0f; // r
45799
45800      b = packetData[++i];
45801      penLoc.column = b & 0x3f; // c
45802
45803      return i;
45804    };
45805    /**
45806     * Execute the RST command.
45807     *
45808     * Reset service to a clean slate. Re-initialize.
45809     *
45810     * @param  {Integer} i        Current index in the 708 packet
45811     * @param  {Service} service  The service object to be affected
45812     * @return {Service}          Re-initialized service
45813     */
45814
45815    Cea708Stream.prototype.reset = function (i, service) {
45816      var pts = this.getPts(i);
45817      this.flushDisplayed(pts, service);
45818      return this.initService(service.serviceNum, i);
45819    }; // This hash maps non-ASCII, special, and extended character codes to their
45820    // proper Unicode equivalent. The first keys that are only a single byte
45821    // are the non-standard ASCII characters, which simply map the CEA608 byte
45822    // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
45823    // character codes, but have their MSB bitmasked with 0x03 so that a lookup
45824    // can be performed regardless of the field and data channel on which the
45825    // character code was received.
45826
45827    var CHARACTER_TRANSLATION = {
45828      0x2a: 0xe1,
45829      // á
45830      0x5c: 0xe9,
45831      // é
45832      0x5e: 0xed,
45833      // í
45834      0x5f: 0xf3,
45835      // ó
45836      0x60: 0xfa,
45837      // ú
45838      0x7b: 0xe7,
45839      // ç
45840      0x7c: 0xf7,
45841      // ÷
45842      0x7d: 0xd1,
45843      // Ñ
45844      0x7e: 0xf1,
45845      // ñ
45846      0x7f: 0x2588,
45847      // █
45848      0x0130: 0xae,
45849      // ®
45850      0x0131: 0xb0,
45851      // °
45852      0x0132: 0xbd,
45853      // ½
45854      0x0133: 0xbf,
45855      // ¿
45856      0x0134: 0x2122,
45857      // ™
45858      0x0135: 0xa2,
45859      // ¢
45860      0x0136: 0xa3,
45861      // £
45862      0x0137: 0x266a,
45863      // ♪
45864      0x0138: 0xe0,
45865      // à
45866      0x0139: 0xa0,
45867      //
45868      0x013a: 0xe8,
45869      // è
45870      0x013b: 0xe2,
45871      // â
45872      0x013c: 0xea,
45873      // ê
45874      0x013d: 0xee,
45875      // î
45876      0x013e: 0xf4,
45877      // ô
45878      0x013f: 0xfb,
45879      // û
45880      0x0220: 0xc1,
45881      // Á
45882      0x0221: 0xc9,
45883      // É
45884      0x0222: 0xd3,
45885      // Ó
45886      0x0223: 0xda,
45887      // Ú
45888      0x0224: 0xdc,
45889      // Ü
45890      0x0225: 0xfc,
45891      // ü
45892      0x0226: 0x2018,
45893      // ‘
45894      0x0227: 0xa1,
45895      // ¡
45896      0x0228: 0x2a,
45897      // *
45898      0x0229: 0x27,
45899      // '
45900      0x022a: 0x2014,
45901      // —
45902      0x022b: 0xa9,
45903      // ©
45904      0x022c: 0x2120,
45905      // ℠
45906      0x022d: 0x2022,
45907      // •
45908      0x022e: 0x201c,
45909      // “
45910      0x022f: 0x201d,
45911      // ”
45912      0x0230: 0xc0,
45913      // À
45914      0x0231: 0xc2,
45915      // Â
45916      0x0232: 0xc7,
45917      // Ç
45918      0x0233: 0xc8,
45919      // È
45920      0x0234: 0xca,
45921      // Ê
45922      0x0235: 0xcb,
45923      // Ë
45924      0x0236: 0xeb,
45925      // ë
45926      0x0237: 0xce,
45927      // Î
45928      0x0238: 0xcf,
45929      // Ï
45930      0x0239: 0xef,
45931      // ï
45932      0x023a: 0xd4,
45933      // Ô
45934      0x023b: 0xd9,
45935      // Ù
45936      0x023c: 0xf9,
45937      // ù
45938      0x023d: 0xdb,
45939      // Û
45940      0x023e: 0xab,
45941      // «
45942      0x023f: 0xbb,
45943      // »
45944      0x0320: 0xc3,
45945      // Ã
45946      0x0321: 0xe3,
45947      // ã
45948      0x0322: 0xcd,
45949      // Í
45950      0x0323: 0xcc,
45951      // Ì
45952      0x0324: 0xec,
45953      // ì
45954      0x0325: 0xd2,
45955      // Ò
45956      0x0326: 0xf2,
45957      // ò
45958      0x0327: 0xd5,
45959      // Õ
45960      0x0328: 0xf5,
45961      // õ
45962      0x0329: 0x7b,
45963      // {
45964      0x032a: 0x7d,
45965      // }
45966      0x032b: 0x5c,
45967      // \
45968      0x032c: 0x5e,
45969      // ^
45970      0x032d: 0x5f,
45971      // _
45972      0x032e: 0x7c,
45973      // |
45974      0x032f: 0x7e,
45975      // ~
45976      0x0330: 0xc4,
45977      // Ä
45978      0x0331: 0xe4,
45979      // ä
45980      0x0332: 0xd6,
45981      // Ö
45982      0x0333: 0xf6,
45983      // ö
45984      0x0334: 0xdf,
45985      // ß
45986      0x0335: 0xa5,
45987      // ¥
45988      0x0336: 0xa4,
45989      // ¤
45990      0x0337: 0x2502,
45991      // │
45992      0x0338: 0xc5,
45993      // Å
45994      0x0339: 0xe5,
45995      // å
45996      0x033a: 0xd8,
45997      // Ø
45998      0x033b: 0xf8,
45999      // ø
46000      0x033c: 0x250c,
46001      // ┌
46002      0x033d: 0x2510,
46003      // ┐
46004      0x033e: 0x2514,
46005      // └
46006      0x033f: 0x2518 // ┘
46007    };
46008
46009    var getCharFromCode = function (code) {
46010      if (code === null) {
46011        return '';
46012      }
46013      code = CHARACTER_TRANSLATION[code] || code;
46014      return String.fromCharCode(code);
46015    }; // the index of the last row in a CEA-608 display buffer
46016
46017    var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
46018    // getting it through bit logic.
46019
46020    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
46021    // cells. The "bottom" row is the last element in the outer array.
46022    // We keep track of positioning information as we go by storing the
46023    // number of indentations and the tab offset in this buffer.
46024
46025    var createDisplayBuffer = function () {
46026      var result = [],
46027        i = BOTTOM_ROW + 1;
46028      while (i--) {
46029        result.push({
46030          text: '',
46031          indent: 0,
46032          offset: 0
46033        });
46034      }
46035      return result;
46036    };
46037    var Cea608Stream = function (field, dataChannel) {
46038      Cea608Stream.prototype.init.call(this);
46039      this.field_ = field || 0;
46040      this.dataChannel_ = dataChannel || 0;
46041      this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
46042      this.setConstants();
46043      this.reset();
46044      this.push = function (packet) {
46045        var data, swap, char0, char1, text; // remove the parity bits
46046
46047        data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
46048
46049        if (data === this.lastControlCode_) {
46050          this.lastControlCode_ = null;
46051          return;
46052        } // Store control codes
46053
46054        if ((data & 0xf000) === 0x1000) {
46055          this.lastControlCode_ = data;
46056        } else if (data !== this.PADDING_) {
46057          this.lastControlCode_ = null;
46058        }
46059        char0 = data >>> 8;
46060        char1 = data & 0xff;
46061        if (data === this.PADDING_) {
46062          return;
46063        } else if (data === this.RESUME_CAPTION_LOADING_) {
46064          this.mode_ = 'popOn';
46065        } else if (data === this.END_OF_CAPTION_) {
46066          // If an EOC is received while in paint-on mode, the displayed caption
46067          // text should be swapped to non-displayed memory as if it was a pop-on
46068          // caption. Because of that, we should explicitly switch back to pop-on
46069          // mode
46070          this.mode_ = 'popOn';
46071          this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
46072
46073          this.flushDisplayed(packet.pts); // flip memory
46074
46075          swap = this.displayed_;
46076          this.displayed_ = this.nonDisplayed_;
46077          this.nonDisplayed_ = swap; // start measuring the time to display the caption
46078
46079          this.startPts_ = packet.pts;
46080        } else if (data === this.ROLL_UP_2_ROWS_) {
46081          this.rollUpRows_ = 2;
46082          this.setRollUp(packet.pts);
46083        } else if (data === this.ROLL_UP_3_ROWS_) {
46084          this.rollUpRows_ = 3;
46085          this.setRollUp(packet.pts);
46086        } else if (data === this.ROLL_UP_4_ROWS_) {
46087          this.rollUpRows_ = 4;
46088          this.setRollUp(packet.pts);
46089        } else if (data === this.CARRIAGE_RETURN_) {
46090          this.clearFormatting(packet.pts);
46091          this.flushDisplayed(packet.pts);
46092          this.shiftRowsUp_();
46093          this.startPts_ = packet.pts;
46094        } else if (data === this.BACKSPACE_) {
46095          if (this.mode_ === 'popOn') {
46096            this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
46097          } else {
46098            this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
46099          }
46100        } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
46101          this.flushDisplayed(packet.pts);
46102          this.displayed_ = createDisplayBuffer();
46103        } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
46104          this.nonDisplayed_ = createDisplayBuffer();
46105        } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
46106          if (this.mode_ !== 'paintOn') {
46107            // NOTE: This should be removed when proper caption positioning is
46108            // implemented
46109            this.flushDisplayed(packet.pts);
46110            this.displayed_ = createDisplayBuffer();
46111          }
46112          this.mode_ = 'paintOn';
46113          this.startPts_ = packet.pts; // Append special characters to caption text
46114        } else if (this.isSpecialCharacter(char0, char1)) {
46115          // Bitmask char0 so that we can apply character transformations
46116          // regardless of field and data channel.
46117          // Then byte-shift to the left and OR with char1 so we can pass the
46118          // entire character code to `getCharFromCode`.
46119          char0 = (char0 & 0x03) << 8;
46120          text = getCharFromCode(char0 | char1);
46121          this[this.mode_](packet.pts, text);
46122          this.column_++; // Append extended characters to caption text
46123        } else if (this.isExtCharacter(char0, char1)) {
46124          // Extended characters always follow their "non-extended" equivalents.
46125          // IE if a "è" is desired, you'll always receive "eè"; non-compliant
46126          // decoders are supposed to drop the "è", while compliant decoders
46127          // backspace the "e" and insert "è".
46128          // Delete the previous character
46129          if (this.mode_ === 'popOn') {
46130            this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
46131          } else {
46132            this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
46133          } // Bitmask char0 so that we can apply character transformations
46134          // regardless of field and data channel.
46135          // Then byte-shift to the left and OR with char1 so we can pass the
46136          // entire character code to `getCharFromCode`.
46137
46138          char0 = (char0 & 0x03) << 8;
46139          text = getCharFromCode(char0 | char1);
46140          this[this.mode_](packet.pts, text);
46141          this.column_++; // Process mid-row codes
46142        } else if (this.isMidRowCode(char0, char1)) {
46143          // Attributes are not additive, so clear all formatting
46144          this.clearFormatting(packet.pts); // According to the standard, mid-row codes
46145          // should be replaced with spaces, so add one now
46146
46147          this[this.mode_](packet.pts, ' ');
46148          this.column_++;
46149          if ((char1 & 0xe) === 0xe) {
46150            this.addFormatting(packet.pts, ['i']);
46151          }
46152          if ((char1 & 0x1) === 0x1) {
46153            this.addFormatting(packet.pts, ['u']);
46154          } // Detect offset control codes and adjust cursor
46155        } else if (this.isOffsetControlCode(char0, char1)) {
46156          // Cursor position is set by indent PAC (see below) in 4-column
46157          // increments, with an additional offset code of 1-3 to reach any
46158          // of the 32 columns specified by CEA-608. So all we need to do
46159          // here is increment the column cursor by the given offset.
46160          const offset = char1 & 0x03; // For an offest value 1-3, set the offset for that caption
46161          // in the non-displayed array.
46162
46163          this.nonDisplayed_[this.row_].offset = offset;
46164          this.column_ += offset; // Detect PACs (Preamble Address Codes)
46165        } else if (this.isPAC(char0, char1)) {
46166          // There's no logic for PAC -> row mapping, so we have to just
46167          // find the row code in an array and use its index :(
46168          var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
46169
46170          if (this.mode_ === 'rollUp') {
46171            // This implies that the base row is incorrectly set.
46172            // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
46173            // of roll-up rows set.
46174            if (row - this.rollUpRows_ + 1 < 0) {
46175              row = this.rollUpRows_ - 1;
46176            }
46177            this.setRollUp(packet.pts, row);
46178          }
46178
46179          if (row !== this.row_) {
46180            // formatting is only persistent for current row
46181            this.clearFormatting(packet.pts);
46182            this.row_ = row;
46183          } // All PACs can apply underline, so detect and apply
46184          // (All odd-numbered second bytes set underline)
46185
46186          if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
46187            this.addFormatting(packet.pts, ['u']);
46188          }
46189          if ((data & 0x10) === 0x10) {
46190            // We've got an indent level code. Each successive even number
vendor: 1,262 bytes, lines 46191-46218
46191            // increments the column cursor by 4, so we can get the desired
46192            // column position by bit-shifting to the right (to get n/2)
46193            // and multiplying by 4.
46194            const indentations = (data & 0xe) >> 1;
46195            this.column_ = indentations * 4; // add to the number of indentations for positioning
46196
46197            this.nonDisplayed_[this.row_].indent += indentations;
46198          }
46199          if (this.isColorPAC(char1)) {
46200            // it's a color code, though we only support white, which
46201            // can be either normal or italicized. white italics can be
46202            // either 0x4e or 0x6e depending on the row, so we just
46203            // bitwise-and with 0xe to see if italics should be turned on
46204            if ((char1 & 0xe) === 0xe) {
46205              this.addFormatting(packet.pts, ['i']);
46206            }
46207          } // We have a normal character in char0, and possibly one in char1
46208        } else if (this.isNormalChar(char0)) {
46209          if (char1 === 0x00) {
46210            char1 = null;
46211          }
46212          text = getCharFromCode(char0);
46213          text += getCharFromCode(char1);
46214          this[this.mode_](packet.pts, text);
46215          this.column_ += text.length;
46216        } // finish data processing
46217      };
46218    };
vendor: 15,185 bytes, lines 46219-46614
46219
46220    Cea608Stream.prototype = new Stream$7(); // Trigger a cue point that captures the current state of the
46221    // display buffer
46222
46223    Cea608Stream.prototype.flushDisplayed = function (pts) {
46224      const logWarning = index => {
46225        this.trigger('log', {
46226          level: 'warn',
46227          message: 'Skipping a malformed 608 caption at index ' + index + '.'
46228        });
46229      };
46230      const content = [];
46231      this.displayed_.forEach((row, i) => {
46232        if (row && row.text && row.text.length) {
46233          try {
46234            // remove spaces from the start and end of the string
46235            row.text = row.text.trim();
46236          } catch (e) {
46237            // Ordinarily, this shouldn't happen. However, caption
46238            // parsing errors should not throw exceptions and
46239            // break playback.
46240            logWarning(i);
46241          } // See the below link for more details on the following fields:
46242          // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
46243
46244          if (row.text.length) {
46245            content.push({
46246              // The text to be displayed in the caption from this specific row, with whitespace removed.
46247              text: row.text,
46248              // Value between 1 and 15 representing the PAC row used to calculate line height.
46249              line: i + 1,
46250              // A number representing the indent position by percentage (CEA-608 PAC indent code).
46251              // The value will be a number between 10 and 80. Offset is used to add an aditional
46252              // value to the position if necessary.
46253              position: 10 + Math.min(70, row.indent * 10) + row.offset * 2.5
46254            });
46255          }
46256        } else if (row === undefined || row === null) {
46257          logWarning(i);
46258        }
46259      });
46260      if (content.length) {
46261        this.trigger('data', {
46262          startPts: this.startPts_,
46263          endPts: pts,
46264          content,
46265          stream: this.name_
46266        });
46267      }
46268    };
46269    /**
46270     * Zero out the data, used for startup and on seek
46271     */
46272
46273    Cea608Stream.prototype.reset = function () {
46274      this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
46275      // actually display captions. If a caption is shifted to a row
46276      // with a lower index than this, it is cleared from the display
46277      // buffer
46278
46279      this.topRow_ = 0;
46280      this.startPts_ = 0;
46281      this.displayed_ = createDisplayBuffer();
46282      this.nonDisplayed_ = createDisplayBuffer();
46283      this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
46284
46285      this.column_ = 0;
46286      this.row_ = BOTTOM_ROW;
46287      this.rollUpRows_ = 2; // This variable holds currently-applied formatting
46288
46289      this.formatting_ = [];
46290    };
46291    /**
46292     * Sets up control code and related constants for this instance
46293     */
46294
46295    Cea608Stream.prototype.setConstants = function () {
46296      // The following attributes have these uses:
46297      // ext_ :    char0 for mid-row codes, and the base for extended
46298      //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
46299      //           extended codes)
46300      // control_: char0 for control codes, except byte-shifted to the
46301      //           left so that we can do this.control_ | CONTROL_CODE
46302      // offset_:  char0 for tab offset codes
46303      //
46304      // It's also worth noting that control codes, and _only_ control codes,
46305      // differ between field 1 and field2. Field 2 control codes are always
46306      // their field 1 value plus 1. That's why there's the "| field" on the
46307      // control value.
46308      if (this.dataChannel_ === 0) {
46309        this.BASE_ = 0x10;
46310        this.EXT_ = 0x11;
46311        this.CONTROL_ = (0x14 | this.field_) << 8;
46312        this.OFFSET_ = 0x17;
46313      } else if (this.dataChannel_ === 1) {
46314        this.BASE_ = 0x18;
46315        this.EXT_ = 0x19;
46316        this.CONTROL_ = (0x1c | this.field_) << 8;
46317        this.OFFSET_ = 0x1f;
46318      } // Constants for the LSByte command codes recognized by Cea608Stream. This
46319      // list is not exhaustive. For a more comprehensive listing and semantics see
46320      // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
46321      // Padding
46322
46323      this.PADDING_ = 0x0000; // Pop-on Mode
46324
46325      this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
46326      this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
46327
46328      this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
46329      this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
46330      this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
46331      this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
46332
46333      this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
46334
46335      this.BACKSPACE_ = this.CONTROL_ | 0x21;
46336      this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
46337      this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
46338    };
46339    /**
46340     * Detects if the 2-byte packet data is a special character
46341     *
46342     * Special characters have a second byte in the range 0x30 to 0x3f,
46343     * with the first byte being 0x11 (for data channel 1) or 0x19 (for
46344     * data channel 2).
46345     *
46346     * @param  {Integer} char0 The first byte
46347     * @param  {Integer} char1 The second byte
46348     * @return {Boolean}       Whether the 2 bytes are an special character
46349     */
46350
46351    Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
46352      return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
46353    };
46354    /**
46355     * Detects if the 2-byte packet data is an extended character
46356     *
46357     * Extended characters have a second byte in the range 0x20 to 0x3f,
46358     * with the first byte being 0x12 or 0x13 (for data channel 1) or
46359     * 0x1a or 0x1b (for data channel 2).
46360     *
46361     * @param  {Integer} char0 The first byte
46362     * @param  {Integer} char1 The second byte
46363     * @return {Boolean}       Whether the 2 bytes are an extended character
46364     */
46365
46366    Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
46367      return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
46368    };
46369    /**
46370     * Detects if the 2-byte packet is a mid-row code
46371     *
46372     * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
46373     * the first byte being 0x11 (for data channel 1) or 0x19 (for data
46374     * channel 2).
46375     *
46376     * @param  {Integer} char0 The first byte
46377     * @param  {Integer} char1 The second byte
46378     * @return {Boolean}       Whether the 2 bytes are a mid-row code
46379     */
46380
46381    Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
46382      return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
46383    };
46384    /**
46385     * Detects if the 2-byte packet is an offset control code
46386     *
46387     * Offset control codes have a second byte in the range 0x21 to 0x23,
46388     * with the first byte being 0x17 (for data channel 1) or 0x1f (for
46389     * data channel 2).
46390     *
46391     * @param  {Integer} char0 The first byte
46392     * @param  {Integer} char1 The second byte
46393     * @return {Boolean}       Whether the 2 bytes are an offset control code
46394     */
46395
46396    Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
46397      return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
46398    };
46399    /**
46400     * Detects if the 2-byte packet is a Preamble Address Code
46401     *
46402     * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
46403     * or 0x18 to 0x1f (for data channel 2), with the second byte in the
46404     * range 0x40 to 0x7f.
46405     *
46406     * @param  {Integer} char0 The first byte
46407     * @param  {Integer} char1 The second byte
46408     * @return {Boolean}       Whether the 2 bytes are a PAC
46409     */
46410
46411    Cea608Stream.prototype.isPAC = function (char0, char1) {
46412      return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
46413    };
46414    /**
46415     * Detects if a packet's second byte is in the range of a PAC color code
46416     *
46417     * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
46418     * 0x60 to 0x6f.
46419     *
46420     * @param  {Integer} char1 The second byte
46421     * @return {Boolean}       Whether the byte is a color PAC
46422     */
46423
46424    Cea608Stream.prototype.isColorPAC = function (char1) {
46425      return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
46426    };
46427    /**
46428     * Detects if a single byte is in the range of a normal character
46429     *
46430     * Normal text bytes are in the range 0x20 to 0x7f.
46431     *
46432     * @param  {Integer} char  The byte
46433     * @return {Boolean}       Whether the byte is a normal character
46434     */
46435
46436    Cea608Stream.prototype.isNormalChar = function (char) {
46437      return char >= 0x20 && char <= 0x7f;
46438    };
46439    /**
46440     * Configures roll-up
46441     *
46442     * @param  {Integer} pts         Current PTS
46443     * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
46444     *                               a new position
46445     */
46446
46447    Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
46448      // Reset the base row to the bottom row when switching modes
46449      if (this.mode_ !== 'rollUp') {
46450        this.row_ = BOTTOM_ROW;
46451        this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
46452
46453        this.flushDisplayed(pts);
46454        this.nonDisplayed_ = createDisplayBuffer();
46455        this.displayed_ = createDisplayBuffer();
46456      }
46457      if (newBaseRow !== undefined && newBaseRow !== this.row_) {
46458        // move currently displayed captions (up or down) to the new base row
46459        for (var i = 0; i < this.rollUpRows_; i++) {
46460          this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
46461          this.displayed_[this.row_ - i] = {
46462            text: '',
46463            indent: 0,
46464            offset: 0
46465          };
46466        }
46467      }
46468      if (newBaseRow === undefined) {
46469        newBaseRow = this.row_;
46470      }
46471      this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
46472    }; // Adds the opening HTML tag for the passed character to the caption text,
46473    // and keeps track of it for later closing
46474
46475    Cea608Stream.prototype.addFormatting = function (pts, format) {
46476      this.formatting_ = this.formatting_.concat(format);
46477      var text = format.reduce(function (text, format) {
46478        return text + '<' + format + '>';
46479      }, '');
46480      this[this.mode_](pts, text);
46481    }; // Adds HTML closing tags for current formatting to caption text and
46482    // clears remembered formatting
46483
46484    Cea608Stream.prototype.clearFormatting = function (pts) {
46485      if (!this.formatting_.length) {
46486        return;
46487      }
46488      var text = this.formatting_.reverse().reduce(function (text, format) {
46489        return text + '</' + format + '>';
46490      }, '');
46491      this.formatting_ = [];
46492      this[this.mode_](pts, text);
46493    }; // Mode Implementations
46494
46495    Cea608Stream.prototype.popOn = function (pts, text) {
46496      var baseRow = this.nonDisplayed_[this.row_].text; // buffer characters
46497
46498      baseRow += text;
46499      this.nonDisplayed_[this.row_].text = baseRow;
46500    };
46501    Cea608Stream.prototype.rollUp = function (pts, text) {
46502      var baseRow = this.displayed_[this.row_].text;
46503      baseRow += text;
46504      this.displayed_[this.row_].text = baseRow;
46505    };
46506    Cea608Stream.prototype.shiftRowsUp_ = function () {
46507      var i; // clear out inactive rows
46508
46509      for (i = 0; i < this.topRow_; i++) {
46510        this.displayed_[i] = {
46511          text: '',
46512          indent: 0,
46513          offset: 0
46514        };
46515      }
46516      for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
46517        this.displayed_[i] = {
46518          text: '',
46519          indent: 0,
46520          offset: 0
46521        };
46522      } // shift displayed rows up
46523
46524      for (i = this.topRow_; i < this.row_; i++) {
46525        this.displayed_[i] = this.displayed_[i + 1];
46526      } // clear out the bottom row
46527
46528      this.displayed_[this.row_] = {
46529        text: '',
46530        indent: 0,
46531        offset: 0
46532      };
46533    };
46534    Cea608Stream.prototype.paintOn = function (pts, text) {
46535      var baseRow = this.displayed_[this.row_].text;
46536      baseRow += text;
46537      this.displayed_[this.row_].text = baseRow;
46538    }; // exports
46539
46540    var captionStream = {
46541      CaptionStream: CaptionStream$2,
46542      Cea608Stream: Cea608Stream,
46543      Cea708Stream: Cea708Stream
46544    };
46545    /**
46546     * mux.js
46547     *
46548     * Copyright (c) Brightcove
46549     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46550     */
46551
46552    var streamTypes = {
46553      H264_STREAM_TYPE: 0x1B,
46554      ADTS_STREAM_TYPE: 0x0F,
46555      METADATA_STREAM_TYPE: 0x15
46556    };
46557    /**
46558     * mux.js
46559     *
46560     * Copyright (c) Brightcove
46561     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46562     *
46563     * Accepts program elementary stream (PES) data events and corrects
46564     * decode and presentation time stamps to account for a rollover
46565     * of the 33 bit value.
46566     */
46567
46568    var Stream$6 = stream;
46569    var MAX_TS = 8589934592;
46570    var RO_THRESH = 4294967296;
46571    var TYPE_SHARED = 'shared';
46572    var handleRollover$1 = function (value, reference) {
46573      var direction = 1;
46574      if (value > reference) {
46575        // If the current timestamp value is greater than our reference timestamp and we detect a
46576        // timestamp rollover, this means the roll over is happening in the opposite direction.
46577        // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
46578        // point will be set to a small number, e.g. 1. The user then seeks backwards over the
46579        // rollover point. In loading this segment, the timestamp values will be very large,
46580        // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
46581        // the time stamp to be `value - 2^33`.
46582        direction = -1;
46583      } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
46584      // cause an incorrect adjustment.
46585
46586      while (Math.abs(reference - value) > RO_THRESH) {
46587        value += direction * MAX_TS;
46588      }
46589      return value;
46590    };
46591    var TimestampRolloverStream$1 = function (type) {
46592      var lastDTS, referenceDTS;
46593      TimestampRolloverStream$1.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
46594      // video and audio. We could use `undefined` here, but having a string
46595      // makes debugging a little clearer.
46596
46597      this.type_ = type || TYPE_SHARED;
46598      this.push = function (data) {
46599        /**
46600         * Rollover stream expects data from elementary stream.
46601         * Elementary stream can push forward 2 types of data
46602         * - Parsed Video/Audio/Timed-metadata PES (packetized elementary stream) packets
46603         * - Tracks metadata from PMT (Program Map Table)
46604         * Rollover stream expects pts/dts info to be available, since it stores lastDTS
46605         * We should ignore non-PES packets since they may override lastDTS to undefined.
46606         * lastDTS is important to signal the next segments
46607         * about rollover from the previous segments.
46608         */
46609        if (data.type === 'metadata') {
46610          this.trigger('data', data);
46611          return;
46612        } // Any "shared" rollover streams will accept _all_ data. Otherwise,
46613        // streams will only accept data that matches their type.
46614
vendor: 26,567 bytes, lines 46615-47258
46615        if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
46616          return;
46617        }
46618        if (referenceDTS === undefined) {
46619          referenceDTS = data.dts;
46620        }
46621        data.dts = handleRollover$1(data.dts, referenceDTS);
46622        data.pts = handleRollover$1(data.pts, referenceDTS);
46623        lastDTS = data.dts;
46624        this.trigger('data', data);
46625      };
46626      this.flush = function () {
46627        referenceDTS = lastDTS;
46628        this.trigger('done');
46629      };
46630      this.endTimeline = function () {
46631        this.flush();
46632        this.trigger('endedtimeline');
46633      };
46634      this.discontinuity = function () {
46635        referenceDTS = void 0;
46636        lastDTS = void 0;
46637      };
46638      this.reset = function () {
46639        this.discontinuity();
46640        this.trigger('reset');
46641      };
46642    };
46643    TimestampRolloverStream$1.prototype = new Stream$6();
46644    var timestampRolloverStream = {
46645      TimestampRolloverStream: TimestampRolloverStream$1,
46646      handleRollover: handleRollover$1
46647    }; // Once IE11 support is dropped, this function should be removed.
46648
46649    var typedArrayIndexOf$1 = (typedArray, element, fromIndex) => {
46650      if (!typedArray) {
46651        return -1;
46652      }
46653      var currentIndex = fromIndex;
46654      for (; currentIndex < typedArray.length; currentIndex++) {
46655        if (typedArray[currentIndex] === element) {
46656          return currentIndex;
46657        }
46658      }
46659      return -1;
46660    };
46661    var typedArray = {
46662      typedArrayIndexOf: typedArrayIndexOf$1
46663    };
46664    /**
46665     * mux.js
46666     *
46667     * Copyright (c) Brightcove
46668     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46669     *
46670     * Tools for parsing ID3 frame data
46671     * @see http://id3.org/id3v2.3.0
46672     */
46673
46674    var typedArrayIndexOf = typedArray.typedArrayIndexOf,
46675      // Frames that allow different types of text encoding contain a text
46676      // encoding description byte [ID3v2.4.0 section 4.]
46677      textEncodingDescriptionByte = {
46678        Iso88591: 0x00,
46679        // ISO-8859-1, terminated with \0.
46680        Utf16: 0x01,
46681        // UTF-16 encoded Unicode BOM, terminated with \0\0
46682        Utf16be: 0x02,
46683        // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
46684        Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
46685      },
46686      // return a percent-encoded representation of the specified byte range
46687      // @see http://en.wikipedia.org/wiki/Percent-encoding 
46688      percentEncode$1 = function (bytes, start, end) {
46689        var i,
46690          result = '';
46691        for (i = start; i < end; i++) {
46692          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
46693        }
46694        return result;
46695      },
46696      // return the string representation of the specified byte range,
46697      // interpreted as UTf-8.
46698      parseUtf8 = function (bytes, start, end) {
46699        return decodeURIComponent(percentEncode$1(bytes, start, end));
46700      },
46701      // return the string representation of the specified byte range,
46702      // interpreted as ISO-8859-1.
46703      parseIso88591$1 = function (bytes, start, end) {
46704        return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
46705      },
46706      parseSyncSafeInteger$1 = function (data) {
46707        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
46708      },
46709      frameParsers = {
46710        'APIC': function (frame) {
46711          var i = 1,
46712            mimeTypeEndIndex,
46713            descriptionEndIndex,
46714            LINK_MIME_TYPE = '-->';
46715          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46716            // ignore frames with unrecognized character encodings
46717            return;
46718          } // parsing fields [ID3v2.4.0 section 4.14.]
46719
46720          mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
46721          if (mimeTypeEndIndex < 0) {
46722            // malformed frame
46723            return;
46724          } // parsing Mime type field (terminated with \0)
46725
46726          frame.mimeType = parseIso88591$1(frame.data, i, mimeTypeEndIndex);
46727          i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
46728
46729          frame.pictureType = frame.data[i];
46730          i++;
46731          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
46732          if (descriptionEndIndex < 0) {
46733            // malformed frame
46734            return;
46735          } // parsing Description field (terminated with \0)
46736
46737          frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
46738          i = descriptionEndIndex + 1;
46739          if (frame.mimeType === LINK_MIME_TYPE) {
46740            // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
46741            frame.url = parseIso88591$1(frame.data, i, frame.data.length);
46742          } else {
46743            // parsing Picture Data field as binary data
46744            frame.pictureData = frame.data.subarray(i, frame.data.length);
46745          }
46746        },
46747        'T*': function (frame) {
46748          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46749            // ignore frames with unrecognized character encodings
46750            return;
46751          } // parse text field, do not include null terminator in the frame value
46752          // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
46753
46754          frame.value = parseUtf8(frame.data, 1, frame.data.length).replace(/\0*$/, ''); // text information frames supports multiple strings, stored as a terminator separated list [ID3v2.4.0 section 4.2.]
46755
46756          frame.values = frame.value.split('\0');
46757        },
46758        'TXXX': function (frame) {
46759          var descriptionEndIndex;
46760          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46761            // ignore frames with unrecognized character encodings
46762            return;
46763          }
46764          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
46765          if (descriptionEndIndex === -1) {
46766            return;
46767          } // parse the text fields
46768
46769          frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
46770          // frames that allow different types of encoding contain terminated text
46771          // [ID3v2.4.0 section 4.]
46772
46773          frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
46774          frame.data = frame.value;
46775        },
46776        'W*': function (frame) {
46777          // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
46778          // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
46779          frame.url = parseIso88591$1(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
46780        },
46781        'WXXX': function (frame) {
46782          var descriptionEndIndex;
46783          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46784            // ignore frames with unrecognized character encodings
46785            return;
46786          }
46787          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
46788          if (descriptionEndIndex === -1) {
46789            return;
46790          } // parse the description and URL fields
46791
46792          frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
46793          // if the value is followed by a string termination all the following information
46794          // should be ignored [ID3v2.4.0 section 4.3]
46795
46796          frame.url = parseIso88591$1(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
46797        },
46798        'PRIV': function (frame) {
46799          var i;
46800          for (i = 0; i < frame.data.length; i++) {
46801            if (frame.data[i] === 0) {
46802              // parse the description and URL fields
46803              frame.owner = parseIso88591$1(frame.data, 0, i);
46804              break;
46805            }
46806          }
46807          frame.privateData = frame.data.subarray(i + 1);
46808          frame.data = frame.privateData;
46809        }
46810      };
46811    var parseId3Frames$1 = function (data) {
46812      var frameSize,
46813        frameHeader,
46814        frameStart = 10,
46815        tagSize = 0,
46816        frames = []; // If we don't have enough data for a header, 10 bytes, 
46817      // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
46818
46819      if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
46820        return;
46821      } // the frame size is transmitted as a 28-bit integer in the
46822      // last four bytes of the ID3 header.
46823      // The most significant bit of each byte is dropped and the
46824      // results concatenated to recover the actual value.
46825
46826      tagSize = parseSyncSafeInteger$1(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
46827      // convenient for our comparisons to include it
46828
46829      tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
46830
46831      var hasExtendedHeader = data[5] & 0x40;
46832      if (hasExtendedHeader) {
46833        // advance the frame start past the extended header
46834        frameStart += 4; // header size field
46835
46836        frameStart += parseSyncSafeInteger$1(data.subarray(10, 14));
46837        tagSize -= parseSyncSafeInteger$1(data.subarray(16, 20)); // clip any padding off the end
46838      } // parse one or more ID3 frames
46839      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46840
46841      do {
46842        // determine the number of bytes in this frame
46843        frameSize = parseSyncSafeInteger$1(data.subarray(frameStart + 4, frameStart + 8));
46844        if (frameSize < 1) {
46845          break;
46846        }
46847        frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
46848        var frame = {
46849          id: frameHeader,
46850          data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
46851        };
46852        frame.key = frame.id; // parse frame values
46853
46854        if (frameParsers[frame.id]) {
46855          // use frame specific parser
46856          frameParsers[frame.id](frame);
46857        } else if (frame.id[0] === 'T') {
46858          // use text frame generic parser
46859          frameParsers['T*'](frame);
46860        } else if (frame.id[0] === 'W') {
46861          // use URL link frame generic parser
46862          frameParsers['W*'](frame);
46863        }
46864        frames.push(frame);
46865        frameStart += 10; // advance past the frame header
46866
46867        frameStart += frameSize; // advance past the frame body
46868      } while (frameStart < tagSize);
46869      return frames;
46870    };
46871    var parseId3 = {
46872      parseId3Frames: parseId3Frames$1,
46873      parseSyncSafeInteger: parseSyncSafeInteger$1,
46874      frameParsers: frameParsers
46875    };
46876    /**
46877     * mux.js
46878     *
46879     * Copyright (c) Brightcove
46880     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46881     *
46882     * Accepts program elementary stream (PES) data events and parses out
46883     * ID3 metadata from them, if present.
46884     * @see http://id3.org/id3v2.3.0
46885     */
46886
46887    var Stream$5 = stream,
46888      StreamTypes$3 = streamTypes,
46889      id3 = parseId3,
46890      MetadataStream;
46891    MetadataStream = function (options) {
46892      var settings = {
46893          // the bytes of the program-level descriptor field in MP2T
46894          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
46895          // program element descriptors"
46896          descriptor: options && options.descriptor
46897        },
46898        // the total size in bytes of the ID3 tag being parsed
46899        tagSize = 0,
46900        // tag data that is not complete enough to be parsed
46901        buffer = [],
46902        // the total number of bytes currently in the buffer
46903        bufferSize = 0,
46904        i;
46905      MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
46906      // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
46907
46908      this.dispatchType = StreamTypes$3.METADATA_STREAM_TYPE.toString(16);
46909      if (settings.descriptor) {
46910        for (i = 0; i < settings.descriptor.length; i++) {
46911          this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
46912        }
46913      }
46914      this.push = function (chunk) {
46915        var tag, frameStart, frameSize, frame, i, frameHeader;
46916        if (chunk.type !== 'timed-metadata') {
46917          return;
46918        } // if data_alignment_indicator is set in the PES header,
46919        // we must have the start of a new ID3 tag. Assume anything
46920        // remaining in the buffer was malformed and throw it out
46921
46922        if (chunk.dataAlignmentIndicator) {
46923          bufferSize = 0;
46924          buffer.length = 0;
46925        } // ignore events that don't look like ID3 data
46926
46927        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))) {
46928          this.trigger('log', {
46929            level: 'warn',
46930            message: 'Skipping unrecognized metadata packet'
46931          });
46932          return;
46933        } // add this chunk to the data we've collected so far
46934
46935        buffer.push(chunk);
46936        bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
46937
46938        if (buffer.length === 1) {
46939          // the frame size is transmitted as a 28-bit integer in the
46940          // last four bytes of the ID3 header.
46941          // The most significant bit of each byte is dropped and the
46942          // results concatenated to recover the actual value.
46943          tagSize = id3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
46944          // convenient for our comparisons to include it
46945
46946          tagSize += 10;
46947        } // if the entire frame has not arrived, wait for more data
46948
46949        if (bufferSize < tagSize) {
46950          return;
46951        } // collect the entire frame so it can be parsed
46952
46953        tag = {
46954          data: new Uint8Array(tagSize),
46955          frames: [],
46956          pts: buffer[0].pts,
46957          dts: buffer[0].dts
46958        };
46959        for (i = 0; i < tagSize;) {
46960          tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
46961          i += buffer[0].data.byteLength;
46962          bufferSize -= buffer[0].data.byteLength;
46963          buffer.shift();
46964        } // find the start of the first frame and the end of the tag
46965
46966        frameStart = 10;
46967        if (tag.data[5] & 0x40) {
46968          // advance the frame start past the extended header
46969          frameStart += 4; // header size field
46970
46971          frameStart += id3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
46972
46973          tagSize -= id3.parseSyncSafeInteger(tag.data.subarray(16, 20));
46974        } // parse one or more ID3 frames
46975        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46976
46977        do {
46978          // determine the number of bytes in this frame
46979          frameSize = id3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
46980          if (frameSize < 1) {
46981            this.trigger('log', {
46982              level: 'warn',
46983              message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
46984            }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
46985            // to be sent along.
46986
46987            break;
46988          }
46989          frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
46990          frame = {
46991            id: frameHeader,
46992            data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
46993          };
46994          frame.key = frame.id; // parse frame values
46995
46996          if (id3.frameParsers[frame.id]) {
46997            // use frame specific parser
46998            id3.frameParsers[frame.id](frame);
46999          } else if (frame.id[0] === 'T') {
47000            // use text frame generic parser
47001            id3.frameParsers['T*'](frame);
47002          } else if (frame.id[0] === 'W') {
47003            // use URL link frame generic parser
47004            id3.frameParsers['W*'](frame);
47005          } // handle the special PRIV frame used to indicate the start
47006          // time for raw AAC data
47007
47008          if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
47009            var d = frame.data,
47010              size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
47011            size *= 4;
47012            size += d[7] & 0x03;
47013            frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
47014            // on the value of this frame
47015            // we couldn't have known the appropriate pts and dts before
47016            // parsing this ID3 tag so set those values now
47017
47018            if (tag.pts === undefined && tag.dts === undefined) {
47019              tag.pts = frame.timeStamp;
47020              tag.dts = frame.timeStamp;
47021            }
47022            this.trigger('timestamp', frame);
47023          }
47024          tag.frames.push(frame);
47025          frameStart += 10; // advance past the frame header
47026
47027          frameStart += frameSize; // advance past the frame body
47028        } while (frameStart < tagSize);
47029        this.trigger('data', tag);
47030      };
47031    };
47032    MetadataStream.prototype = new Stream$5();
47033    var metadataStream = MetadataStream;
47034    /**
47035     * mux.js
47036     *
47037     * Copyright (c) Brightcove
47038     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47039     *
47040     * A stream-based mp2t to mp4 converter. This utility can be used to
47041     * deliver mp4s to a SourceBuffer on platforms that support native
47042     * Media Source Extensions.
47043     */
47044
47045    var Stream$4 = stream,
47046      CaptionStream$1 = captionStream,
47047      StreamTypes$2 = streamTypes,
47048      TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
47049
47050    var TransportPacketStream, TransportParseStream, ElementaryStream; // constants
47051
47052    var MP2T_PACKET_LENGTH$1 = 188,
47053      // bytes
47054      SYNC_BYTE$1 = 0x47;
47055    /**
47056     * Splits an incoming stream of binary data into MPEG-2 Transport
47057     * Stream packets.
47058     */
47059
47060    TransportPacketStream = function () {
47061      var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
47062        bytesInBuffer = 0;
47063      TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
47064
47065      /**
47066       * Split a stream of data into M2TS packets
47067      **/
47068
47069      this.push = function (bytes) {
47070        var startIndex = 0,
47071          endIndex = MP2T_PACKET_LENGTH$1,
47072          everything; // If there are bytes remaining from the last segment, prepend them to the
47073        // bytes that were pushed in
47074
47075        if (bytesInBuffer) {
47076          everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
47077          everything.set(buffer.subarray(0, bytesInBuffer));
47078          everything.set(bytes, bytesInBuffer);
47079          bytesInBuffer = 0;
47080        } else {
47081          everything = bytes;
47082        } // While we have enough data for a packet
47083
47084        while (endIndex < everything.byteLength) {
47085          // Look for a pair of start and end sync bytes in the data..
47086          if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
47087            // We found a packet so emit it and jump one whole packet forward in
47088            // the stream
47089            this.trigger('data', everything.subarray(startIndex, endIndex));
47090            startIndex += MP2T_PACKET_LENGTH$1;
47091            endIndex += MP2T_PACKET_LENGTH$1;
47092            continue;
47093          } // If we get here, we have somehow become de-synchronized and we need to step
47094          // forward one byte at a time until we find a pair of sync bytes that denote
47095          // a packet
47096
47097          startIndex++;
47098          endIndex++;
47099        } // If there was some data left over at the end of the segment that couldn't
47100        // possibly be a whole packet, keep it because it might be the start of a packet
47101        // that continues in the next segment
47102
47103        if (startIndex < everything.byteLength) {
47104          buffer.set(everything.subarray(startIndex), 0);
47105          bytesInBuffer = everything.byteLength - startIndex;
47106        }
47107      };
47108      /**
47109       * Passes identified M2TS packets to the TransportParseStream to be parsed
47110      **/
47111
47112      this.flush = function () {
47113        // If the buffer contains a whole packet when we are being flushed, emit it
47114        // and empty the buffer. Otherwise hold onto the data because it may be
47115        // important for decoding the next segment
47116        if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
47117          this.trigger('data', buffer);
47118          bytesInBuffer = 0;
47119        }
47120        this.trigger('done');
47121      };
47122      this.endTimeline = function () {
47123        this.flush();
47124        this.trigger('endedtimeline');
47125      };
47126      this.reset = function () {
47127        bytesInBuffer = 0;
47128        this.trigger('reset');
47129      };
47130    };
47131    TransportPacketStream.prototype = new Stream$4();
47132    /**
47133     * Accepts an MP2T TransportPacketStream and emits data events with parsed
47134     * forms of the individual transport stream packets.
47135     */
47136
47137    TransportParseStream = function () {
47138      var parsePsi, parsePat, parsePmt, self;
47139      TransportParseStream.prototype.init.call(this);
47140      self = this;
47141      this.packetsWaitingForPmt = [];
47142      this.programMapTable = undefined;
47143      parsePsi = function (payload, psi) {
47144        var offset = 0; // PSI packets may be split into multiple sections and those
47145        // sections may be split into multiple packets. If a PSI
47146        // section starts in this packet, the payload_unit_start_indicator
47147        // will be true and the first byte of the payload will indicate
47148        // the offset from the current position to the start of the
47149        // section.
47150
47151        if (psi.payloadUnitStartIndicator) {
47152          offset += payload[offset] + 1;
47153        }
47154        if (psi.type === 'pat') {
47155          parsePat(payload.subarray(offset), psi);
47156        } else {
47157          parsePmt(payload.subarray(offset), psi);
47158        }
47159      };
47160      parsePat = function (payload, pat) {
47161        pat.section_number = payload[7]; // eslint-disable-line camelcase
47162
47163        pat.last_section_number = payload[8]; // eslint-disable-line camelcase
47164        // skip the PSI header and parse the first PMT entry
47165
47166        self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
47167        pat.pmtPid = self.pmtPid;
47168      };
47169      /**
47170       * Parse out the relevant fields of a Program Map Table (PMT).
47171       * @param payload {Uint8Array} the PMT-specific portion of an MP2T
47172       * packet. The first byte in this array should be the table_id
47173       * field.
47174       * @param pmt {object} the object that should be decorated with
47175       * fields parsed from the PMT.
47176       */
47177
47178      parsePmt = function (payload, pmt) {
47179        var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
47180        // take effect. We don't believe this should ever be the case
47181        // for HLS but we'll ignore "forward" PMT declarations if we see
47182        // them. Future PMT declarations have the current_next_indicator
47183        // set to zero.
47184
47185        if (!(payload[5] & 0x01)) {
47186          return;
47187        } // overwrite any existing program map table
47188
47189        self.programMapTable = {
47190          video: null,
47191          audio: null,
47192          'timed-metadata': {}
47193        }; // the mapping table ends at the end of the current section
47194
47195        sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
47196        tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
47197        // long the program info descriptors are
47198
47199        programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
47200
47201        offset = 12 + programInfoLength;
47202        while (offset < tableEnd) {
47203          var streamType = payload[offset];
47204          var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
47205          // TODO: should this be done for metadata too? for now maintain behavior of
47206          //       multiple metadata streams
47207
47208          if (streamType === StreamTypes$2.H264_STREAM_TYPE && self.programMapTable.video === null) {
47209            self.programMapTable.video = pid;
47210          } else if (streamType === StreamTypes$2.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
47211            self.programMapTable.audio = pid;
47212          } else if (streamType === StreamTypes$2.METADATA_STREAM_TYPE) {
47213            // map pid to stream type for metadata streams
47214            self.programMapTable['timed-metadata'][pid] = streamType;
47215          } // move to the next table entry
47216          // skip past the elementary stream descriptors, if present
47217
47218          offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
47219        } // record the map on the packet as well
47220
47221        pmt.programMapTable = self.programMapTable;
47222      };
47223      /**
47224       * Deliver a new MP2T packet to the next stream in the pipeline.
47225       */
47226
47227      this.push = function (packet) {
47228        var result = {},
47229          offset = 4;
47230        result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
47231
47232        result.pid = packet[1] & 0x1f;
47233        result.pid <<= 8;
47234        result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
47235        // fifth byte of the TS packet header. The adaptation field is
47236        // used to add stuffing to PES packets that don't fill a complete
47237        // TS packet, and to specify some forms of timing and control data
47238        // that we do not currently use.
47239
47240        if ((packet[3] & 0x30) >>> 4 > 0x01) {
47241          offset += packet[offset] + 1;
47242        } // parse the rest of the packet based on the type
47243
47244        if (result.pid === 0) {
47245          result.type = 'pat';
47246          parsePsi(packet.subarray(offset), result);
47247          this.trigger('data', result);
47248        } else if (result.pid === this.pmtPid) {
47249          result.type = 'pmt';
47250          parsePsi(packet.subarray(offset), result);
47251          this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
47252
47253          while (this.packetsWaitingForPmt.length) {
47254            this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
47255          }
47256        } else if (this.programMapTable === undefined) {
47257          // When we have not seen a PMT yet, defer further processing of
47258          // PES packets until one has been parsed
vendor: 32,290 bytes, lines 47259-48166
47259          this.packetsWaitingForPmt.push([packet, offset, result]);
47260        } else {
47261          this.processPes_(packet, offset, result);
47262        }
47263      };
47264      this.processPes_ = function (packet, offset, result) {
47265        // set the appropriate stream type
47266        if (result.pid === this.programMapTable.video) {
47267          result.streamType = StreamTypes$2.H264_STREAM_TYPE;
47268        } else if (result.pid === this.programMapTable.audio) {
47269          result.streamType = StreamTypes$2.ADTS_STREAM_TYPE;
47270        } else {
47271          // if not video or audio, it is timed-metadata or unknown
47272          // if unknown, streamType will be undefined
47273          result.streamType = this.programMapTable['timed-metadata'][result.pid];
47274        }
47275        result.type = 'pes';
47276        result.data = packet.subarray(offset);
47277        this.trigger('data', result);
47278      };
47279    };
47280    TransportParseStream.prototype = new Stream$4();
47281    TransportParseStream.STREAM_TYPES = {
47282      h264: 0x1b,
47283      adts: 0x0f
47284    };
47285    /**
47286     * Reconsistutes program elementary stream (PES) packets from parsed
47287     * transport stream packets. That is, if you pipe an
47288     * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
47289     * events will be events which capture the bytes for individual PES
47290     * packets plus relevant metadata that has been extracted from the
47291     * container.
47292     */
47293
47294    ElementaryStream = function () {
47295      var self = this,
47296        segmentHadPmt = false,
47297        // PES packet fragments
47298        video = {
47299          data: [],
47300          size: 0
47301        },
47302        audio = {
47303          data: [],
47304          size: 0
47305        },
47306        timedMetadata = {
47307          data: [],
47308          size: 0
47309        },
47310        programMapTable,
47311        parsePes = function (payload, pes) {
47312          var ptsDtsFlags;
47313          const startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
47314
47315          pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
47316          // that are frame data that is continuing from the previous fragment. This
47317          // is to check that the pes data is the start of a new pes payload
47318
47319          if (startPrefix !== 1) {
47320            return;
47321          } // get the packet length, this will be 0 for video
47322
47323          pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
47324
47325          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
47326          // and a DTS value. Determine what combination of values is
47327          // available to work with.
47328
47329          ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
47330          // performs all bitwise operations on 32-bit integers but javascript
47331          // supports a much greater range (52-bits) of integer using standard
47332          // mathematical operations.
47333          // We construct a 31-bit value using bitwise operators over the 31
47334          // most significant bits and then multiply by 4 (equal to a left-shift
47335          // of 2) before we add the final 2 least significant bits of the
47336          // timestamp (equal to an OR.)
47337
47338          if (ptsDtsFlags & 0xC0) {
47339            // the PTS and DTS are not written out directly. For information
47340            // on how they are encoded, see
47341            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
47342            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
47343            pes.pts *= 4; // Left shift by 2
47344
47345            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
47346
47347            pes.dts = pes.pts;
47348            if (ptsDtsFlags & 0x40) {
47349              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
47350              pes.dts *= 4; // Left shift by 2
47351
47352              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
47353            }
47354          } // the data section starts immediately after the PES header.
47355          // pes_header_data_length specifies the number of header bytes
47356          // that follow the last byte of the field.
47357
47358          pes.data = payload.subarray(9 + payload[8]);
47359        },
47360        /**
47361          * Pass completely parsed PES packets to the next stream in the pipeline
47362         **/
47363        flushStream = function (stream, type, forceFlush) {
47364          var packetData = new Uint8Array(stream.size),
47365            event = {
47366              type: type
47367            },
47368            i = 0,
47369            offset = 0,
47370            packetFlushable = false,
47371            fragment; // do nothing if there is not enough buffered data for a complete
47372          // PES header
47373
47374          if (!stream.data.length || stream.size < 9) {
47375            return;
47376          }
47377          event.trackId = stream.data[0].pid; // reassemble the packet
47378
47379          for (i = 0; i < stream.data.length; i++) {
47380            fragment = stream.data[i];
47381            packetData.set(fragment.data, offset);
47382            offset += fragment.data.byteLength;
47383          } // parse assembled packet's PES header
47384
47385          parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
47386          // check that there is enough stream data to fill the packet
47387
47388          packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
47389
47390          if (forceFlush || packetFlushable) {
47391            stream.size = 0;
47392            stream.data.length = 0;
47393          } // only emit packets that are complete. this is to avoid assembling
47394          // incomplete PES packets due to poor segmentation
47395
47396          if (packetFlushable) {
47397            self.trigger('data', event);
47398          }
47399        };
47400      ElementaryStream.prototype.init.call(this);
47401      /**
47402       * Identifies M2TS packet types and parses PES packets using metadata
47403       * parsed from the PMT
47404       **/
47405
47406      this.push = function (data) {
47407        ({
47408          pat: function () {// we have to wait for the PMT to arrive as well before we
47409            // have any meaningful metadata
47410          },
47411          pes: function () {
47412            var stream, streamType;
47413            switch (data.streamType) {
47414              case StreamTypes$2.H264_STREAM_TYPE:
47415                stream = video;
47416                streamType = 'video';
47417                break;
47418              case StreamTypes$2.ADTS_STREAM_TYPE:
47419                stream = audio;
47420                streamType = 'audio';
47421                break;
47422              case StreamTypes$2.METADATA_STREAM_TYPE:
47423                stream = timedMetadata;
47424                streamType = 'timed-metadata';
47425                break;
47426              default:
47427                // ignore unknown stream types
47428                return;
47429            } // if a new packet is starting, we can flush the completed
47430            // packet
47431
47432            if (data.payloadUnitStartIndicator) {
47433              flushStream(stream, streamType, true);
47434            } // buffer this fragment until we are sure we've received the
47435            // complete payload
47436
47437            stream.data.push(data);
47438            stream.size += data.data.byteLength;
47439          },
47440          pmt: function () {
47441            var event = {
47442              type: 'metadata',
47443              tracks: []
47444            };
47445            programMapTable = data.programMapTable; // translate audio and video streams to tracks
47446
47447            if (programMapTable.video !== null) {
47448              event.tracks.push({
47449                timelineStartInfo: {
47450                  baseMediaDecodeTime: 0
47451                },
47452                id: +programMapTable.video,
47453                codec: 'avc',
47454                type: 'video'
47455              });
47456            }
47457            if (programMapTable.audio !== null) {
47458              event.tracks.push({
47459                timelineStartInfo: {
47460                  baseMediaDecodeTime: 0
47461                },
47462                id: +programMapTable.audio,
47463                codec: 'adts',
47464                type: 'audio'
47465              });
47466            }
47467            segmentHadPmt = true;
47468            self.trigger('data', event);
47469          }
47470        })[data.type]();
47471      };
47472      this.reset = function () {
47473        video.size = 0;
47474        video.data.length = 0;
47475        audio.size = 0;
47476        audio.data.length = 0;
47477        this.trigger('reset');
47478      };
47479      /**
47480       * Flush any remaining input. Video PES packets may be of variable
47481       * length. Normally, the start of a new video packet can trigger the
47482       * finalization of the previous packet. That is not possible if no
47483       * more video is forthcoming, however. In that case, some other
47484       * mechanism (like the end of the file) has to be employed. When it is
47485       * clear that no additional data is forthcoming, calling this method
47486       * will flush the buffered packets.
47487       */
47488
47489      this.flushStreams_ = function () {
47490        // !!THIS ORDER IS IMPORTANT!!
47491        // video first then audio
47492        flushStream(video, 'video');
47493        flushStream(audio, 'audio');
47494        flushStream(timedMetadata, 'timed-metadata');
47495      };
47496      this.flush = function () {
47497        // if on flush we haven't had a pmt emitted
47498        // and we have a pmt to emit. emit the pmt
47499        // so that we trigger a trackinfo downstream.
47500        if (!segmentHadPmt && programMapTable) {
47501          var pmt = {
47502            type: 'metadata',
47503            tracks: []
47504          }; // translate audio and video streams to tracks
47505
47506          if (programMapTable.video !== null) {
47507            pmt.tracks.push({
47508              timelineStartInfo: {
47509                baseMediaDecodeTime: 0
47510              },
47511              id: +programMapTable.video,
47512              codec: 'avc',
47513              type: 'video'
47514            });
47515          }
47516          if (programMapTable.audio !== null) {
47517            pmt.tracks.push({
47518              timelineStartInfo: {
47519                baseMediaDecodeTime: 0
47520              },
47521              id: +programMapTable.audio,
47522              codec: 'adts',
47523              type: 'audio'
47524            });
47525          }
47526          self.trigger('data', pmt);
47527        }
47528        segmentHadPmt = false;
47529        this.flushStreams_();
47530        this.trigger('done');
47531      };
47532    };
47533    ElementaryStream.prototype = new Stream$4();
47534    var m2ts$1 = {
47535      PAT_PID: 0x0000,
47536      MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
47537      TransportPacketStream: TransportPacketStream,
47538      TransportParseStream: TransportParseStream,
47539      ElementaryStream: ElementaryStream,
47540      TimestampRolloverStream: TimestampRolloverStream,
47541      CaptionStream: CaptionStream$1.CaptionStream,
47542      Cea608Stream: CaptionStream$1.Cea608Stream,
47543      Cea708Stream: CaptionStream$1.Cea708Stream,
47544      MetadataStream: metadataStream
47545    };
47546    for (var type in StreamTypes$2) {
47547      if (StreamTypes$2.hasOwnProperty(type)) {
47548        m2ts$1[type] = StreamTypes$2[type];
47549      }
47550    }
47551    var m2ts_1 = m2ts$1;
47552    /**
47553     * mux.js
47554     *
47555     * Copyright (c) Brightcove
47556     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47557     */
47558
47559    var Stream$3 = stream;
47560    var ONE_SECOND_IN_TS$2 = clock$2.ONE_SECOND_IN_TS;
47561    var AdtsStream$1;
47562    var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
47563    /*
47564     * Accepts a ElementaryStream and emits data events with parsed
47565     * AAC Audio Frames of the individual packets. Input audio in ADTS
47566     * format is unpacked and re-emitted as AAC frames.
47567     *
47568     * @see http://wiki.multimedia.cx/index.php?title=ADTS
47569     * @see http://wiki.multimedia.cx/?title=Understanding_AAC
47570     */
47571
47572    AdtsStream$1 = function (handlePartialSegments) {
47573      var buffer,
47574        frameNum = 0;
47575      AdtsStream$1.prototype.init.call(this);
47576      this.skipWarn_ = function (start, end) {
47577        this.trigger('log', {
47578          level: 'warn',
47579          message: `adts skiping bytes ${start} to ${end} in frame ${frameNum} outside syncword`
47580        });
47581      };
47582      this.push = function (packet) {
47583        var i = 0,
47584          frameLength,
47585          protectionSkipBytes,
47586          oldBuffer,
47587          sampleCount,
47588          adtsFrameDuration;
47589        if (!handlePartialSegments) {
47590          frameNum = 0;
47591        }
47592        if (packet.type !== 'audio') {
47593          // ignore non-audio data
47594          return;
47595        } // Prepend any data in the buffer to the input data so that we can parse
47596        // aac frames the cross a PES packet boundary
47597
47598        if (buffer && buffer.length) {
47599          oldBuffer = buffer;
47600          buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
47601          buffer.set(oldBuffer);
47602          buffer.set(packet.data, oldBuffer.byteLength);
47603        } else {
47604          buffer = packet.data;
47605        } // unpack any ADTS frames which have been fully received
47606        // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
47607
47608        var skip; // We use i + 7 here because we want to be able to parse the entire header.
47609        // If we don't have enough bytes to do that, then we definitely won't have a full frame.
47610
47611        while (i + 7 < buffer.length) {
47612          // Look for the start of an ADTS header..
47613          if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
47614            if (typeof skip !== 'number') {
47615              skip = i;
47616            } // If a valid header was not found,  jump one forward and attempt to
47617            // find a valid ADTS header starting at the next byte
47618
47619            i++;
47620            continue;
47621          }
47622          if (typeof skip === 'number') {
47623            this.skipWarn_(skip, i);
47624            skip = null;
47625          } // The protection skip bit tells us if we have 2 bytes of CRC data at the
47626          // end of the ADTS header
47627
47628          protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
47629          // end of the sync sequence
47630          // NOTE: frame length includes the size of the header
47631
47632          frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
47633          sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
47634          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,
47635          // then we have to wait for more data
47636
47637          if (buffer.byteLength - i < frameLength) {
47638            break;
47639          } // Otherwise, deliver the complete AAC frame
47640
47641          this.trigger('data', {
47642            pts: packet.pts + frameNum * adtsFrameDuration,
47643            dts: packet.dts + frameNum * adtsFrameDuration,
47644            sampleCount: sampleCount,
47645            audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
47646            channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
47647            samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
47648            samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
47649            // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
47650            samplesize: 16,
47651            // data is the frame without it's header
47652            data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
47653          });
47654          frameNum++;
47655          i += frameLength;
47656        }
47657        if (typeof skip === 'number') {
47658          this.skipWarn_(skip, i);
47659          skip = null;
47660        } // remove processed bytes from the buffer.
47661
47662        buffer = buffer.subarray(i);
47663      };
47664      this.flush = function () {
47665        frameNum = 0;
47666        this.trigger('done');
47667      };
47668      this.reset = function () {
47669        buffer = void 0;
47670        this.trigger('reset');
47671      };
47672      this.endTimeline = function () {
47673        buffer = void 0;
47674        this.trigger('endedtimeline');
47675      };
47676    };
47677    AdtsStream$1.prototype = new Stream$3();
47678    var adts = AdtsStream$1;
47679    /**
47680     * mux.js
47681     *
47682     * Copyright (c) Brightcove
47683     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47684     */
47685
47686    var ExpGolomb$1;
47687    /**
47688     * Parser for exponential Golomb codes, a variable-bitwidth number encoding
47689     * scheme used by h264.
47690     */
47691
47692    ExpGolomb$1 = function (workingData) {
47693      var
47694        // the number of bytes left to examine in workingData
47695        workingBytesAvailable = workingData.byteLength,
47696        // the current word being examined
47697        workingWord = 0,
47698        // :uint
47699        // the number of bits left to examine in the current word
47700        workingBitsAvailable = 0; // :uint;
47701      // ():uint
47702
47703      this.length = function () {
47704        return 8 * workingBytesAvailable;
47705      }; // ():uint
47706
47707      this.bitsAvailable = function () {
47708        return 8 * workingBytesAvailable + workingBitsAvailable;
47709      }; // ():void
47710
47711      this.loadWord = function () {
47712        var position = workingData.byteLength - workingBytesAvailable,
47713          workingBytes = new Uint8Array(4),
47714          availableBytes = Math.min(4, workingBytesAvailable);
47715        if (availableBytes === 0) {
47716          throw new Error('no bytes available');
47717        }
47718        workingBytes.set(workingData.subarray(position, position + availableBytes));
47719        workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
47720
47721        workingBitsAvailable = availableBytes * 8;
47722        workingBytesAvailable -= availableBytes;
47723      }; // (count:int):void
47724
47725      this.skipBits = function (count) {
47726        var skipBytes; // :int
47727
47728        if (workingBitsAvailable > count) {
47729          workingWord <<= count;
47730          workingBitsAvailable -= count;
47731        } else {
47732          count -= workingBitsAvailable;
47733          skipBytes = Math.floor(count / 8);
47734          count -= skipBytes * 8;
47735          workingBytesAvailable -= skipBytes;
47736          this.loadWord();
47737          workingWord <<= count;
47738          workingBitsAvailable -= count;
47739        }
47740      }; // (size:int):uint
47741
47742      this.readBits = function (size) {
47743        var bits = Math.min(workingBitsAvailable, size),
47744          // :uint
47745          valu = workingWord >>> 32 - bits; // :uint
47746        // if size > 31, handle error
47747
47748        workingBitsAvailable -= bits;
47749        if (workingBitsAvailable > 0) {
47750          workingWord <<= bits;
47751        } else if (workingBytesAvailable > 0) {
47752          this.loadWord();
47753        }
47754        bits = size - bits;
47755        if (bits > 0) {
47756          return valu << bits | this.readBits(bits);
47757        }
47758        return valu;
47759      }; // ():uint
47760
47761      this.skipLeadingZeros = function () {
47762        var leadingZeroCount; // :uint
47763
47764        for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
47765          if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
47766            // the first bit of working word is 1
47767            workingWord <<= leadingZeroCount;
47768            workingBitsAvailable -= leadingZeroCount;
47769            return leadingZeroCount;
47770          }
47771        } // we exhausted workingWord and still have not found a 1
47772
47773        this.loadWord();
47774        return leadingZeroCount + this.skipLeadingZeros();
47775      }; // ():void
47776
47777      this.skipUnsignedExpGolomb = function () {
47778        this.skipBits(1 + this.skipLeadingZeros());
47779      }; // ():void
47780
47781      this.skipExpGolomb = function () {
47782        this.skipBits(1 + this.skipLeadingZeros());
47783      }; // ():uint
47784
47785      this.readUnsignedExpGolomb = function () {
47786        var clz = this.skipLeadingZeros(); // :uint
47787
47788        return this.readBits(clz + 1) - 1;
47789      }; // ():int
47790
47791      this.readExpGolomb = function () {
47792        var valu = this.readUnsignedExpGolomb(); // :int
47793
47794        if (0x01 & valu) {
47795          // the number is odd if the low order bit is set
47796          return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
47797        }
47798
47799        return -1 * (valu >>> 1); // divide by two then make it negative
47800      }; // Some convenience functions
47801      // :Boolean
47802
47803      this.readBoolean = function () {
47804        return this.readBits(1) === 1;
47805      }; // ():int
47806
47807      this.readUnsignedByte = function () {
47808        return this.readBits(8);
47809      };
47810      this.loadWord();
47811    };
47812    var expGolomb = ExpGolomb$1;
47813    /**
47814     * mux.js
47815     *
47816     * Copyright (c) Brightcove
47817     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47818     */
47819
47820    var Stream$2 = stream;
47821    var ExpGolomb = expGolomb;
47822    var H264Stream$1, NalByteStream;
47823    var PROFILES_WITH_OPTIONAL_SPS_DATA;
47824    /**
47825     * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
47826     */
47827
47828    NalByteStream = function () {
47829      var syncPoint = 0,
47830        i,
47831        buffer;
47832      NalByteStream.prototype.init.call(this);
47833      /*
47834       * Scans a byte stream and triggers a data event with the NAL units found.
47835       * @param {Object} data Event received from H264Stream
47836       * @param {Uint8Array} data.data The h264 byte stream to be scanned
47837       *
47838       * @see H264Stream.push
47839       */
47840
47841      this.push = function (data) {
47842        var swapBuffer;
47843        if (!buffer) {
47844          buffer = data.data;
47845        } else {
47846          swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
47847          swapBuffer.set(buffer);
47848          swapBuffer.set(data.data, buffer.byteLength);
47849          buffer = swapBuffer;
47850        }
47851        var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
47852        // scan for NAL unit boundaries
47853        // a match looks like this:
47854        // 0 0 1 .. NAL .. 0 0 1
47855        // ^ sync point        ^ i
47856        // or this:
47857        // 0 0 1 .. NAL .. 0 0 0
47858        // ^ sync point        ^ i
47859        // advance the sync point to a NAL start, if necessary
47860
47861        for (; syncPoint < len - 3; syncPoint++) {
47862          if (buffer[syncPoint + 2] === 1) {
47863            // the sync point is properly aligned
47864            i = syncPoint + 5;
47865            break;
47866          }
47867        }
47868        while (i < len) {
47869          // look at the current byte to determine if we've hit the end of
47870          // a NAL unit boundary
47871          switch (buffer[i]) {
47872            case 0:
47873              // skip past non-sync sequences
47874              if (buffer[i - 1] !== 0) {
47875                i += 2;
47876                break;
47877              } else if (buffer[i - 2] !== 0) {
47878                i++;
47879                break;
47880              } // deliver the NAL unit if it isn't empty
47881
47882              if (syncPoint + 3 !== i - 2) {
47883                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47884              } // drop trailing zeroes
47885
47886              do {
47887                i++;
47888              } while (buffer[i] !== 1 && i < len);
47889              syncPoint = i - 2;
47890              i += 3;
47891              break;
47892            case 1:
47893              // skip past non-sync sequences
47894              if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
47895                i += 3;
47896                break;
47897              } // deliver the NAL unit
47898
47899              this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47900              syncPoint = i - 2;
47901              i += 3;
47902              break;
47903            default:
47904              // the current byte isn't a one or zero, so it cannot be part
47905              // of a sync sequence
47906              i += 3;
47907              break;
47908          }
47909        } // filter out the NAL units that were delivered
47910
47911        buffer = buffer.subarray(syncPoint);
47912        i -= syncPoint;
47913        syncPoint = 0;
47914      };
47915      this.reset = function () {
47916        buffer = null;
47917        syncPoint = 0;
47918        this.trigger('reset');
47919      };
47920      this.flush = function () {
47921        // deliver the last buffered NAL unit
47922        if (buffer && buffer.byteLength > 3) {
47923          this.trigger('data', buffer.subarray(syncPoint + 3));
47924        } // reset the stream state
47925
47926        buffer = null;
47927        syncPoint = 0;
47928        this.trigger('done');
47929      };
47930      this.endTimeline = function () {
47931        this.flush();
47932        this.trigger('endedtimeline');
47933      };
47934    };
47935    NalByteStream.prototype = new Stream$2(); // values of profile_idc that indicate additional fields are included in the SPS
47936    // see Recommendation ITU-T H.264 (4/2013),
47937    // 7.3.2.1.1 Sequence parameter set data syntax
47938
47939    PROFILES_WITH_OPTIONAL_SPS_DATA = {
47940      100: true,
47941      110: true,
47942      122: true,
47943      244: true,
47944      44: true,
47945      83: true,
47946      86: true,
47947      118: true,
47948      128: true,
47949      // TODO: the three profiles below don't
47950      // appear to have sps data in the specificiation anymore?
47951      138: true,
47952      139: true,
47953      134: true
47954    };
47955    /**
47956     * Accepts input from a ElementaryStream and produces H.264 NAL unit data
47957     * events.
47958     */
47959
47960    H264Stream$1 = function () {
47961      var nalByteStream = new NalByteStream(),
47962        self,
47963        trackId,
47964        currentPts,
47965        currentDts,
47966        discardEmulationPreventionBytes,
47967        readSequenceParameterSet,
47968        skipScalingList;
47969      H264Stream$1.prototype.init.call(this);
47970      self = this;
47971      /*
47972       * Pushes a packet from a stream onto the NalByteStream
47973       *
47974       * @param {Object} packet - A packet received from a stream
47975       * @param {Uint8Array} packet.data - The raw bytes of the packet
47976       * @param {Number} packet.dts - Decode timestamp of the packet
47977       * @param {Number} packet.pts - Presentation timestamp of the packet
47978       * @param {Number} packet.trackId - The id of the h264 track this packet came from
47979       * @param {('video'|'audio')} packet.type - The type of packet
47980       *
47981       */
47982
47983      this.push = function (packet) {
47984        if (packet.type !== 'video') {
47985          return;
47986        }
47987        trackId = packet.trackId;
47988        currentPts = packet.pts;
47989        currentDts = packet.dts;
47990        nalByteStream.push(packet);
47991      };
47992      /*
47993       * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
47994       * for the NALUs to the next stream component.
47995       * Also, preprocess caption and sequence parameter NALUs.
47996       *
47997       * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
47998       * @see NalByteStream.push
47999       */
48000
48001      nalByteStream.on('data', function (data) {
48002        var event = {
48003          trackId: trackId,
48004          pts: currentPts,
48005          dts: currentDts,
48006          data: data,
48007          nalUnitTypeCode: data[0] & 0x1f
48008        };
48009        switch (event.nalUnitTypeCode) {
48010          case 0x05:
48011            event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
48012            break;
48013          case 0x06:
48014            event.nalUnitType = 'sei_rbsp';
48015            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
48016            break;
48017          case 0x07:
48018            event.nalUnitType = 'seq_parameter_set_rbsp';
48019            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
48020            event.config = readSequenceParameterSet(event.escapedRBSP);
48021            break;
48022          case 0x08:
48023            event.nalUnitType = 'pic_parameter_set_rbsp';
48024            break;
48025          case 0x09:
48026            event.nalUnitType = 'access_unit_delimiter_rbsp';
48027            break;
48028        } // This triggers data on the H264Stream
48029
48030        self.trigger('data', event);
48031      });
48032      nalByteStream.on('done', function () {
48033        self.trigger('done');
48034      });
48035      nalByteStream.on('partialdone', function () {
48036        self.trigger('partialdone');
48037      });
48038      nalByteStream.on('reset', function () {
48039        self.trigger('reset');
48040      });
48041      nalByteStream.on('endedtimeline', function () {
48042        self.trigger('endedtimeline');
48043      });
48044      this.flush = function () {
48045        nalByteStream.flush();
48046      };
48047      this.partialFlush = function () {
48048        nalByteStream.partialFlush();
48049      };
48050      this.reset = function () {
48051        nalByteStream.reset();
48052      };
48053      this.endTimeline = function () {
48054        nalByteStream.endTimeline();
48055      };
48056      /**
48057       * Advance the ExpGolomb decoder past a scaling list. The scaling
48058       * list is optionally transmitted as part of a sequence parameter
48059       * set and is not relevant to transmuxing.
48060       * @param count {number} the number of entries in this scaling list
48061       * @param expGolombDecoder {object} an ExpGolomb pointed to the
48062       * start of a scaling list
48063       * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
48064       */
48065
48066      skipScalingList = function (count, expGolombDecoder) {
48067        var lastScale = 8,
48068          nextScale = 8,
48069          j,
48070          deltaScale;
48071        for (j = 0; j < count; j++) {
48072          if (nextScale !== 0) {
48073            deltaScale = expGolombDecoder.readExpGolomb();
48074            nextScale = (lastScale + deltaScale + 256) % 256;
48075          }
48076          lastScale = nextScale === 0 ? lastScale : nextScale;
48077        }
48078      };
48079      /**
48080       * Expunge any "Emulation Prevention" bytes from a "Raw Byte
48081       * Sequence Payload"
48082       * @param data {Uint8Array} the bytes of a RBSP from a NAL
48083       * unit
48084       * @return {Uint8Array} the RBSP without any Emulation
48085       * Prevention Bytes
48086       */
48087
48088      discardEmulationPreventionBytes = function (data) {
48089        var length = data.byteLength,
48090          emulationPreventionBytesPositions = [],
48091          i = 1,
48092          newLength,
48093          newData; // Find all `Emulation Prevention Bytes`
48094
48095        while (i < length - 2) {
48096          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
48097            emulationPreventionBytesPositions.push(i + 2);
48098            i += 2;
48099          } else {
48100            i++;
48101          }
48102        } // If no Emulation Prevention Bytes were found just return the original
48103        // array
48104
48105        if (emulationPreventionBytesPositions.length === 0) {
48106          return data;
48107        } // Create a new array to hold the NAL unit data
48108
48109        newLength = length - emulationPreventionBytesPositions.length;
48110        newData = new Uint8Array(newLength);
48111        var sourceIndex = 0;
48112        for (i = 0; i < newLength; sourceIndex++, i++) {
48113          if (sourceIndex === emulationPreventionBytesPositions[0]) {
48114            // Skip this byte
48115            sourceIndex++; // Remove this position index
48116
48117            emulationPreventionBytesPositions.shift();
48118          }
48119          newData[i] = data[sourceIndex];
48120        }
48121        return newData;
48122      };
48123      /**
48124       * Read a sequence parameter set and return some interesting video
48125       * properties. A sequence parameter set is the H264 metadata that
48126       * describes the properties of upcoming video frames.
48127       * @param data {Uint8Array} the bytes of a sequence parameter set
48128       * @return {object} an object with configuration parsed from the
48129       * sequence parameter set, including the dimensions of the
48130       * associated video frames.
48131       */
48132
48133      readSequenceParameterSet = function (data) {
48134        var frameCropLeftOffset = 0,
48135          frameCropRightOffset = 0,
48136          frameCropTopOffset = 0,
48137          frameCropBottomOffset = 0,
48138          expGolombDecoder,
48139          profileIdc,
48140          levelIdc,
48141          profileCompatibility,
48142          chromaFormatIdc,
48143          picOrderCntType,
48144          numRefFramesInPicOrderCntCycle,
48145          picWidthInMbsMinus1,
48146          picHeightInMapUnitsMinus1,
48147          frameMbsOnlyFlag,
48148          scalingListCount,
48149          sarRatio = [1, 1],
48150          aspectRatioIdc,
48151          i;
48152        expGolombDecoder = new ExpGolomb(data);
48153        profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
48154
48155        profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
48156
48157        levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
48158
48159        expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
48160        // some profiles have more optional data we don't need
48161
48162        if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
48163          chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
48164          if (chromaFormatIdc === 3) {
48165            expGolombDecoder.skipBits(1); // separate_colour_plane_flag
48166          }
vendor: 10,354 bytes, lines 48167-48435
48167
48168          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
48169
48170          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
48171
48172          expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
48173
48174          if (expGolombDecoder.readBoolean()) {
48175            // seq_scaling_matrix_present_flag
48176            scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
48177            for (i = 0; i < scalingListCount; i++) {
48178              if (expGolombDecoder.readBoolean()) {
48179                // seq_scaling_list_present_flag[ i ]
48180                if (i < 6) {
48181                  skipScalingList(16, expGolombDecoder);
48182                } else {
48183                  skipScalingList(64, expGolombDecoder);
48184                }
48185              }
48186            }
48187          }
48188        }
48189        expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
48190
48191        picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
48192        if (picOrderCntType === 0) {
48193          expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
48194        } else if (picOrderCntType === 1) {
48195          expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
48196
48197          expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
48198
48199          expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
48200
48201          numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
48202          for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
48203            expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
48204          }
48205        }
48206
48207        expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
48208
48209        expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
48210
48211        picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48212        picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48213        frameMbsOnlyFlag = expGolombDecoder.readBits(1);
48214        if (frameMbsOnlyFlag === 0) {
48215          expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
48216        }
48217
48218        expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
48219
48220        if (expGolombDecoder.readBoolean()) {
48221          // frame_cropping_flag
48222          frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
48223          frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
48224          frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
48225          frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
48226        }
48227        if (expGolombDecoder.readBoolean()) {
48228          // vui_parameters_present_flag
48229          if (expGolombDecoder.readBoolean()) {
48230            // aspect_ratio_info_present_flag
48231            aspectRatioIdc = expGolombDecoder.readUnsignedByte();
48232            switch (aspectRatioIdc) {
48233              case 1:
48234                sarRatio = [1, 1];
48235                break;
48236              case 2:
48237                sarRatio = [12, 11];
48238                break;
48239              case 3:
48240                sarRatio = [10, 11];
48241                break;
48242              case 4:
48243                sarRatio = [16, 11];
48244                break;
48245              case 5:
48246                sarRatio = [40, 33];
48247                break;
48248              case 6:
48249                sarRatio = [24, 11];
48250                break;
48251              case 7:
48252                sarRatio = [20, 11];
48253                break;
48254              case 8:
48255                sarRatio = [32, 11];
48256                break;
48257              case 9:
48258                sarRatio = [80, 33];
48259                break;
48260              case 10:
48261                sarRatio = [18, 11];
48262                break;
48263              case 11:
48264                sarRatio = [15, 11];
48265                break;
48266              case 12:
48267                sarRatio = [64, 33];
48268                break;
48269              case 13:
48270                sarRatio = [160, 99];
48271                break;
48272              case 14:
48273                sarRatio = [4, 3];
48274                break;
48275              case 15:
48276                sarRatio = [3, 2];
48277                break;
48278              case 16:
48279                sarRatio = [2, 1];
48280                break;
48281              case 255:
48282                {
48283                  sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
48284                  break;
48285                }
48286            }
48287            if (sarRatio) {
48288              sarRatio[0] / sarRatio[1];
48289            }
48290          }
48291        }
48292        return {
48293          profileIdc: profileIdc,
48294          levelIdc: levelIdc,
48295          profileCompatibility: profileCompatibility,
48296          width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
48297          height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
48298          // sar is sample aspect ratio
48299          sarRatio: sarRatio
48300        };
48301      };
48302    };
48303    H264Stream$1.prototype = new Stream$2();
48304    var h264 = {
48305      H264Stream: H264Stream$1,
48306      NalByteStream: NalByteStream
48307    };
48308    /**
48309     * mux.js
48310     *
48311     * Copyright (c) Brightcove
48312     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48313     *
48314     * Utilities to detect basic properties and metadata about Aac data.
48315     */
48316
48317    var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
48318    var parseId3TagSize = function (header, byteIndex) {
48319      var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
48320        flags = header[byteIndex + 5],
48321        footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
48322
48323      returnSize = returnSize >= 0 ? returnSize : 0;
48324      if (footerPresent) {
48325        return returnSize + 20;
48326      }
48327      return returnSize + 10;
48328    };
48329    var getId3Offset = function (data, offset) {
48330      if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
48331        return offset;
48332      }
48333      offset += parseId3TagSize(data, offset);
48334      return getId3Offset(data, offset);
48335    }; // TODO: use vhs-utils
48336
48337    var isLikelyAacData$1 = function (data) {
48338      var offset = getId3Offset(data, 0);
48339      return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 &&
48340      // verify that the 2 layer bits are 0, aka this
48341      // is not mp3 data but aac data.
48342      (data[offset + 1] & 0x16) === 0x10;
48343    };
48344    var parseSyncSafeInteger = function (data) {
48345      return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
48346    }; // return a percent-encoded representation of the specified byte range
48347    // @see http://en.wikipedia.org/wiki/Percent-encoding
48348
48349    var percentEncode = function (bytes, start, end) {
48350      var i,
48351        result = '';
48352      for (i = start; i < end; i++) {
48353        result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
48354      }
48355      return result;
48356    }; // return the string representation of the specified byte range,
48357    // interpreted as ISO-8859-1.
48358
48359    var parseIso88591 = function (bytes, start, end) {
48360      return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
48361    };
48362
48363    var parseAdtsSize = function (header, byteIndex) {
48364      var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
48365        middle = header[byteIndex + 4] << 3,
48366        highTwo = header[byteIndex + 3] & 0x3 << 11;
48367      return highTwo | middle | lowThree;
48368    };
48369    var parseType$4 = function (header, byteIndex) {
48370      if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
48371        return 'timed-metadata';
48372      } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
48373        return 'audio';
48374      }
48375      return null;
48376    };
48377    var parseSampleRate = function (packet) {
48378      var i = 0;
48379      while (i + 5 < packet.length) {
48380        if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
48381          // If a valid header was not found,  jump one forward and attempt to
48382          // find a valid ADTS header starting at the next byte
48383          i++;
48384          continue;
48385        }
48386        return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
48387      }
48388      return null;
48389    };
48390    var parseAacTimestamp = function (packet) {
48391      var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
48392
48393      frameStart = 10;
48394      if (packet[5] & 0x40) {
48395        // advance the frame start past the extended header
48396        frameStart += 4; // header size field
48397
48398        frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
48399      } // parse one or more ID3 frames
48400      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
48401
48402      do {
48403        // determine the number of bytes in this frame
48404        frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
48405        if (frameSize < 1) {
48406          return null;
48407        }
48408        frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
48409        if (frameHeader === 'PRIV') {
48410          frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
48411          for (var i = 0; i < frame.byteLength; i++) {
48412            if (frame[i] === 0) {
48413              var owner = parseIso88591(frame, 0, i);
48414              if (owner === 'com.apple.streaming.transportStreamTimestamp') {
48415                var d = frame.subarray(i + 1);
48416                var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
48417                size *= 4;
48418                size += d[7] & 0x03;
48419                return size;
48420              }
48421              break;
48422            }
48423          }
48424        }
48425        frameStart += 10; // advance past the frame header
48426
48427        frameStart += frameSize; // advance past the frame body
48428      } while (frameStart < packet.byteLength);
48429      return null;
48430    };
48431    var utils = {
48432      isLikelyAacData: isLikelyAacData$1,
48433      parseId3TagSize: parseId3TagSize,
48434      parseAdtsSize: parseAdtsSize,
48435      parseType: parseType$4
vendor: 17,397 bytes, lines 48435-48865
48435,
48436      parseSampleRate: parseSampleRate,
48437      parseAacTimestamp: parseAacTimestamp
48438    };
48439    /**
48440     * mux.js
48441     *
48442     * Copyright (c) Brightcove
48443     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48444     *
48445     * A stream-based aac to mp4 converter. This utility can be used to
48446     * deliver mp4s to a SourceBuffer on platforms that support native
48447     * Media Source Extensions.
48448     */
48449
48450    var Stream$1 = stream;
48451    var aacUtils = utils; // Constants
48452
48453    var AacStream$1;
48454    /**
48455     * Splits an incoming stream of binary data into ADTS and ID3 Frames.
48456     */
48457
48458    AacStream$1 = function () {
48459      var everything = new Uint8Array(),
48460        timeStamp = 0;
48461      AacStream$1.prototype.init.call(this);
48462      this.setTimestamp = function (timestamp) {
48463        timeStamp = timestamp;
48464      };
48465      this.push = function (bytes) {
48466        var frameSize = 0,
48467          byteIndex = 0,
48468          bytesLeft,
48469          chunk,
48470          packet,
48471          tempLength; // If there are bytes remaining from the last segment, prepend them to the
48472        // bytes that were pushed in
48473
48474        if (everything.length) {
48475          tempLength = everything.length;
48476          everything = new Uint8Array(bytes.byteLength + tempLength);
48477          everything.set(everything.subarray(0, tempLength));
48478          everything.set(bytes, tempLength);
48479        } else {
48480          everything = bytes;
48481        }
48482        while (everything.length - byteIndex >= 3) {
48483          if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
48484            // Exit early because we don't have enough to parse
48485            // the ID3 tag header
48486            if (everything.length - byteIndex < 10) {
48487              break;
48488            } // check framesize
48489
48490            frameSize = aacUtils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48491            // to emit a full packet
48492            // Add to byteIndex to support multiple ID3 tags in sequence
48493
48494            if (byteIndex + frameSize > everything.length) {
48495              break;
48496            }
48497            chunk = {
48498              type: 'timed-metadata',
48499              data: everything.subarray(byteIndex, byteIndex + frameSize)
48500            };
48501            this.trigger('data', chunk);
48502            byteIndex += frameSize;
48503            continue;
48504          } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
48505            // Exit early because we don't have enough to parse
48506            // the ADTS frame header
48507            if (everything.length - byteIndex < 7) {
48508              break;
48509            }
48510            frameSize = aacUtils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48511            // to emit a full packet
48512
48513            if (byteIndex + frameSize > everything.length) {
48514              break;
48515            }
48516            packet = {
48517              type: 'audio',
48518              data: everything.subarray(byteIndex, byteIndex + frameSize),
48519              pts: timeStamp,
48520              dts: timeStamp
48521            };
48522            this.trigger('data', packet);
48523            byteIndex += frameSize;
48524            continue;
48525          }
48526          byteIndex++;
48527        }
48528        bytesLeft = everything.length - byteIndex;
48529        if (bytesLeft > 0) {
48530          everything = everything.subarray(byteIndex);
48531        } else {
48532          everything = new Uint8Array();
48533        }
48534      };
48535      this.reset = function () {
48536        everything = new Uint8Array();
48537        this.trigger('reset');
48538      };
48539      this.endTimeline = function () {
48540        everything = new Uint8Array();
48541        this.trigger('endedtimeline');
48542      };
48543    };
48544    AacStream$1.prototype = new Stream$1();
48545    var aac = AacStream$1;
48546    var AUDIO_PROPERTIES$1 = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
48547    var audioProperties = AUDIO_PROPERTIES$1;
48548    var VIDEO_PROPERTIES$1 = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
48549    var videoProperties = VIDEO_PROPERTIES$1;
48550    /**
48551     * mux.js
48552     *
48553     * Copyright (c) Brightcove
48554     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48555     *
48556     * A stream-based mp2t to mp4 converter. This utility can be used to
48557     * deliver mp4s to a SourceBuffer on platforms that support native
48558     * Media Source Extensions.
48559     */
48560
48561    var Stream = stream;
48562    var mp4 = mp4Generator;
48563    var frameUtils = frameUtils$1;
48564    var audioFrameUtils = audioFrameUtils$1;
48565    var trackDecodeInfo = trackDecodeInfo$1;
48566    var m2ts = m2ts_1;
48567    var clock = clock$2;
48568    var AdtsStream = adts;
48569    var H264Stream = h264.H264Stream;
48570    var AacStream = aac;
48571    var isLikelyAacData = utils.isLikelyAacData;
48572    var ONE_SECOND_IN_TS$1 = clock$2.ONE_SECOND_IN_TS;
48573    var AUDIO_PROPERTIES = audioProperties;
48574    var VIDEO_PROPERTIES = videoProperties; // object types
48575
48576    var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
48577    var retriggerForStream = function (key, event) {
48578      event.stream = key;
48579      this.trigger('log', event);
48580    };
48581    var addPipelineLogRetriggers = function (transmuxer, pipeline) {
48582      var keys = Object.keys(pipeline);
48583      for (var i = 0; i < keys.length; i++) {
48584        var key = keys[i]; // skip non-stream keys and headOfPipeline
48585        // which is just a duplicate
48586
48587        if (key === 'headOfPipeline' || !pipeline[key].on) {
48588          continue;
48589        }
48590        pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
48591      }
48592    };
48593    /**
48594     * Compare two arrays (even typed) for same-ness
48595     */
48596
48597    var arrayEquals = function (a, b) {
48598      var i;
48599      if (a.length !== b.length) {
48600        return false;
48601      } // compare the value of each element in the array
48602
48603      for (i = 0; i < a.length; i++) {
48604        if (a[i] !== b[i]) {
48605          return false;
48606        }
48607      }
48608      return true;
48609    };
48610    var generateSegmentTimingInfo = function (baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
48611      var ptsOffsetFromDts = startPts - startDts,
48612        decodeDuration = endDts - startDts,
48613        presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
48614      // however, the player time values will reflect a start from the baseMediaDecodeTime.
48615      // In order to provide relevant values for the player times, base timing info on the
48616      // baseMediaDecodeTime and the DTS and PTS durations of the segment.
48617
48618      return {
48619        start: {
48620          dts: baseMediaDecodeTime,
48621          pts: baseMediaDecodeTime + ptsOffsetFromDts
48622        },
48623        end: {
48624          dts: baseMediaDecodeTime + decodeDuration,
48625          pts: baseMediaDecodeTime + presentationDuration
48626        },
48627        prependedContentDuration: prependedContentDuration,
48628        baseMediaDecodeTime: baseMediaDecodeTime
48629      };
48630    };
48631    /**
48632     * Constructs a single-track, ISO BMFF media segment from AAC data
48633     * events. The output of this stream can be fed to a SourceBuffer
48634     * configured with a suitable initialization segment.
48635     * @param track {object} track metadata configuration
48636     * @param options {object} transmuxer options object
48637     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48638     *        in the source; false to adjust the first segment to start at 0.
48639     */
48640
48641    AudioSegmentStream = function (track, options) {
48642      var adtsFrames = [],
48643        sequenceNumber,
48644        earliestAllowedDts = 0,
48645        audioAppendStartTs = 0,
48646        videoBaseMediaDecodeTime = Infinity;
48647      options = options || {};
48648      sequenceNumber = options.firstSequenceNumber || 0;
48649      AudioSegmentStream.prototype.init.call(this);
48650      this.push = function (data) {
48651        trackDecodeInfo.collectDtsInfo(track, data);
48652        if (track) {
48653          AUDIO_PROPERTIES.forEach(function (prop) {
48654            track[prop] = data[prop];
48655          });
48656        } // buffer audio data until end() is called
48657
48658        adtsFrames.push(data);
48659      };
48660      this.setEarliestDts = function (earliestDts) {
48661        earliestAllowedDts = earliestDts;
48662      };
48663      this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
48664        videoBaseMediaDecodeTime = baseMediaDecodeTime;
48665      };
48666      this.setAudioAppendStart = function (timestamp) {
48667        audioAppendStartTs = timestamp;
48668      };
48669      this.flush = function () {
48670        var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
48671
48672        if (adtsFrames.length === 0) {
48673          this.trigger('done', 'AudioSegmentStream');
48674          return;
48675        }
48676        frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
48677        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
48678
48679        videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
48680        // samples (that is, adts frames) in the audio data
48681
48682        track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
48683
48684        mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
48685        adtsFrames = [];
48686        moof = mp4.moof(sequenceNumber, [track]);
48687        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
48688
48689        sequenceNumber++;
48690        boxes.set(moof);
48691        boxes.set(mdat, moof.byteLength);
48692        trackDecodeInfo.clearDtsInfo(track);
48693        frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
48694        // tests) on adding the timingInfo event. However, it seems unlikely that there's a
48695        // valid use-case where an init segment/data should be triggered without associated
48696        // frames. Leaving for now, but should be looked into.
48697
48698        if (frames.length) {
48699          segmentDuration = frames.length * frameDuration;
48700          this.trigger('segmentTimingInfo', generateSegmentTimingInfo(
48701          // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
48702          // frame info is in video clock cycles. Convert to match expectation of
48703          // listeners (that all timestamps will be based on video clock cycles).
48704          clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
48705          // frame times are already in video clock, as is segment duration
48706          frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
48707          this.trigger('timingInfo', {
48708            start: frames[0].pts,
48709            end: frames[0].pts + segmentDuration
48710          });
48711        }
48712        this.trigger('data', {
48713          track: track,
48714          boxes: boxes
48715        });
48716        this.trigger('done', 'AudioSegmentStream');
48717      };
48718      this.reset = function () {
48719        trackDecodeInfo.clearDtsInfo(track);
48720        adtsFrames = [];
48721        this.trigger('reset');
48722      };
48723    };
48724    AudioSegmentStream.prototype = new Stream();
48725    /**
48726     * Constructs a single-track, ISO BMFF media segment from H264 data
48727     * events. The output of this stream can be fed to a SourceBuffer
48728     * configured with a suitable initialization segment.
48729     * @param track {object} track metadata configuration
48730     * @param options {object} transmuxer options object
48731     * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
48732     *        gopsToAlignWith list when attempting to align gop pts
48733     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48734     *        in the source; false to adjust the first segment to start at 0.
48735     */
48736
48737    VideoSegmentStream = function (track, options) {
48738      var sequenceNumber,
48739        nalUnits = [],
48740        gopsToAlignWith = [],
48741        config,
48742        pps;
48743      options = options || {};
48744      sequenceNumber = options.firstSequenceNumber || 0;
48745      VideoSegmentStream.prototype.init.call(this);
48746      delete track.minPTS;
48747      this.gopCache_ = [];
48748      /**
48749        * Constructs a ISO BMFF segment given H264 nalUnits
48750        * @param {Object} nalUnit A data event representing a nalUnit
48751        * @param {String} nalUnit.nalUnitType
48752        * @param {Object} nalUnit.config Properties for a mp4 track
48753        * @param {Uint8Array} nalUnit.data The nalUnit bytes
48754        * @see lib/codecs/h264.js
48755       **/
48756
48757      this.push = function (nalUnit) {
48758        trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
48759
48760        if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
48761          config = nalUnit.config;
48762          track.sps = [nalUnit.data];
48763          VIDEO_PROPERTIES.forEach(function (prop) {
48764            track[prop] = config[prop];
48765          }, this);
48766        }
48767        if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
48768          pps = nalUnit.data;
48769          track.pps = [nalUnit.data];
48770        } // buffer video until flush() is called
48771
48772        nalUnits.push(nalUnit);
48773      };
48774      /**
48775        * Pass constructed ISO BMFF track and boxes on to the
48776        * next stream in the pipeline
48777       **/
48778
48779      this.flush = function () {
48780        var frames,
48781          gopForFusion,
48782          gops,
48783          moof,
48784          mdat,
48785          boxes,
48786          prependedContentDuration = 0,
48787          firstGop,
48788          lastGop; // Throw away nalUnits at the start of the byte stream until
48789        // we find the first AUD
48790
48791        while (nalUnits.length) {
48792          if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
48793            break;
48794          }
48795          nalUnits.shift();
48796        } // Return early if no video data has been observed
48797
48798        if (nalUnits.length === 0) {
48799          this.resetStream_();
48800          this.trigger('done', 'VideoSegmentStream');
48801          return;
48802        } // Organize the raw nal-units into arrays that represent
48803        // higher-level constructs such as frames and gops
48804        // (group-of-pictures)
48805
48806        frames = frameUtils.groupNalsIntoFrames(nalUnits);
48807        gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
48808        // a problem since MSE (on Chrome) requires a leading keyframe.
48809        //
48810        // We have two approaches to repairing this situation:
48811        // 1) GOP-FUSION:
48812        //    This is where we keep track of the GOPS (group-of-pictures)
48813        //    from previous fragments and attempt to find one that we can
48814        //    prepend to the current fragment in order to create a valid
48815        //    fragment.
48816        // 2) KEYFRAME-PULLING:
48817        //    Here we search for the first keyframe in the fragment and
48818        //    throw away all the frames between the start of the fragment
48819        //    and that keyframe. We then extend the duration and pull the
48820        //    PTS of the keyframe forward so that it covers the time range
48821        //    of the frames that were disposed of.
48822        //
48823        // #1 is far prefereable over #2 which can cause "stuttering" but
48824        // requires more things to be just right.
48825
48826        if (!gops[0][0].keyFrame) {
48827          // Search for a gop for fusion from our gopCache
48828          gopForFusion = this.getGopForFusion_(nalUnits[0], track);
48829          if (gopForFusion) {
48830            // in order to provide more accurate timing information about the segment, save
48831            // the number of seconds prepended to the original segment due to GOP fusion
48832            prependedContentDuration = gopForFusion.duration;
48833            gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
48834            // new gop at the beginning
48835
48836            gops.byteLength += gopForFusion.byteLength;
48837            gops.nalCount += gopForFusion.nalCount;
48838            gops.pts = gopForFusion.pts;
48839            gops.dts = gopForFusion.dts;
48840            gops.duration += gopForFusion.duration;
48841          } else {
48842            // If we didn't find a candidate gop fall back to keyframe-pulling
48843            gops = frameUtils.extendFirstKeyFrame(gops);
48844          }
48845        } // Trim gops to align with gopsToAlignWith
48846
48847        if (gopsToAlignWith.length) {
48848          var alignedGops;
48849          if (options.alignGopsAtEnd) {
48850            alignedGops = this.alignGopsAtEnd_(gops);
48851          } else {
48852            alignedGops = this.alignGopsAtStart_(gops);
48853          }
48854          if (!alignedGops) {
48855            // save all the nals in the last GOP into the gop cache
48856            this.gopCache_.unshift({
48857              gop: gops.pop(),
48858              pps: track.pps,
48859              sps: track.sps
48860            }); // Keep a maximum of 6 GOPs in the cache
48861
48862            this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48863
48864            nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
48865
vendor: 6,878 bytes, lines 48866-49031
48866            this.resetStream_();
48867            this.trigger('done', 'VideoSegmentStream');
48868            return;
48869          } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
48870          // when recalculated before sending off to CoalesceStream
48871
48872          trackDecodeInfo.clearDtsInfo(track);
48873          gops = alignedGops;
48874        }
48875        trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
48876        // samples (that is, frames) in the video data
48877
48878        track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
48879
48880        mdat = mp4.mdat(frameUtils.concatenateNalData(gops));
48881        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
48882        this.trigger('processedGopsInfo', gops.map(function (gop) {
48883          return {
48884            pts: gop.pts,
48885            dts: gop.dts,
48886            byteLength: gop.byteLength
48887          };
48888        }));
48889        firstGop = gops[0];
48890        lastGop = gops[gops.length - 1];
48891        this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
48892        this.trigger('timingInfo', {
48893          start: gops[0].pts,
48894          end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
48895        }); // save all the nals in the last GOP into the gop cache
48896
48897        this.gopCache_.unshift({
48898          gop: gops.pop(),
48899          pps: track.pps,
48900          sps: track.sps
48901        }); // Keep a maximum of 6 GOPs in the cache
48902
48903        this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48904
48905        nalUnits = [];
48906        this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
48907        this.trigger('timelineStartInfo', track.timelineStartInfo);
48908        moof = mp4.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
48909        // throwing away hundreds of media segment fragments
48910
48911        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
48912
48913        sequenceNumber++;
48914        boxes.set(moof);
48915        boxes.set(mdat, moof.byteLength);
48916        this.trigger('data', {
48917          track: track,
48918          boxes: boxes
48919        });
48920        this.resetStream_(); // Continue with the flush process now
48921
48922        this.trigger('done', 'VideoSegmentStream');
48923      };
48924      this.reset = function () {
48925        this.resetStream_();
48926        nalUnits = [];
48927        this.gopCache_.length = 0;
48928        gopsToAlignWith.length = 0;
48929        this.trigger('reset');
48930      };
48931      this.resetStream_ = function () {
48932        trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
48933        // for instance, when we are rendition switching
48934
48935        config = undefined;
48936        pps = undefined;
48937      }; // Search for a candidate Gop for gop-fusion from the gop cache and
48938      // return it or return null if no good candidate was found
48939
48940      this.getGopForFusion_ = function (nalUnit) {
48941        var halfSecond = 45000,
48942          // Half-a-second in a 90khz clock
48943          allowableOverlap = 10000,
48944          // About 3 frames @ 30fps
48945          nearestDistance = Infinity,
48946          dtsDistance,
48947          nearestGopObj,
48948          currentGop,
48949          currentGopObj,
48950          i; // Search for the GOP nearest to the beginning of this nal unit
48951
48952        for (i = 0; i < this.gopCache_.length; i++) {
48953          currentGopObj = this.gopCache_[i];
48954          currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
48955
48956          if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
48957            continue;
48958          } // Reject Gops that would require a negative baseMediaDecodeTime
48959
48960          if (currentGop.dts < track.timelineStartInfo.dts) {
48961            continue;
48962          } // The distance between the end of the gop and the start of the nalUnit
48963
48964          dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
48965          // a half-second of the nal unit
48966
48967          if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
48968            // Always use the closest GOP we found if there is more than
48969            // one candidate
48970            if (!nearestGopObj || nearestDistance > dtsDistance) {
48971              nearestGopObj = currentGopObj;
48972              nearestDistance = dtsDistance;
48973            }
48974          }
48975        }
48976        if (nearestGopObj) {
48977          return nearestGopObj.gop;
48978        }
48979        return null;
48980      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
48981      // of gopsToAlignWith starting from the START of the list
48982
48983      this.alignGopsAtStart_ = function (gops) {
48984        var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
48985        byteLength = gops.byteLength;
48986        nalCount = gops.nalCount;
48987        duration = gops.duration;
48988        alignIndex = gopIndex = 0;
48989        while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
48990          align = gopsToAlignWith[alignIndex];
48991          gop = gops[gopIndex];
48992          if (align.pts === gop.pts) {
48993            break;
48994          }
48995          if (gop.pts > align.pts) {
48996            // this current gop starts after the current gop we want to align on, so increment
48997            // align index
48998            alignIndex++;
48999            continue;
49000          } // current gop starts before the current gop we want to align on. so increment gop
49001          // index
49002
49003          gopIndex++;
49004          byteLength -= gop.byteLength;
49005          nalCount -= gop.nalCount;
49006          duration -= gop.duration;
49007        }
49008        if (gopIndex === 0) {
49009          // no gops to trim
49010          return gops;
49011        }
49012        if (gopIndex === gops.length) {
49013          // all gops trimmed, skip appending all gops
49014          return null;
49015        }
49016        alignedGops = gops.slice(gopIndex);
49017        alignedGops.byteLength = byteLength;
49018        alignedGops.duration = duration;
49019        alignedGops.nalCount = nalCount;
49020        alignedGops.pts = alignedGops[0].pts;
49021        alignedGops.dts = alignedGops[0].dts;
49022        return alignedGops;
49023      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
49024      // of gopsToAlignWith starting from the END of the list
49025
49026      this.alignGopsAtEnd_ = function (gops) {
49027        var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
49028        alignIndex = gopsToAlignWith.length - 1;
49029        gopIndex = gops.length - 1;
49030        alignEndIndex = null;
49031        matchFound = false;
vendor: 22,196 bytes, lines 49032-49524
49032        while (alignIndex >= 0 && gopIndex >= 0) {
49033          align = gopsToAlignWith[alignIndex];
49034          gop = gops[gopIndex];
49035          if (align.pts === gop.pts) {
49036            matchFound = true;
49037            break;
49038          }
49039          if (align.pts > gop.pts) {
49040            alignIndex--;
49041            continue;
49042          }
49043          if (alignIndex === gopsToAlignWith.length - 1) {
49044            // gop.pts is greater than the last alignment candidate. If no match is found
49045            // by the end of this loop, we still want to append gops that come after this
49046            // point
49047            alignEndIndex = gopIndex;
49048          }
49049          gopIndex--;
49050        }
49051        if (!matchFound && alignEndIndex === null) {
49052          return null;
49053        }
49054        var trimIndex;
49055        if (matchFound) {
49056          trimIndex = gopIndex;
49057        } else {
49058          trimIndex = alignEndIndex;
49059        }
49060        if (trimIndex === 0) {
49061          return gops;
49062        }
49063        var alignedGops = gops.slice(trimIndex);
49064        var metadata = alignedGops.reduce(function (total, gop) {
49065          total.byteLength += gop.byteLength;
49066          total.duration += gop.duration;
49067          total.nalCount += gop.nalCount;
49068          return total;
49069        }, {
49070          byteLength: 0,
49071          duration: 0,
49072          nalCount: 0
49073        });
49074        alignedGops.byteLength = metadata.byteLength;
49075        alignedGops.duration = metadata.duration;
49076        alignedGops.nalCount = metadata.nalCount;
49077        alignedGops.pts = alignedGops[0].pts;
49078        alignedGops.dts = alignedGops[0].dts;
49079        return alignedGops;
49080      };
49081      this.alignGopsWith = function (newGopsToAlignWith) {
49082        gopsToAlignWith = newGopsToAlignWith;
49083      };
49084    };
49085    VideoSegmentStream.prototype = new Stream();
49086    /**
49087     * A Stream that can combine multiple streams (ie. audio & video)
49088     * into a single output segment for MSE. Also supports audio-only
49089     * and video-only streams.
49090     * @param options {object} transmuxer options object
49091     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
49092     *        in the source; false to adjust the first segment to start at media timeline start.
49093     */
49094
49095    CoalesceStream = function (options, metadataStream) {
49096      // Number of Tracks per output segment
49097      // If greater than 1, we combine multiple
49098      // tracks into a single segment
49099      this.numberOfTracks = 0;
49100      this.metadataStream = metadataStream;
49101      options = options || {};
49102      if (typeof options.remux !== 'undefined') {
49103        this.remuxTracks = !!options.remux;
49104      } else {
49105        this.remuxTracks = true;
49106      }
49107      if (typeof options.keepOriginalTimestamps === 'boolean') {
49108        this.keepOriginalTimestamps = options.keepOriginalTimestamps;
49109      } else {
49110        this.keepOriginalTimestamps = false;
49111      }
49112      this.pendingTracks = [];
49113      this.videoTrack = null;
49114      this.pendingBoxes = [];
49115      this.pendingCaptions = [];
49116      this.pendingMetadata = [];
49117      this.pendingBytes = 0;
49118      this.emittedTracks = 0;
49119      CoalesceStream.prototype.init.call(this); // Take output from multiple
49120
49121      this.push = function (output) {
49122        // buffer incoming captions until the associated video segment
49123        // finishes
49124        if (output.content || output.text) {
49125          return this.pendingCaptions.push(output);
49126        } // buffer incoming id3 tags until the final flush
49127
49128        if (output.frames) {
49129          return this.pendingMetadata.push(output);
49130        } // Add this track to the list of pending tracks and store
49131        // important information required for the construction of
49132        // the final segment
49133
49134        this.pendingTracks.push(output.track);
49135        this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
49136        // We unshift audio and push video because
49137        // as of Chrome 75 when switching from
49138        // one init segment to another if the video
49139        // mdat does not appear after the audio mdat
49140        // only audio will play for the duration of our transmux.
49141
49142        if (output.track.type === 'video') {
49143          this.videoTrack = output.track;
49144          this.pendingBoxes.push(output.boxes);
49145        }
49146        if (output.track.type === 'audio') {
49147          this.audioTrack = output.track;
49148          this.pendingBoxes.unshift(output.boxes);
49149        }
49150      };
49151    };
49152    CoalesceStream.prototype = new Stream();
49153    CoalesceStream.prototype.flush = function (flushSource) {
49154      var offset = 0,
49155        event = {
49156          captions: [],
49157          captionStreams: {},
49158          metadata: [],
49159          info: {}
49160        },
49161        caption,
49162        id3,
49163        initSegment,
49164        timelineStartPts = 0,
49165        i;
49166      if (this.pendingTracks.length < this.numberOfTracks) {
49167        if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
49168          // Return because we haven't received a flush from a data-generating
49169          // portion of the segment (meaning that we have only recieved meta-data
49170          // or captions.)
49171          return;
49172        } else if (this.remuxTracks) {
49173          // Return until we have enough tracks from the pipeline to remux (if we
49174          // are remuxing audio and video into a single MP4)
49175          return;
49176        } else if (this.pendingTracks.length === 0) {
49177          // In the case where we receive a flush without any data having been
49178          // received we consider it an emitted track for the purposes of coalescing
49179          // `done` events.
49180          // We do this for the case where there is an audio and video track in the
49181          // segment but no audio data. (seen in several playlists with alternate
49182          // audio tracks and no audio present in the main TS segments.)
49183          this.emittedTracks++;
49184          if (this.emittedTracks >= this.numberOfTracks) {
49185            this.trigger('done');
49186            this.emittedTracks = 0;
49187          }
49188          return;
49189        }
49190      }
49191      if (this.videoTrack) {
49192        timelineStartPts = this.videoTrack.timelineStartInfo.pts;
49193        VIDEO_PROPERTIES.forEach(function (prop) {
49194          event.info[prop] = this.videoTrack[prop];
49195        }, this);
49196      } else if (this.audioTrack) {
49197        timelineStartPts = this.audioTrack.timelineStartInfo.pts;
49198        AUDIO_PROPERTIES.forEach(function (prop) {
49199          event.info[prop] = this.audioTrack[prop];
49200        }, this);
49201      }
49202      if (this.videoTrack || this.audioTrack) {
49203        if (this.pendingTracks.length === 1) {
49204          event.type = this.pendingTracks[0].type;
49205        } else {
49206          event.type = 'combined';
49207        }
49208        this.emittedTracks += this.pendingTracks.length;
49209        initSegment = mp4.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
49210
49211        event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
49212        // and track definitions
49213
49214        event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
49215
49216        event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
49217
49218        for (i = 0; i < this.pendingBoxes.length; i++) {
49219          event.data.set(this.pendingBoxes[i], offset);
49220          offset += this.pendingBoxes[i].byteLength;
49221        } // Translate caption PTS times into second offsets to match the
49222        // video timeline for the segment, and add track info
49223
49224        for (i = 0; i < this.pendingCaptions.length; i++) {
49225          caption = this.pendingCaptions[i];
49226          caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
49227          caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
49228          event.captionStreams[caption.stream] = true;
49229          event.captions.push(caption);
49230        } // Translate ID3 frame PTS times into second offsets to match the
49231        // video timeline for the segment
49232
49233        for (i = 0; i < this.pendingMetadata.length; i++) {
49234          id3 = this.pendingMetadata[i];
49235          id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
49236          event.metadata.push(id3);
49237        } // We add this to every single emitted segment even though we only need
49238        // it for the first
49239
49240        event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
49241
49242        this.pendingTracks.length = 0;
49243        this.videoTrack = null;
49244        this.pendingBoxes.length = 0;
49245        this.pendingCaptions.length = 0;
49246        this.pendingBytes = 0;
49247        this.pendingMetadata.length = 0; // Emit the built segment
49248        // We include captions and ID3 tags for backwards compatibility,
49249        // ideally we should send only video and audio in the data event
49250
49251        this.trigger('data', event); // Emit each caption to the outside world
49252        // Ideally, this would happen immediately on parsing captions,
49253        // but we need to ensure that video data is sent back first
49254        // so that caption timing can be adjusted to match video timing
49255
49256        for (i = 0; i < event.captions.length; i++) {
49257          caption = event.captions[i];
49258          this.trigger('caption', caption);
49259        } // Emit each id3 tag to the outside world
49260        // Ideally, this would happen immediately on parsing the tag,
49261        // but we need to ensure that video data is sent back first
49262        // so that ID3 frame timing can be adjusted to match video timing
49263
49264        for (i = 0; i < event.metadata.length; i++) {
49265          id3 = event.metadata[i];
49266          this.trigger('id3Frame', id3);
49267        }
49268      } // Only emit `done` if all tracks have been flushed and emitted
49269
49270      if (this.emittedTracks >= this.numberOfTracks) {
49271        this.trigger('done');
49272        this.emittedTracks = 0;
49273      }
49274    };
49275    CoalesceStream.prototype.setRemux = function (val) {
49276      this.remuxTracks = val;
49277    };
49278    /**
49279     * A Stream that expects MP2T binary data as input and produces
49280     * corresponding media segments, suitable for use with Media Source
49281     * Extension (MSE) implementations that support the ISO BMFF byte
49282     * stream format, like Chrome.
49283     */
49284
49285    Transmuxer = function (options) {
49286      var self = this,
49287        hasFlushed = true,
49288        videoTrack,
49289        audioTrack;
49290      Transmuxer.prototype.init.call(this);
49291      options = options || {};
49292      this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
49293      this.transmuxPipeline_ = {};
49294      this.setupAacPipeline = function () {
49295        var pipeline = {};
49296        this.transmuxPipeline_ = pipeline;
49297        pipeline.type = 'aac';
49298        pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
49299
49300        pipeline.aacStream = new AacStream();
49301        pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
49302        pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
49303        pipeline.adtsStream = new AdtsStream();
49304        pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
49305        pipeline.headOfPipeline = pipeline.aacStream;
49306        pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
49307        pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
49308        pipeline.metadataStream.on('timestamp', function (frame) {
49309          pipeline.aacStream.setTimestamp(frame.timeStamp);
49310        });
49311        pipeline.aacStream.on('data', function (data) {
49312          if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
49313            return;
49314          }
49315          audioTrack = audioTrack || {
49316            timelineStartInfo: {
49317              baseMediaDecodeTime: self.baseMediaDecodeTime
49318            },
49319            codec: 'adts',
49320            type: 'audio'
49321          }; // hook up the audio segment stream to the first track with aac data
49322
49323          pipeline.coalesceStream.numberOfTracks++;
49324          pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
49325          pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49326          pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
49327
49328          pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
49329
49330          self.trigger('trackinfo', {
49331            hasAudio: !!audioTrack,
49332            hasVideo: !!videoTrack
49333          });
49334        }); // Re-emit any data coming from the coalesce stream to the outside world
49335
49336        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
49337
49338        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49339        addPipelineLogRetriggers(this, pipeline);
49340      };
49341      this.setupTsPipeline = function () {
49342        var pipeline = {};
49343        this.transmuxPipeline_ = pipeline;
49344        pipeline.type = 'ts';
49345        pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
49346
49347        pipeline.packetStream = new m2ts.TransportPacketStream();
49348        pipeline.parseStream = new m2ts.TransportParseStream();
49349        pipeline.elementaryStream = new m2ts.ElementaryStream();
49350        pipeline.timestampRolloverStream = new m2ts.TimestampRolloverStream();
49351        pipeline.adtsStream = new AdtsStream();
49352        pipeline.h264Stream = new H264Stream();
49353        pipeline.captionStream = new m2ts.CaptionStream(options);
49354        pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
49355        pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
49356
49357        pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
49358        // demux the streams
49359
49360        pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
49361        pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
49362        pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
49363
49364        pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
49365        pipeline.elementaryStream.on('data', function (data) {
49366          var i;
49367          if (data.type === 'metadata') {
49368            i = data.tracks.length; // scan the tracks listed in the metadata
49369
49370            while (i--) {
49371              if (!videoTrack && data.tracks[i].type === 'video') {
49372                videoTrack = data.tracks[i];
49373                videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49374              } else if (!audioTrack && data.tracks[i].type === 'audio') {
49375                audioTrack = data.tracks[i];
49376                audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49377              }
49378            } // hook up the video segment stream to the first track with h264 data
49379
49380            if (videoTrack && !pipeline.videoSegmentStream) {
49381              pipeline.coalesceStream.numberOfTracks++;
49382              pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
49383              pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
49384              pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
49385                // When video emits timelineStartInfo data after a flush, we forward that
49386                // info to the AudioSegmentStream, if it exists, because video timeline
49387                // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
49388                // because this is a particularly subtle form of timestamp alteration.
49389                if (audioTrack && !options.keepOriginalTimestamps) {
49390                  audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
49391                  // very earliest DTS we have seen in video because Chrome will
49392                  // interpret any video track with a baseMediaDecodeTime that is
49393                  // non-zero as a gap.
49394
49395                  pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
49396                }
49397              });
49398              pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
49399              pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
49400              pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
49401                if (audioTrack) {
49402                  pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
49403                }
49404              });
49405              pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
49406
49407              pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
49408            }
49409            if (audioTrack && !pipeline.audioSegmentStream) {
49410              // hook up the audio segment stream to the first track with aac data
49411              pipeline.coalesceStream.numberOfTracks++;
49412              pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
49413              pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49414              pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
49415              pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
49416
49417              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
49418            } // emit pmt info
49419
49420            self.trigger('trackinfo', {
49421              hasAudio: !!audioTrack,
49422              hasVideo: !!videoTrack
49423            });
49424          }
49425        }); // Re-emit any data coming from the coalesce stream to the outside world
49426
49427        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
49428        pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
49429          id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
49430          self.trigger('id3Frame', id3Frame);
49431        });
49432        pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
49433
49434        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49435        addPipelineLogRetriggers(this, pipeline);
49436      }; // hook up the segment streams once track metadata is delivered
49437
49438      this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
49439        var pipeline = this.transmuxPipeline_;
49440        if (!options.keepOriginalTimestamps) {
49441          this.baseMediaDecodeTime = baseMediaDecodeTime;
49442        }
49443        if (audioTrack) {
49444          audioTrack.timelineStartInfo.dts = undefined;
49445          audioTrack.timelineStartInfo.pts = undefined;
49446          trackDecodeInfo.clearDtsInfo(audioTrack);
49447          if (pipeline.audioTimestampRolloverStream) {
49448            pipeline.audioTimestampRolloverStream.discontinuity();
49449          }
49450        }
49451        if (videoTrack) {
49452          if (pipeline.videoSegmentStream) {
49453            pipeline.videoSegmentStream.gopCache_ = [];
49454          }
49455          videoTrack.timelineStartInfo.dts = undefined;
49456          videoTrack.timelineStartInfo.pts = undefined;
49457          trackDecodeInfo.clearDtsInfo(videoTrack);
49458          pipeline.captionStream.reset();
49459        }
49460        if (pipeline.timestampRolloverStream) {
49461          pipeline.timestampRolloverStream.discontinuity();
49462        }
49463      };
49464      this.setAudioAppendStart = function (timestamp) {
49465        if (audioTrack) {
49466          this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
49467        }
49468      };
49469      this.setRemux = function (val) {
49470        var pipeline = this.transmuxPipeline_;
49471        options.remux = val;
49472        if (pipeline && pipeline.coalesceStream) {
49473          pipeline.coalesceStream.setRemux(val);
49474        }
49475      };
49476      this.alignGopsWith = function (gopsToAlignWith) {
49477        if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
49478          this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
49479        }
49480      };
49481      this.getLogTrigger_ = function (key) {
49482        var self = this;
49483        return function (event) {
49484          event.stream = key;
49485          self.trigger('log', event);
49486        };
49487      }; // feed incoming data to the front of the parsing pipeline
49488
49489      this.push = function (data) {
49490        if (hasFlushed) {
49491          var isAac = isLikelyAacData(data);
49492          if (isAac && this.transmuxPipeline_.type !== 'aac') {
49493            this.setupAacPipeline();
49494          } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
49495            this.setupTsPipeline();
49496          }
49497          hasFlushed = false;
49498        }
49499        this.transmuxPipeline_.headOfPipeline.push(data);
49500      }; // flush any buffered data
49501
49502      this.flush = function () {
49503        hasFlushed = true; // Start at the top of the pipeline and flush all pending work
49504
49505        this.transmuxPipeline_.headOfPipeline.flush();
49506      };
49507      this.endTimeline = function () {
49508        this.transmuxPipeline_.headOfPipeline.endTimeline();
49509      };
49510      this.reset = function () {
49511        if (this.transmuxPipeline_.headOfPipeline) {
49512          this.transmuxPipeline_.headOfPipeline.reset();
49513        }
49514      }; // Caption data has to be reset when seeking outside buffered range
49515
49516      this.resetCaptions = function () {
49517        if (this.transmuxPipeline_.captionStream) {
49518          this.transmuxPipeline_.captionStream.reset();
49519        }
49520      };
49521    };
49522    Transmuxer.prototype = new Stream();
49523    var transmuxer = {
49524      Transmuxer: Transmuxer
vendor: 4,765 bytes, lines 49524-49661
49524,
49525      VideoSegmentStream: VideoSegmentStream,
49526      AudioSegmentStream: AudioSegmentStream,
49527      AUDIO_PROPERTIES: AUDIO_PROPERTIES,
49528      VIDEO_PROPERTIES: VIDEO_PROPERTIES,
49529      // exported for testing
49530      generateSegmentTimingInfo: generateSegmentTimingInfo
49531    };
49532    /**
49533     * mux.js
49534     *
49535     * Copyright (c) Brightcove
49536     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
49537     */
49538
49539    var toUnsigned$3 = function (value) {
49540      return value >>> 0;
49541    };
49542    var toHexString$1 = function (value) {
49543      return ('00' + value.toString(16)).slice(-2);
49544    };
49545    var bin = {
49546      toUnsigned: toUnsigned$3,
49547      toHexString: toHexString$1
49548    };
49549    var parseType$3 = function (buffer) {
49550      var result = '';
49551      result += String.fromCharCode(buffer[0]);
49552      result += String.fromCharCode(buffer[1]);
49553      result += String.fromCharCode(buffer[2]);
49554      result += String.fromCharCode(buffer[3]);
49555      return result;
49556    };
49557    var parseType_1 = parseType$3;
49558    var toUnsigned$2 = bin.toUnsigned;
49559    var parseType$2 = parseType_1;
49560    var findBox$2 = function (data, path) {
49561      var results = [],
49562        i,
49563        size,
49564        type,
49565        end,
49566        subresults;
49567      if (!path.length) {
49568        // short-circuit the search for empty paths
49569        return null;
49570      }
49571      for (i = 0; i < data.byteLength;) {
49572        size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
49573        type = parseType$2(data.subarray(i + 4, i + 8));
49574        end = size > 1 ? i + size : data.byteLength;
49575        if (type === path[0]) {
49576          if (path.length === 1) {
49577            // this is the end of the path and we've found the box we were
49578            // looking for
49579            results.push(data.subarray(i + 8, end));
49580          } else {
49581            // recursively search for the next box along the path
49582            subresults = findBox$2(data.subarray(i + 8, end), path.slice(1));
49583            if (subresults.length) {
49584              results = results.concat(subresults);
49585            }
49586          }
49587        }
49588        i = end;
49589      } // we've finished searching all of data
49590
49591      return results;
49592    };
49593    var findBox_1 = findBox$2;
49594    var toUnsigned$1 = bin.toUnsigned;
49595    var getUint64$2 = numbers.getUint64;
49596    var tfdt = function (data) {
49597      var result = {
49598        version: data[0],
49599        flags: new Uint8Array(data.subarray(1, 4))
49600      };
49601      if (result.version === 1) {
49602        result.baseMediaDecodeTime = getUint64$2(data.subarray(4));
49603      } else {
49604        result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
49605      }
49606      return result;
49607    };
49608    var parseTfdt$2 = tfdt;
49609    var parseSampleFlags$1 = function (flags) {
49610      return {
49611        isLeading: (flags[0] & 0x0c) >>> 2,
49612        dependsOn: flags[0] & 0x03,
49613        isDependedOn: (flags[1] & 0xc0) >>> 6,
49614        hasRedundancy: (flags[1] & 0x30) >>> 4,
49615        paddingValue: (flags[1] & 0x0e) >>> 1,
49616        isNonSyncSample: flags[1] & 0x01,
49617        degradationPriority: flags[2] << 8 | flags[3]
49618      };
49619    };
49620    var parseSampleFlags_1 = parseSampleFlags$1;
49621    var parseSampleFlags = parseSampleFlags_1;
49622    var trun = function (data) {
49623      var result = {
49624          version: data[0],
49625          flags: new Uint8Array(data.subarray(1, 4)),
49626          samples: []
49627        },
49628        view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49629        // Flag interpretation
49630        dataOffsetPresent = result.flags[2] & 0x01,
49631        // compare with 2nd byte of 0x1
49632        firstSampleFlagsPresent = result.flags[2] & 0x04,
49633        // compare with 2nd byte of 0x4
49634        sampleDurationPresent = result.flags[1] & 0x01,
49635        // compare with 2nd byte of 0x100
49636        sampleSizePresent = result.flags[1] & 0x02,
49637        // compare with 2nd byte of 0x200
49638        sampleFlagsPresent = result.flags[1] & 0x04,
49639        // compare with 2nd byte of 0x400
49640        sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
49641        // compare with 2nd byte of 0x800
49642        sampleCount = view.getUint32(4),
49643        offset = 8,
49644        sample;
49645      if (dataOffsetPresent) {
49646        // 32 bit signed integer
49647        result.dataOffset = view.getInt32(offset);
49648        offset += 4;
49649      } // Overrides the flags for the first sample only. The order of
49650      // optional values will be: duration, size, compositionTimeOffset
49651
49652      if (firstSampleFlagsPresent && sampleCount) {
49653        sample = {
49654          flags: parseSampleFlags(data.subarray(offset, offset + 4))
49655        };
49656        offset += 4;
49657        if (sampleDurationPresent) {
49658          sample.duration = view.getUint32(offset);
49659          offset += 4;
49660        }
49661        if (sampleSizePresent) {
vendor: 4,860 bytes, lines 49662-49807
49662          sample.size = view.getUint32(offset);
49663          offset += 4;
49664        }
49665        if (sampleCompositionTimeOffsetPresent) {
49666          if (result.version === 1) {
49667            sample.compositionTimeOffset = view.getInt32(offset);
49668          } else {
49669            sample.compositionTimeOffset = view.getUint32(offset);
49670          }
49671          offset += 4;
49672        }
49673        result.samples.push(sample);
49674        sampleCount--;
49675      }
49676      while (sampleCount--) {
49677        sample = {};
49678        if (sampleDurationPresent) {
49679          sample.duration = view.getUint32(offset);
49680          offset += 4;
49681        }
49682        if (sampleSizePresent) {
49683          sample.size = view.getUint32(offset);
49684          offset += 4;
49685        }
49686        if (sampleFlagsPresent) {
49687          sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
49688          offset += 4;
49689        }
49690        if (sampleCompositionTimeOffsetPresent) {
49691          if (result.version === 1) {
49692            sample.compositionTimeOffset = view.getInt32(offset);
49693          } else {
49694            sample.compositionTimeOffset = view.getUint32(offset);
49695          }
49696          offset += 4;
49697        }
49698        result.samples.push(sample);
49699      }
49700      return result;
49701    };
49702    var parseTrun$2 = trun;
49703    var tfhd = function (data) {
49704      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49705        result = {
49706          version: data[0],
49707          flags: new Uint8Array(data.subarray(1, 4)),
49708          trackId: view.getUint32(4)
49709        },
49710        baseDataOffsetPresent = result.flags[2] & 0x01,
49711        sampleDescriptionIndexPresent = result.flags[2] & 0x02,
49712        defaultSampleDurationPresent = result.flags[2] & 0x08,
49713        defaultSampleSizePresent = result.flags[2] & 0x10,
49714        defaultSampleFlagsPresent = result.flags[2] & 0x20,
49715        durationIsEmpty = result.flags[0] & 0x010000,
49716        defaultBaseIsMoof = result.flags[0] & 0x020000,
49717        i;
49718      i = 8;
49719      if (baseDataOffsetPresent) {
49720        i += 4; // truncate top 4 bytes
49721        // FIXME: should we read the full 64 bits?
49722
49723        result.baseDataOffset = view.getUint32(12);
49724        i += 4;
49725      }
49726      if (sampleDescriptionIndexPresent) {
49727        result.sampleDescriptionIndex = view.getUint32(i);
49728        i += 4;
49729      }
49730      if (defaultSampleDurationPresent) {
49731        result.defaultSampleDuration = view.getUint32(i);
49732        i += 4;
49733      }
49734      if (defaultSampleSizePresent) {
49735        result.defaultSampleSize = view.getUint32(i);
49736        i += 4;
49737      }
49738      if (defaultSampleFlagsPresent) {
49739        result.defaultSampleFlags = view.getUint32(i);
49740      }
49741      if (durationIsEmpty) {
49742        result.durationIsEmpty = true;
49743      }
49744      if (!baseDataOffsetPresent && defaultBaseIsMoof) {
49745        result.baseDataOffsetIsMoof = true;
49746      }
49747      return result;
49748    };
49749    var parseTfhd$2 = tfhd;
49750    var win;
49751    if (typeof window !== "undefined") {
49752      win = window;
49753    } else if (typeof commonjsGlobal !== "undefined") {
49754      win = commonjsGlobal;
49755    } else if (typeof self !== "undefined") {
49756      win = self;
49757    } else {
49758      win = {};
49759    }
49760    var window_1 = win;
49761    /**
49762     * mux.js
49763     *
49764     * Copyright (c) Brightcove
49765     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
49766     *
49767     * Reads in-band CEA-708 captions out of FMP4 segments.
49768     * @see https://en.wikipedia.org/wiki/CEA-708
49769     */
49770
49771    var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
49772    var CaptionStream = captionStream.CaptionStream;
49773    var findBox$1 = findBox_1;
49774    var parseTfdt$1 = parseTfdt$2;
49775    var parseTrun$1 = parseTrun$2;
49776    var parseTfhd$1 = parseTfhd$2;
49777    var window$2 = window_1;
49778    /**
49779      * Maps an offset in the mdat to a sample based on the the size of the samples.
49780      * Assumes that `parseSamples` has been called first.
49781      *
49782      * @param {Number} offset - The offset into the mdat
49783      * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
49784      * @return {?Object} The matching sample, or null if no match was found.
49785      *
49786      * @see ISO-BMFF-12/2015, Section 8.8.8
49787     **/
49788
49789    var mapToSample = function (offset, samples) {
49790      var approximateOffset = offset;
49791      for (var i = 0; i < samples.length; i++) {
49792        var sample = samples[i];
49793        if (approximateOffset < sample.size) {
49794          return sample;
49795        }
49796        approximateOffset -= sample.size;
49797      }
49798      return null;
49799    };
49800    /**
49801      * Finds SEI nal units contained in a Media Data Box.
49802      * Assumes that `parseSamples` has been called first.
49803      *
49804      * @param {Uint8Array} avcStream - The bytes of the mdat
49805      * @param {Object[]} samples - The samples parsed out by `parseSamples`
49806      * @param {Number} trackId - The trackId of this video track
49807      * @return {Object[]}
vendor: 19,650 bytes, lines 49807-50335
49807 seiNals - the parsed SEI NALUs found.
49808      *   The contents of the seiNal should match what is expected by
49809      *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
49810      *
49811      * @see ISO-BMFF-12/2015, Section 8.1.1
49812      * @see Rec. ITU-T H.264, 7.3.2.3.1
49813     **/
49814
49815    var findSeiNals = function (avcStream, samples, trackId) {
49816      var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
49817        result = {
49818          logs: [],
49819          seiNals: []
49820        },
49821        seiNal,
49822        i,
49823        length,
49824        lastMatchedSample;
49825      for (i = 0; i + 4 < avcStream.length; i += length) {
49826        length = avcView.getUint32(i);
49827        i += 4; // Bail if this doesn't appear to be an H264 stream
49828
49829        if (length <= 0) {
49830          continue;
49831        }
49832        switch (avcStream[i] & 0x1F) {
49833          case 0x06:
49834            var data = avcStream.subarray(i + 1, i + 1 + length);
49835            var matchingSample = mapToSample(i, samples);
49836            seiNal = {
49837              nalUnitType: 'sei_rbsp',
49838              size: length,
49839              data: data,
49840              escapedRBSP: discardEmulationPreventionBytes(data),
49841              trackId: trackId
49842            };
49843            if (matchingSample) {
49844              seiNal.pts = matchingSample.pts;
49845              seiNal.dts = matchingSample.dts;
49846              lastMatchedSample = matchingSample;
49847            } else if (lastMatchedSample) {
49848              // If a matching sample cannot be found, use the last
49849              // sample's values as they should be as close as possible
49850              seiNal.pts = lastMatchedSample.pts;
49851              seiNal.dts = lastMatchedSample.dts;
49852            } else {
49853              result.logs.push({
49854                level: 'warn',
49855                message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
49856              });
49857              break;
49858            }
49859            result.seiNals.push(seiNal);
49860            break;
49861        }
49862      }
49863      return result;
49864    };
49865    /**
49866      * Parses sample information out of Track Run Boxes and calculates
49867      * the absolute presentation and decode timestamps of each sample.
49868      *
49869      * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
49870      * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
49871          @see ISO-BMFF-12/2015, Section 8.8.12
49872      * @param {Object} tfhd - The parsed Track Fragment Header
49873      *   @see inspect.parseTfhd
49874      * @return {Object[]} the parsed samples
49875      *
49876      * @see ISO-BMFF-12/2015, Section 8.8.8
49877     **/
49878
49879    var parseSamples = function (truns, baseMediaDecodeTime, tfhd) {
49880      var currentDts = baseMediaDecodeTime;
49881      var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
49882      var defaultSampleSize = tfhd.defaultSampleSize || 0;
49883      var trackId = tfhd.trackId;
49884      var allSamples = [];
49885      truns.forEach(function (trun) {
49886        // Note: We currently do not parse the sample table as well
49887        // as the trun. It's possible some sources will require this.
49888        // moov > trak > mdia > minf > stbl
49889        var trackRun = parseTrun$1(trun);
49890        var samples = trackRun.samples;
49891        samples.forEach(function (sample) {
49892          if (sample.duration === undefined) {
49893            sample.duration = defaultSampleDuration;
49894          }
49895          if (sample.size === undefined) {
49896            sample.size = defaultSampleSize;
49897          }
49898          sample.trackId = trackId;
49899          sample.dts = currentDts;
49900          if (sample.compositionTimeOffset === undefined) {
49901            sample.compositionTimeOffset = 0;
49902          }
49903          if (typeof currentDts === 'bigint') {
49904            sample.pts = currentDts + window$2.BigInt(sample.compositionTimeOffset);
49905            currentDts += window$2.BigInt(sample.duration);
49906          } else {
49907            sample.pts = currentDts + sample.compositionTimeOffset;
49908            currentDts += sample.duration;
49909          }
49910        });
49911        allSamples = allSamples.concat(samples);
49912      });
49913      return allSamples;
49914    };
49915    /**
49916      * Parses out caption nals from an FMP4 segment's video tracks.
49917      *
49918      * @param {Uint8Array} segment - The bytes of a single segment
49919      * @param {Number} videoTrackId - The trackId of a video track in the segment
49920      * @return {Object.<Number, Object[]>} A mapping of video trackId to
49921      *   a list of seiNals found in that track
49922     **/
49923
49924    var parseCaptionNals = function (segment, videoTrackId) {
49925      // To get the samples
49926      var trafs = findBox$1(segment, ['moof', 'traf']); // To get SEI NAL units
49927
49928      var mdats = findBox$1(segment, ['mdat']);
49929      var captionNals = {};
49930      var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
49931
49932      mdats.forEach(function (mdat, index) {
49933        var matchingTraf = trafs[index];
49934        mdatTrafPairs.push({
49935          mdat: mdat,
49936          traf: matchingTraf
49937        });
49938      });
49939      mdatTrafPairs.forEach(function (pair) {
49940        var mdat = pair.mdat;
49941        var traf = pair.traf;
49942        var tfhd = findBox$1(traf, ['tfhd']); // Exactly 1 tfhd per traf
49943
49944        var headerInfo = parseTfhd$1(tfhd[0]);
49945        var trackId = headerInfo.trackId;
49946        var tfdt = findBox$1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
49947
49948        var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt$1(tfdt[0]).baseMediaDecodeTime : 0;
49949        var truns = findBox$1(traf, ['trun']);
49950        var samples;
49951        var result; // Only parse video data for the chosen video track
49952
49953        if (videoTrackId === trackId && truns.length > 0) {
49954          samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
49955          result = findSeiNals(mdat, samples, trackId);
49956          if (!captionNals[trackId]) {
49957            captionNals[trackId] = {
49958              seiNals: [],
49959              logs: []
49960            };
49961          }
49962          captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
49963          captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
49964        }
49965      });
49966      return captionNals;
49967    };
49968    /**
49969      * Parses out inband captions from an MP4 container and returns
49970      * caption objects that can be used by WebVTT and the TextTrack API.
49971      * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
49972      * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
49973      * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
49974      *
49975      * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
49976      * @param {Number} trackId - The id of the video track to parse
49977      * @param {Number} timescale - The timescale for the video track from the init segment
49978      *
49979      * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
49980      * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
49981      * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
49982      * @return {Object[]} parsedCaptions[].content - A list of individual caption segments
49983      * @return {String} parsedCaptions[].content.text - The visible content of the caption segment
49984      * @return {Number} parsedCaptions[].content.line - The line height from 1-15 for positioning of the caption segment
49985      * @return {Number} parsedCaptions[].content.position - The column indent percentage for cue positioning from 10-80
49986     **/
49987
49988    var parseEmbeddedCaptions = function (segment, trackId, timescale) {
49989      var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
49990
49991      if (trackId === null) {
49992        return null;
49993      }
49994      captionNals = parseCaptionNals(segment, trackId);
49995      var trackNals = captionNals[trackId] || {};
49996      return {
49997        seiNals: trackNals.seiNals,
49998        logs: trackNals.logs,
49999        timescale: timescale
50000      };
50001    };
50002    /**
50003      * Converts SEI NALUs into captions that can be used by video.js
50004     **/
50005
50006    var CaptionParser = function () {
50007      var isInitialized = false;
50008      var captionStream; // Stores segments seen before trackId and timescale are set
50009
50010      var segmentCache; // Stores video track ID of the track being parsed
50011
50012      var trackId; // Stores the timescale of the track being parsed
50013
50014      var timescale; // Stores captions parsed so far
50015
50016      var parsedCaptions; // Stores whether we are receiving partial data or not
50017
50018      var parsingPartial;
50019      /**
50020        * A method to indicate whether a CaptionParser has been initalized
50021        * @returns {Boolean}
50022       **/
50023
50024      this.isInitialized = function () {
50025        return isInitialized;
50026      };
50027      /**
50028        * Initializes the underlying CaptionStream, SEI NAL parsing
50029        * and management, and caption collection
50030       **/
50031
50032      this.init = function (options) {
50033        captionStream = new CaptionStream();
50034        isInitialized = true;
50035        parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
50036
50037        captionStream.on('data', function (event) {
50038          // Convert to seconds in the source's timescale
50039          event.startTime = event.startPts / timescale;
50040          event.endTime = event.endPts / timescale;
50041          parsedCaptions.captions.push(event);
50042          parsedCaptions.captionStreams[event.stream] = true;
50043        });
50044        captionStream.on('log', function (log) {
50045          parsedCaptions.logs.push(log);
50046        });
50047      };
50048      /**
50049        * Determines if a new video track will be selected
50050        * or if the timescale changed
50051        * @return {Boolean}
50052       **/
50053
50054      this.isNewInit = function (videoTrackIds, timescales) {
50055        if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
50056          return false;
50057        }
50058        return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
50059      };
50060      /**
50061        * Parses out SEI captions and interacts with underlying
50062        * CaptionStream to return dispatched captions
50063        *
50064        * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
50065        * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
50066        * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
50067        * @see parseEmbeddedCaptions
50068        * @see m2ts/caption-stream.js
50069       **/
50070
50071      this.parse = function (segment, videoTrackIds, timescales) {
50072        var parsedData;
50073        if (!this.isInitialized()) {
50074          return null; // This is not likely to be a video segment
50075        } else if (!videoTrackIds || !timescales) {
50076          return null;
50077        } else if (this.isNewInit(videoTrackIds, timescales)) {
50078          // Use the first video track only as there is no
50079          // mechanism to switch to other video tracks
50080          trackId = videoTrackIds[0];
50081          timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
50082          // data until we have one.
50083          // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
50084        } else if (trackId === null || !timescale) {
50085          segmentCache.push(segment);
50086          return null;
50087        } // Now that a timescale and trackId is set, parse cached segments
50088
50089        while (segmentCache.length > 0) {
50090          var cachedSegment = segmentCache.shift();
50091          this.parse(cachedSegment, videoTrackIds, timescales);
50092        }
50093        parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
50094        if (parsedData && parsedData.logs) {
50095          parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
50096        }
50097        if (parsedData === null || !parsedData.seiNals) {
50098          if (parsedCaptions.logs.length) {
50099            return {
50100              logs: parsedCaptions.logs,
50101              captions: [],
50102              captionStreams: []
50103            };
50104          }
50105          return null;
50106        }
50107        this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
50108
50109        this.flushStream();
50110        return parsedCaptions;
50111      };
50112      /**
50113        * Pushes SEI NALUs onto CaptionStream
50114        * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
50115        * Assumes that `parseCaptionNals` has been called first
50116        * @see m2ts/caption-stream.js
50117        **/
50118
50119      this.pushNals = function (nals) {
50120        if (!this.isInitialized() || !nals || nals.length === 0) {
50121          return null;
50122        }
50123        nals.forEach(function (nal) {
50124          captionStream.push(nal);
50125        });
50126      };
50127      /**
50128        * Flushes underlying CaptionStream to dispatch processed, displayable captions
50129        * @see m2ts/caption-stream.js
50130       **/
50131
50132      this.flushStream = function () {
50133        if (!this.isInitialized()) {
50134          return null;
50135        }
50136        if (!parsingPartial) {
50137          captionStream.flush();
50138        } else {
50139          captionStream.partialFlush();
50140        }
50141      };
50142      /**
50143        * Reset caption buckets for new data
50144       **/
50145
50146      this.clearParsedCaptions = function () {
50147        parsedCaptions.captions = [];
50148        parsedCaptions.captionStreams = {};
50149        parsedCaptions.logs = [];
50150      };
50151      /**
50152        * Resets underlying CaptionStream
50153        * @see m2ts/caption-stream.js
50154       **/
50155
50156      this.resetCaptionStream = function () {
50157        if (!this.isInitialized()) {
50158          return null;
50159        }
50160        captionStream.reset();
50161      };
50162      /**
50163        * Convenience method to clear all captions flushed from the
50164        * CaptionStream and still being parsed
50165        * @see m2ts/caption-stream.js
50166       **/
50167
50168      this.clearAllCaptions = function () {
50169        this.clearParsedCaptions();
50170        this.resetCaptionStream();
50171      };
50172      /**
50173        * Reset caption parser
50174       **/
50175
50176      this.reset = function () {
50177        segmentCache = [];
50178        trackId = null;
50179        timescale = null;
50180        if (!parsedCaptions) {
50181          parsedCaptions = {
50182            captions: [],
50183            // CC1, CC2, CC3, CC4
50184            captionStreams: {},
50185            logs: []
50186          };
50187        } else {
50188          this.clearParsedCaptions();
50189        }
50190        this.resetCaptionStream();
50191      };
50192      this.reset();
50193    };
50194    var captionParser = CaptionParser;
50195    /**
50196     * Returns the first string in the data array ending with a null char '\0'
50197     * @param {UInt8} data 
50198     * @returns the string with the null char
50199     */
50200
50201    var uint8ToCString$1 = function (data) {
50202      var index = 0;
50203      var curChar = String.fromCharCode(data[index]);
50204      var retString = '';
50205      while (curChar !== '\0') {
50206        retString += curChar;
50207        index++;
50208        curChar = String.fromCharCode(data[index]);
50209      } // Add nullChar
50210
50211      retString += curChar;
50212      return retString;
50213    };
50214    var string = {
50215      uint8ToCString: uint8ToCString$1
50216    };
50217    var uint8ToCString = string.uint8ToCString;
50218    var getUint64$1 = numbers.getUint64;
50219    /**
50220     * Based on: ISO/IEC 23009 Section: 5.10.3.3
50221     * References:
50222     * https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
50223     * https://aomediacodec.github.io/id3-emsg/
50224     * 
50225     * Takes emsg box data as a uint8 array and returns a emsg box object
50226     * @param {UInt8Array} boxData data from emsg box
50227     * @returns A parsed emsg box object
50228     */
50229
50230    var parseEmsgBox = function (boxData) {
50231      // version + flags
50232      var offset = 4;
50233      var version = boxData[0];
50234      var scheme_id_uri, value, timescale, presentation_time, presentation_time_delta, event_duration, id, message_data;
50235      if (version === 0) {
50236        scheme_id_uri = uint8ToCString(boxData.subarray(offset));
50237        offset += scheme_id_uri.length;
50238        value = uint8ToCString(boxData.subarray(offset));
50239        offset += value.length;
50240        var dv = new DataView(boxData.buffer);
50241        timescale = dv.getUint32(offset);
50242        offset += 4;
50243        presentation_time_delta = dv.getUint32(offset);
50244        offset += 4;
50245        event_duration = dv.getUint32(offset);
50246        offset += 4;
50247        id = dv.getUint32(offset);
50248        offset += 4;
50249      } else if (version === 1) {
50250        var dv = new DataView(boxData.buffer);
50251        timescale = dv.getUint32(offset);
50252        offset += 4;
50253        presentation_time = getUint64$1(boxData.subarray(offset));
50254        offset += 8;
50255        event_duration = dv.getUint32(offset);
50256        offset += 4;
50257        id = dv.getUint32(offset);
50258        offset += 4;
50259        scheme_id_uri = uint8ToCString(boxData.subarray(offset));
50260        offset += scheme_id_uri.length;
50261        value = uint8ToCString(boxData.subarray(offset));
50262        offset += value.length;
50263      }
50264      message_data = new Uint8Array(boxData.subarray(offset, boxData.byteLength));
50265      var emsgBox = {
50266        scheme_id_uri,
50267        value,
50268        // if timescale is undefined or 0 set to 1 
50269        timescale: timescale ? timescale : 1,
50270        presentation_time,
50271        presentation_time_delta,
50272        event_duration,
50273        id,
50274        message_data
50275      };
50276      return isValidEmsgBox(version, emsgBox) ? emsgBox : undefined;
50277    };
50278    /**
50279     * Scales a presentation time or time delta with an offset with a provided timescale
50280     * @param {number} presentationTime 
50281     * @param {number} timescale 
50282     * @param {number} timeDelta 
50283     * @param {number} offset 
50284     * @returns the scaled time as a number
50285     */
50286
50287    var scaleTime = function (presentationTime, timescale, timeDelta, offset) {
50288      return presentationTime || presentationTime === 0 ? presentationTime / timescale : offset + timeDelta / timescale;
50289    };
50290    /**
50291     * Checks the emsg box data for validity based on the version
50292     * @param {number} version of the emsg box to validate
50293     * @param {Object} emsg the emsg data to validate
50294     * @returns if the box is valid as a boolean
50295     */
50296
50297    var isValidEmsgBox = function (version, emsg) {
50298      var hasScheme = emsg.scheme_id_uri !== '\0';
50299      var isValidV0Box = version === 0 && isDefined(emsg.presentation_time_delta) && hasScheme;
50300      var isValidV1Box = version === 1 && isDefined(emsg.presentation_time) && hasScheme; // Only valid versions of emsg are 0 and 1
50301
50302      return !(version > 1) && isValidV0Box || isValidV1Box;
50303    }; // Utility function to check if an object is defined
50304
50305    var isDefined = function (data) {
50306      return data !== undefined || data !== null;
50307    };
50308    var emsg$1 = {
50309      parseEmsgBox: parseEmsgBox,
50310      scaleTime: scaleTime
50311    };
50312    /**
50313     * mux.js
50314     *
50315     * Copyright (c) Brightcove
50316     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50317     *
50318     * Utilities to detect basic properties and metadata about MP4s.
50319     */
50320
50321    var toUnsigned = bin.toUnsigned;
50322    var toHexString = bin.toHexString;
50323    var findBox = findBox_1;
50324    var parseType$1 = parseType_1;
50325    var emsg = emsg$1;
50326    var parseTfhd = parseTfhd$2;
50327    var parseTrun = parseTrun$2;
50328    var parseTfdt = parseTfdt$2;
50329    var getUint64 = numbers.getUint64;
50330    var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader, getEmsgID3;
50331    var window$1 = window_1;
50332    var parseId3Frames = parseId3.parseId3Frames;
50333    /**
50334     * Parses an MP4 initialization segment and extracts the timescale
50335     * values for any declared tracks. Timescale values indicate the
50336     * number of clock ticks per second to assume for time-based values
50337     * elsewhere in the MP4.
50338     *
50339     * To determine the start time of an MP4, you need two pieces of
50340     * information: the timescale unit and the earliest base media decode
50341     * time. Multiple timescales can be specified within an MP4 but the
50342     * base media decode time is always expressed in the timescale from
50343     * the media header box for the track:
50344     * ```
50345     * moov > trak > mdia > mdhd.timescale
50346     * ```
50347     * @param init {Uint8Array} the bytes of the init segment
50348     * @return {object} a hash of track ids to timescale values or null if
50349     * the init segment is malformed.
50350     */
50351
50352    timescale = function (init) {
50353      var result = {},
50354        traks = findBox(init, ['moov', 'trak']); // mdhd timescale
50355
50356      return traks.reduce(function (result, trak) {
50357        var tkhd, version, index, id, mdhd;
50358        tkhd = findBox(trak, ['tkhd'])[0];
50359        if (!tkhd) {
50360          return null;
50361        }
50362        version = tkhd[0];
50363        index = version === 0 ? 12 : 20;
50364        id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
50365        mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
50366        if (!mdhd) {
50367          return null;
50368        }
50369        version = mdhd[0];
50370        index = version === 0 ? 12 : 20;
50371        result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50372        return result;
50373      }, result);
50374    };
50375    /**
50376     * Determine the base media decode start time, in seconds, for an MP4
50377     * fragment. If multiple fragments are specified, the earliest time is
50378     * returned.
50379     *
50380     * The base media decode time can be parsed from track fragment
50381     * metadata:
50382     * ```
50383     * moof > traf > tfdt.baseMediaDecodeTime
50384     * ```
50385     * It requires the timescale value from the mdhd to interpret.
50386     *
50387     * @param timescale {object} a hash of track ids to timescale values.
50388     * @return {number} the earliest base media decode start time for the
50389     * fragment, in seconds
50390     */
50391
50392    startTime = function (timescale, fragment) {
50393      var trafs; // we need info from two childrend of each track fragment box
50394
50395      trafs = findBox(fragment, ['moof', 'traf']); // determine the start times for each track
50396
50397      var lowestTime = trafs.reduce(function (acc, traf) {
50398        var tfhd = findBox(traf, ['tfhd'])[0]; // get the track id from the tfhd
50399
50400        var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
50401
50402        var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
50403
50404        var tfdt = findBox(traf, ['tfdt'])[0];
50405        var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
50406        var baseTime; // version 1 is 64 bit
50407
50408        if (tfdt[0] === 1) {
50409          baseTime = getUint64(tfdt.subarray(4, 12));
50410        } else {
50411          baseTime = dv.getUint32(4);
50412        }
vendor: 16,117 bytes, lines 50412-50800
50412 // convert base time to seconds if it is a valid number.
50413
50414        let seconds;
50415        if (typeof baseTime === 'bigint') {
50416          seconds = baseTime / window$1.BigInt(scale);
50417        } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
50418          seconds = baseTime / scale;
50419        }
50420        if (seconds < Number.MAX_SAFE_INTEGER) {
50421          seconds = Number(seconds);
50422        }
50423        if (seconds < acc) {
50424          acc = seconds;
50425        }
50426        return acc;
50427      }, Infinity);
50428      return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
50429    };
50430    /**
50431     * Determine the composition start, in seconds, for an MP4
50432     * fragment.
50433     *
50434     * The composition start time of a fragment can be calculated using the base
50435     * media decode time, composition time offset, and timescale, as follows:
50436     *
50437     * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
50438     *
50439     * All of the aforementioned information is contained within a media fragment's
50440     * `traf` box, except for timescale info, which comes from the initialization
50441     * segment, so a track id (also contained within a `traf`) is also necessary to
50442     * associate it with a timescale
50443     *
50444     *
50445     * @param timescales {object} - a hash of track ids to timescale values.
50446     * @param fragment {Unit8Array} - the bytes of a media segment
50447     * @return {number} the composition start time for the fragment, in seconds
50448     **/
50449
50450    compositionStartTime = function (timescales, fragment) {
50451      var trafBoxes = findBox(fragment, ['moof', 'traf']);
50452      var baseMediaDecodeTime = 0;
50453      var compositionTimeOffset = 0;
50454      var trackId;
50455      if (trafBoxes && trafBoxes.length) {
50456        // The spec states that track run samples contained within a `traf` box are contiguous, but
50457        // it does not explicitly state whether the `traf` boxes themselves are contiguous.
50458        // We will assume that they are, so we only need the first to calculate start time.
50459        var tfhd = findBox(trafBoxes[0], ['tfhd'])[0];
50460        var trun = findBox(trafBoxes[0], ['trun'])[0];
50461        var tfdt = findBox(trafBoxes[0], ['tfdt'])[0];
50462        if (tfhd) {
50463          var parsedTfhd = parseTfhd(tfhd);
50464          trackId = parsedTfhd.trackId;
50465        }
50466        if (tfdt) {
50467          var parsedTfdt = parseTfdt(tfdt);
50468          baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
50469        }
50470        if (trun) {
50471          var parsedTrun = parseTrun(trun);
50472          if (parsedTrun.samples && parsedTrun.samples.length) {
50473            compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
50474          }
50475        }
50476      } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
50477      // specified.
50478
50479      var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
50480
50481      if (typeof baseMediaDecodeTime === 'bigint') {
50482        compositionTimeOffset = window$1.BigInt(compositionTimeOffset);
50483        timescale = window$1.BigInt(timescale);
50484      }
50485      var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
50486      if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
50487        result = Number(result);
50488      }
50489      return result;
50490    };
50491    /**
50492      * Find the trackIds of the video tracks in this source.
50493      * Found by parsing the Handler Reference and Track Header Boxes:
50494      *   moov > trak > mdia > hdlr
50495      *   moov > trak > tkhd
50496      *
50497      * @param {Uint8Array} init - The bytes of the init segment for this source
50498      * @return {Number[]} A list of trackIds
50499      *
50500      * @see ISO-BMFF-12/2015, Section 8.4.3
50501     **/
50502
50503    getVideoTrackIds = function (init) {
50504      var traks = findBox(init, ['moov', 'trak']);
50505      var videoTrackIds = [];
50506      traks.forEach(function (trak) {
50507        var hdlrs = findBox(trak, ['mdia', 'hdlr']);
50508        var tkhds = findBox(trak, ['tkhd']);
50509        hdlrs.forEach(function (hdlr, index) {
50510          var handlerType = parseType$1(hdlr.subarray(8, 12));
50511          var tkhd = tkhds[index];
50512          var view;
50513          var version;
50514          var trackId;
50515          if (handlerType === 'vide') {
50516            view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50517            version = view.getUint8(0);
50518            trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
50519            videoTrackIds.push(trackId);
50520          }
50521        });
50522      });
50523      return videoTrackIds;
50524    };
50525    getTimescaleFromMediaHeader = function (mdhd) {
50526      // mdhd is a FullBox, meaning it will have its own version as the first byte
50527      var version = mdhd[0];
50528      var index = version === 0 ? 12 : 20;
50529      return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50530    };
50531    /**
50532     * Get all the video, audio, and hint tracks from a non fragmented
50533     * mp4 segment
50534     */
50535
50536    getTracks = function (init) {
50537      var traks = findBox(init, ['moov', 'trak']);
50538      var tracks = [];
50539      traks.forEach(function (trak) {
50540        var track = {};
50541        var tkhd = findBox(trak, ['tkhd'])[0];
50542        var view, tkhdVersion; // id
50543
50544        if (tkhd) {
50545          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50546          tkhdVersion = view.getUint8(0);
50547          track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
50548        }
50549        var hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; // type
50550
50551        if (hdlr) {
50552          var type = parseType$1(hdlr.subarray(8, 12));
50553          if (type === 'vide') {
50554            track.type = 'video';
50555          } else if (type === 'soun') {
50556            track.type = 'audio';
50557          } else {
50558            track.type = type;
50559          }
50560        } // codec
50561
50562        var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
50563        if (stsd) {
50564          var sampleDescriptions = stsd.subarray(8); // gives the codec type string
50565
50566          track.codec = parseType$1(sampleDescriptions.subarray(4, 8));
50567          var codecBox = findBox(sampleDescriptions, [track.codec])[0];
50568          var codecConfig, codecConfigType;
50569          if (codecBox) {
50570            // https://tools.ietf.org/html/rfc6381#section-3.3
50571            if (/^[asm]vc[1-9]$/i.test(track.codec)) {
50572              // we don't need anything but the "config" parameter of the
50573              // avc1 codecBox
50574              codecConfig = codecBox.subarray(78);
50575              codecConfigType = parseType$1(codecConfig.subarray(4, 8));
50576              if (codecConfigType === 'avcC' && codecConfig.length > 11) {
50577                track.codec += '.'; // left padded with zeroes for single digit hex
50578                // profile idc
50579
50580                track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
50581
50582                track.codec += toHexString(codecConfig[10]); // level idc
50583
50584                track.codec += toHexString(codecConfig[11]);
50585              } else {
50586                // TODO: show a warning that we couldn't parse the codec
50587                // and are using the default
50588                track.codec = 'avc1.4d400d';
50589              }
50590            } else if (/^mp4[a,v]$/i.test(track.codec)) {
50591              // we do not need anything but the streamDescriptor of the mp4a codecBox
50592              codecConfig = codecBox.subarray(28);
50593              codecConfigType = parseType$1(codecConfig.subarray(4, 8));
50594              if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
50595                track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
50596
50597                track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
50598              } else {
50599                // TODO: show a warning that we couldn't parse the codec
50600                // and are using the default
50601                track.codec = 'mp4a.40.2';
50602              }
50603            } else {
50604              // flac, opus, etc
50605              track.codec = track.codec.toLowerCase();
50606            }
50607          }
50608        }
50609        var mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
50610        if (mdhd) {
50611          track.timescale = getTimescaleFromMediaHeader(mdhd);
50612        }
50613        tracks.push(track);
50614      });
50615      return tracks;
50616    };
50617    /**
50618     * Returns an array of emsg ID3 data from the provided segmentData.
50619     * An offset can also be provided as the Latest Arrival Time to calculate 
50620     * the Event Start Time of v0 EMSG boxes. 
50621     * See: https://dashif-documents.azurewebsites.net/Events/master/event.html#Inband-event-timing
50622     * 
50623     * @param {Uint8Array} segmentData the segment byte array.
50624     * @param {number} offset the segment start time or Latest Arrival Time, 
50625     * @return {Object[]} an array of ID3 parsed from EMSG boxes
50626     */
50627
50628    getEmsgID3 = function (segmentData, offset = 0) {
50629      var emsgBoxes = findBox(segmentData, ['emsg']);
50630      return emsgBoxes.map(data => {
50631        var parsedBox = emsg.parseEmsgBox(new Uint8Array(data));
50632        var parsedId3Frames = parseId3Frames(parsedBox.message_data);
50633        return {
50634          cueTime: emsg.scaleTime(parsedBox.presentation_time, parsedBox.timescale, parsedBox.presentation_time_delta, offset),
50635          duration: emsg.scaleTime(parsedBox.event_duration, parsedBox.timescale),
50636          frames: parsedId3Frames
50637        };
50638      });
50639    };
50640    var probe$2 = {
50641      // export mp4 inspector's findBox and parseType for backwards compatibility
50642      findBox: findBox,
50643      parseType: parseType$1,
50644      timescale: timescale,
50645      startTime: startTime,
50646      compositionStartTime: compositionStartTime,
50647      videoTrackIds: getVideoTrackIds,
50648      tracks: getTracks,
50649      getTimescaleFromMediaHeader: getTimescaleFromMediaHeader,
50650      getEmsgID3: getEmsgID3
50651    };
50652    /**
50653     * mux.js
50654     *
50655     * Copyright (c) Brightcove
50656     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50657     *
50658     * Utilities to detect basic properties and metadata about TS Segments.
50659     */
50660
50661    var StreamTypes$1 = streamTypes;
50662    var parsePid = function (packet) {
50663      var pid = packet[1] & 0x1f;
50664      pid <<= 8;
50665      pid |= packet[2];
50666      return pid;
50667    };
50668    var parsePayloadUnitStartIndicator = function (packet) {
50669      return !!(packet[1] & 0x40);
50670    };
50671    var parseAdaptionField = function (packet) {
50672      var offset = 0; // if an adaption field is present, its length is specified by the
50673      // fifth byte of the TS packet header. The adaptation field is
50674      // used to add stuffing to PES packets that don't fill a complete
50675      // TS packet, and to specify some forms of timing and control data
50676      // that we do not currently use.
50677
50678      if ((packet[3] & 0x30) >>> 4 > 0x01) {
50679        offset += packet[4] + 1;
50680      }
50681      return offset;
50682    };
50683    var parseType = function (packet, pmtPid) {
50684      var pid = parsePid(packet);
50685      if (pid === 0) {
50686        return 'pat';
50687      } else if (pid === pmtPid) {
50688        return 'pmt';
50689      } else if (pmtPid) {
50690        return 'pes';
50691      }
50692      return null;
50693    };
50694    var parsePat = function (packet) {
50695      var pusi = parsePayloadUnitStartIndicator(packet);
50696      var offset = 4 + parseAdaptionField(packet);
50697      if (pusi) {
50698        offset += packet[offset] + 1;
50699      }
50700      return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
50701    };
50702    var parsePmt = function (packet) {
50703      var programMapTable = {};
50704      var pusi = parsePayloadUnitStartIndicator(packet);
50705      var payloadOffset = 4 + parseAdaptionField(packet);
50706      if (pusi) {
50707        payloadOffset += packet[payloadOffset] + 1;
50708      } // PMTs can be sent ahead of the time when they should actually
50709      // take effect. We don't believe this should ever be the case
50710      // for HLS but we'll ignore "forward" PMT declarations if we see
50711      // them. Future PMT declarations have the current_next_indicator
50712      // set to zero.
50713
50714      if (!(packet[payloadOffset + 5] & 0x01)) {
50715        return;
50716      }
50717      var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
50718
50719      sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
50720      tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
50721      // long the program info descriptors are
50722
50723      programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
50724
50725      var offset = 12 + programInfoLength;
50726      while (offset < tableEnd) {
50727        var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
50728
50729        programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
50730        // skip past the elementary stream descriptors, if present
50731
50732        offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
50733      }
50734      return programMapTable;
50735    };
50736    var parsePesType = function (packet, programMapTable) {
50737      var pid = parsePid(packet);
50738      var type = programMapTable[pid];
50739      switch (type) {
50740        case StreamTypes$1.H264_STREAM_TYPE:
50741          return 'video';
50742        case StreamTypes$1.ADTS_STREAM_TYPE:
50743          return 'audio';
50744        case StreamTypes$1.METADATA_STREAM_TYPE:
50745          return 'timed-metadata';
50746        default:
50747          return null;
50748      }
50749    };
50750    var parsePesTime = function (packet) {
50751      var pusi = parsePayloadUnitStartIndicator(packet);
50752      if (!pusi) {
50753        return null;
50754      }
50755      var offset = 4 + parseAdaptionField(packet);
50756      if (offset >= packet.byteLength) {
50757        // From the H 222.0 MPEG-TS spec
50758        // "For transport stream packets carrying PES packets, stuffing is needed when there
50759        //  is insufficient PES packet data to completely fill the transport stream packet
50760        //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
50761        //  the sum of the lengths of the data elements in it, so that the payload bytes
50762        //  remaining after the adaptation field exactly accommodates the available PES packet
50763        //  data."
50764        //
50765        // If the offset is >= the length of the packet, then the packet contains no data
50766        // and instead is just adaption field stuffing bytes
50767        return null;
50768      }
50769      var pes = null;
50770      var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
50771      // and a DTS value. Determine what combination of values is
50772      // available to work with.
50773
50774      ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
50775      // performs all bitwise operations on 32-bit integers but javascript
50776      // supports a much greater range (52-bits) of integer using standard
50777      // mathematical operations.
50778      // We construct a 31-bit value using bitwise operators over the 31
50779      // most significant bits and then multiply by 4 (equal to a left-shift
50780      // of 2) before we add the final 2 least significant bits of the
50781      // timestamp (equal to an OR.)
50782
50783      if (ptsDtsFlags & 0xC0) {
50784        pes = {}; // the PTS and DTS are not written out directly. For information
50785        // on how they are encoded, see
50786        // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
50787
50788        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;
50789        pes.pts *= 4; // Left shift by 2
50790
50791        pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
50792
50793        pes.dts = pes.pts;
50794        if (ptsDtsFlags & 0x40) {
50795          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;
50796          pes.dts *= 4; // Left shift by 2
50797
50798          pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
50799        }
50800      }
vendor: 31,773 bytes, lines 50801-51702
50801
50802      return pes;
50803    };
50804    var parseNalUnitType = function (type) {
50805      switch (type) {
50806        case 0x05:
50807          return 'slice_layer_without_partitioning_rbsp_idr';
50808        case 0x06:
50809          return 'sei_rbsp';
50810        case 0x07:
50811          return 'seq_parameter_set_rbsp';
50812        case 0x08:
50813          return 'pic_parameter_set_rbsp';
50814        case 0x09:
50815          return 'access_unit_delimiter_rbsp';
50816        default:
50817          return null;
50818      }
50819    };
50820    var videoPacketContainsKeyFrame = function (packet) {
50821      var offset = 4 + parseAdaptionField(packet);
50822      var frameBuffer = packet.subarray(offset);
50823      var frameI = 0;
50824      var frameSyncPoint = 0;
50825      var foundKeyFrame = false;
50826      var nalType; // advance the sync point to a NAL start, if necessary
50827
50828      for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
50829        if (frameBuffer[frameSyncPoint + 2] === 1) {
50830          // the sync point is properly aligned
50831          frameI = frameSyncPoint + 5;
50832          break;
50833        }
50834      }
50835      while (frameI < frameBuffer.byteLength) {
50836        // look at the current byte to determine if we've hit the end of
50837        // a NAL unit boundary
50838        switch (frameBuffer[frameI]) {
50839          case 0:
50840            // skip past non-sync sequences
50841            if (frameBuffer[frameI - 1] !== 0) {
50842              frameI += 2;
50843              break;
50844            } else if (frameBuffer[frameI - 2] !== 0) {
50845              frameI++;
50846              break;
50847            }
50848            if (frameSyncPoint + 3 !== frameI - 2) {
50849              nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50850              if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50851                foundKeyFrame = true;
50852              }
50853            } // drop trailing zeroes
50854
50855            do {
50856              frameI++;
50857            } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
50858            frameSyncPoint = frameI - 2;
50859            frameI += 3;
50860            break;
50861          case 1:
50862            // skip past non-sync sequences
50863            if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
50864              frameI += 3;
50865              break;
50866            }
50867            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50868            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50869              foundKeyFrame = true;
50870            }
50871            frameSyncPoint = frameI - 2;
50872            frameI += 3;
50873            break;
50874          default:
50875            // the current byte isn't a one or zero, so it cannot be part
50876            // of a sync sequence
50877            frameI += 3;
50878            break;
50879        }
50880      }
50881      frameBuffer = frameBuffer.subarray(frameSyncPoint);
50882      frameI -= frameSyncPoint;
50883      frameSyncPoint = 0; // parse the final nal
50884
50885      if (frameBuffer && frameBuffer.byteLength > 3) {
50886        nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50887        if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50888          foundKeyFrame = true;
50889        }
50890      }
50891      return foundKeyFrame;
50892    };
50893    var probe$1 = {
50894      parseType: parseType,
50895      parsePat: parsePat,
50896      parsePmt: parsePmt,
50897      parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
50898      parsePesType: parsePesType,
50899      parsePesTime: parsePesTime,
50900      videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
50901    };
50902    /**
50903     * mux.js
50904     *
50905     * Copyright (c) Brightcove
50906     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50907     *
50908     * Parse mpeg2 transport stream packets to extract basic timing information
50909     */
50910
50911    var StreamTypes = streamTypes;
50912    var handleRollover = timestampRolloverStream.handleRollover;
50913    var probe = {};
50914    probe.ts = probe$1;
50915    probe.aac = utils;
50916    var ONE_SECOND_IN_TS = clock$2.ONE_SECOND_IN_TS;
50917    var MP2T_PACKET_LENGTH = 188,
50918      // bytes
50919      SYNC_BYTE = 0x47;
50920    /**
50921     * walks through segment data looking for pat and pmt packets to parse out
50922     * program map table information
50923     */
50924
50925    var parsePsi_ = function (bytes, pmt) {
50926      var startIndex = 0,
50927        endIndex = MP2T_PACKET_LENGTH,
50928        packet,
50929        type;
50930      while (endIndex < bytes.byteLength) {
50931        // Look for a pair of start and end sync bytes in the data..
50932        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
50933          // We found a packet
50934          packet = bytes.subarray(startIndex, endIndex);
50935          type = probe.ts.parseType(packet, pmt.pid);
50936          switch (type) {
50937            case 'pat':
50938              pmt.pid = probe.ts.parsePat(packet);
50939              break;
50940            case 'pmt':
50941              var table = probe.ts.parsePmt(packet);
50942              pmt.table = pmt.table || {};
50943              Object.keys(table).forEach(function (key) {
50944                pmt.table[key] = table[key];
50945              });
50946              break;
50947          }
50948          startIndex += MP2T_PACKET_LENGTH;
50949          endIndex += MP2T_PACKET_LENGTH;
50950          continue;
50951        } // If we get here, we have somehow become de-synchronized and we need to step
50952        // forward one byte at a time until we find a pair of sync bytes that denote
50953        // a packet
50954
50955        startIndex++;
50956        endIndex++;
50957      }
50958    };
50959    /**
50960     * walks through the segment data from the start and end to get timing information
50961     * for the first and last audio pes packets
50962     */
50963
50964    var parseAudioPes_ = function (bytes, pmt, result) {
50965      var startIndex = 0,
50966        endIndex = MP2T_PACKET_LENGTH,
50967        packet,
50968        type,
50969        pesType,
50970        pusi,
50971        parsed;
50972      var endLoop = false; // Start walking from start of segment to get first audio packet
50973
50974      while (endIndex <= bytes.byteLength) {
50975        // Look for a pair of start and end sync bytes in the data..
50976        if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
50977          // We found a packet
50978          packet = bytes.subarray(startIndex, endIndex);
50979          type = probe.ts.parseType(packet, pmt.pid);
50980          switch (type) {
50981            case 'pes':
50982              pesType = probe.ts.parsePesType(packet, pmt.table);
50983              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
50984              if (pesType === 'audio' && pusi) {
50985                parsed = probe.ts.parsePesTime(packet);
50986                if (parsed) {
50987                  parsed.type = 'audio';
50988                  result.audio.push(parsed);
50989                  endLoop = true;
50990                }
50991              }
50992              break;
50993          }
50994          if (endLoop) {
50995            break;
50996          }
50997          startIndex += MP2T_PACKET_LENGTH;
50998          endIndex += MP2T_PACKET_LENGTH;
50999          continue;
51000        } // If we get here, we have somehow become de-synchronized and we need to step
51001        // forward one byte at a time until we find a pair of sync bytes that denote
51002        // a packet
51003
51004        startIndex++;
51005        endIndex++;
51006      } // Start walking from end of segment to get last audio packet
51007
51008      endIndex = bytes.byteLength;
51009      startIndex = endIndex - MP2T_PACKET_LENGTH;
51010      endLoop = false;
51011      while (startIndex >= 0) {
51012        // Look for a pair of start and end sync bytes in the data..
51013        if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
51014          // We found a packet
51015          packet = bytes.subarray(startIndex, endIndex);
51016          type = probe.ts.parseType(packet, pmt.pid);
51017          switch (type) {
51018            case 'pes':
51019              pesType = probe.ts.parsePesType(packet, pmt.table);
51020              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51021              if (pesType === 'audio' && pusi) {
51022                parsed = probe.ts.parsePesTime(packet);
51023                if (parsed) {
51024                  parsed.type = 'audio';
51025                  result.audio.push(parsed);
51026                  endLoop = true;
51027                }
51028              }
51029              break;
51030          }
51031          if (endLoop) {
51032            break;
51033          }
51034          startIndex -= MP2T_PACKET_LENGTH;
51035          endIndex -= MP2T_PACKET_LENGTH;
51036          continue;
51037        } // If we get here, we have somehow become de-synchronized and we need to step
51038        // forward one byte at a time until we find a pair of sync bytes that denote
51039        // a packet
51040
51041        startIndex--;
51042        endIndex--;
51043      }
51044    };
51045    /**
51046     * walks through the segment data from the start and end to get timing information
51047     * for the first and last video pes packets as well as timing information for the first
51048     * key frame.
51049     */
51050
51051    var parseVideoPes_ = function (bytes, pmt, result) {
51052      var startIndex = 0,
51053        endIndex = MP2T_PACKET_LENGTH,
51054        packet,
51055        type,
51056        pesType,
51057        pusi,
51058        parsed,
51059        frame,
51060        i,
51061        pes;
51062      var endLoop = false;
51063      var currentFrame = {
51064        data: [],
51065        size: 0
51066      }; // Start walking from start of segment to get first video packet
51067
51068      while (endIndex < bytes.byteLength) {
51069        // Look for a pair of start and end sync bytes in the data..
51070        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51071          // We found a packet
51072          packet = bytes.subarray(startIndex, endIndex);
51073          type = probe.ts.parseType(packet, pmt.pid);
51074          switch (type) {
51075            case 'pes':
51076              pesType = probe.ts.parsePesType(packet, pmt.table);
51077              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51078              if (pesType === 'video') {
51079                if (pusi && !endLoop) {
51080                  parsed = probe.ts.parsePesTime(packet);
51081                  if (parsed) {
51082                    parsed.type = 'video';
51083                    result.video.push(parsed);
51084                    endLoop = true;
51085                  }
51086                }
51087                if (!result.firstKeyFrame) {
51088                  if (pusi) {
51089                    if (currentFrame.size !== 0) {
51090                      frame = new Uint8Array(currentFrame.size);
51091                      i = 0;
51092                      while (currentFrame.data.length) {
51093                        pes = currentFrame.data.shift();
51094                        frame.set(pes, i);
51095                        i += pes.byteLength;
51096                      }
51097                      if (probe.ts.videoPacketContainsKeyFrame(frame)) {
51098                        var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
51099                        // the keyframe seems to work fine with HLS playback
51100                        // and definitely preferable to a crash with TypeError...
51101
51102                        if (firstKeyFrame) {
51103                          result.firstKeyFrame = firstKeyFrame;
51104                          result.firstKeyFrame.type = 'video';
51105                        } else {
51106                          // eslint-disable-next-line
51107                          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.');
51108                        }
51109                      }
51110                      currentFrame.size = 0;
51111                    }
51112                  }
51113                  currentFrame.data.push(packet);
51114                  currentFrame.size += packet.byteLength;
51115                }
51116              }
51117              break;
51118          }
51119          if (endLoop && result.firstKeyFrame) {
51120            break;
51121          }
51122          startIndex += MP2T_PACKET_LENGTH;
51123          endIndex += MP2T_PACKET_LENGTH;
51124          continue;
51125        } // If we get here, we have somehow become de-synchronized and we need to step
51126        // forward one byte at a time until we find a pair of sync bytes that denote
51127        // a packet
51128
51129        startIndex++;
51130        endIndex++;
51131      } // Start walking from end of segment to get last video packet
51132
51133      endIndex = bytes.byteLength;
51134      startIndex = endIndex - MP2T_PACKET_LENGTH;
51135      endLoop = false;
51136      while (startIndex >= 0) {
51137        // Look for a pair of start and end sync bytes in the data..
51138        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51139          // We found a packet
51140          packet = bytes.subarray(startIndex, endIndex);
51141          type = probe.ts.parseType(packet, pmt.pid);
51142          switch (type) {
51143            case 'pes':
51144              pesType = probe.ts.parsePesType(packet, pmt.table);
51145              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51146              if (pesType === 'video' && pusi) {
51147                parsed = probe.ts.parsePesTime(packet);
51148                if (parsed) {
51149                  parsed.type = 'video';
51150                  result.video.push(parsed);
51151                  endLoop = true;
51152                }
51153              }
51154              break;
51155          }
51156          if (endLoop) {
51157            break;
51158          }
51159          startIndex -= MP2T_PACKET_LENGTH;
51160          endIndex -= MP2T_PACKET_LENGTH;
51161          continue;
51162        } // If we get here, we have somehow become de-synchronized and we need to step
51163        // forward one byte at a time until we find a pair of sync bytes that denote
51164        // a packet
51165
51166        startIndex--;
51167        endIndex--;
51168      }
51169    };
51170    /**
51171     * Adjusts the timestamp information for the segment to account for
51172     * rollover and convert to seconds based on pes packet timescale (90khz clock)
51173     */
51174
51175    var adjustTimestamp_ = function (segmentInfo, baseTimestamp) {
51176      if (segmentInfo.audio && segmentInfo.audio.length) {
51177        var audioBaseTimestamp = baseTimestamp;
51178        if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
51179          audioBaseTimestamp = segmentInfo.audio[0].dts;
51180        }
51181        segmentInfo.audio.forEach(function (info) {
51182          info.dts = handleRollover(info.dts, audioBaseTimestamp);
51183          info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
51184
51185          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51186          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51187        });
51188      }
51189      if (segmentInfo.video && segmentInfo.video.length) {
51190        var videoBaseTimestamp = baseTimestamp;
51191        if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
51192          videoBaseTimestamp = segmentInfo.video[0].dts;
51193        }
51194        segmentInfo.video.forEach(function (info) {
51195          info.dts = handleRollover(info.dts, videoBaseTimestamp);
51196          info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
51197
51198          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51199          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51200        });
51201        if (segmentInfo.firstKeyFrame) {
51202          var frame = segmentInfo.firstKeyFrame;
51203          frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
51204          frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
51205
51206          frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
51207          frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
51208        }
51209      }
51210    };
51211    /**
51212     * inspects the aac data stream for start and end time information
51213     */
51214
51215    var inspectAac_ = function (bytes) {
51216      var endLoop = false,
51217        audioCount = 0,
51218        sampleRate = null,
51219        timestamp = null,
51220        frameSize = 0,
51221        byteIndex = 0,
51222        packet;
51223      while (bytes.length - byteIndex >= 3) {
51224        var type = probe.aac.parseType(bytes, byteIndex);
51225        switch (type) {
51226          case 'timed-metadata':
51227            // Exit early because we don't have enough to parse
51228            // the ID3 tag header
51229            if (bytes.length - byteIndex < 10) {
51230              endLoop = true;
51231              break;
51232            }
51233            frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51234            // to emit a full packet
51235
51236            if (frameSize > bytes.length) {
51237              endLoop = true;
51238              break;
51239            }
51240            if (timestamp === null) {
51241              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51242              timestamp = probe.aac.parseAacTimestamp(packet);
51243            }
51244            byteIndex += frameSize;
51245            break;
51246          case 'audio':
51247            // Exit early because we don't have enough to parse
51248            // the ADTS frame header
51249            if (bytes.length - byteIndex < 7) {
51250              endLoop = true;
51251              break;
51252            }
51253            frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51254            // to emit a full packet
51255
51256            if (frameSize > bytes.length) {
51257              endLoop = true;
51258              break;
51259            }
51260            if (sampleRate === null) {
51261              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51262              sampleRate = probe.aac.parseSampleRate(packet);
51263            }
51264            audioCount++;
51265            byteIndex += frameSize;
51266            break;
51267          default:
51268            byteIndex++;
51269            break;
51270        }
51271        if (endLoop) {
51272          return null;
51273        }
51274      }
51275      if (sampleRate === null || timestamp === null) {
51276        return null;
51277      }
51278      var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
51279      var result = {
51280        audio: [{
51281          type: 'audio',
51282          dts: timestamp,
51283          pts: timestamp
51284        }, {
51285          type: 'audio',
51286          dts: timestamp + audioCount * 1024 * audioTimescale,
51287          pts: timestamp + audioCount * 1024 * audioTimescale
51288        }]
51289      };
51290      return result;
51291    };
51292    /**
51293     * inspects the transport stream segment data for start and end time information
51294     * of the audio and video tracks (when present) as well as the first key frame's
51295     * start time.
51296     */
51297
51298    var inspectTs_ = function (bytes) {
51299      var pmt = {
51300        pid: null,
51301        table: null
51302      };
51303      var result = {};
51304      parsePsi_(bytes, pmt);
51305      for (var pid in pmt.table) {
51306        if (pmt.table.hasOwnProperty(pid)) {
51307          var type = pmt.table[pid];
51308          switch (type) {
51309            case StreamTypes.H264_STREAM_TYPE:
51310              result.video = [];
51311              parseVideoPes_(bytes, pmt, result);
51312              if (result.video.length === 0) {
51313                delete result.video;
51314              }
51315              break;
51316            case StreamTypes.ADTS_STREAM_TYPE:
51317              result.audio = [];
51318              parseAudioPes_(bytes, pmt, result);
51319              if (result.audio.length === 0) {
51320                delete result.audio;
51321              }
51322              break;
51323          }
51324        }
51325      }
51326      return result;
51327    };
51328    /**
51329     * Inspects segment byte data and returns an object with start and end timing information
51330     *
51331     * @param {Uint8Array} bytes The segment byte data
51332     * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
51333     *  timestamps for rollover. This value must be in 90khz clock.
51334     * @return {Object} Object containing start and end frame timing info of segment.
51335     */
51336
51337    var inspect = function (bytes, baseTimestamp) {
51338      var isAacData = probe.aac.isLikelyAacData(bytes);
51339      var result;
51340      if (isAacData) {
51341        result = inspectAac_(bytes);
51342      } else {
51343        result = inspectTs_(bytes);
51344      }
51345      if (!result || !result.audio && !result.video) {
51346        return null;
51347      }
51348      adjustTimestamp_(result, baseTimestamp);
51349      return result;
51350    };
51351    var tsInspector = {
51352      inspect: inspect,
51353      parseAudioPes_: parseAudioPes_
51354    };
51355    /* global self */
51356
51357    /**
51358     * Re-emits transmuxer events by converting them into messages to the
51359     * world outside the worker.
51360     *
51361     * @param {Object} transmuxer the transmuxer to wire events on
51362     * @private
51363     */
51364
51365    const wireTransmuxerEvents = function (self, transmuxer) {
51366      transmuxer.on('data', function (segment) {
51367        // transfer ownership of the underlying ArrayBuffer
51368        // instead of doing a copy to save memory
51369        // ArrayBuffers are transferable but generic TypedArrays are not
51370        // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
51371        const initArray = segment.initSegment;
51372        segment.initSegment = {
51373          data: initArray.buffer,
51374          byteOffset: initArray.byteOffset,
51375          byteLength: initArray.byteLength
51376        };
51377        const typedArray = segment.data;
51378        segment.data = typedArray.buffer;
51379        self.postMessage({
51380          action: 'data',
51381          segment,
51382          byteOffset: typedArray.byteOffset,
51383          byteLength: typedArray.byteLength
51384        }, [segment.data]);
51385      });
51386      transmuxer.on('done', function (data) {
51387        self.postMessage({
51388          action: 'done'
51389        });
51390      });
51391      transmuxer.on('gopInfo', function (gopInfo) {
51392        self.postMessage({
51393          action: 'gopInfo',
51394          gopInfo
51395        });
51396      });
51397      transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
51398        const videoSegmentTimingInfo = {
51399          start: {
51400            decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
51401            presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
51402          },
51403          end: {
51404            decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
51405            presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
51406          },
51407          baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51408        };
51409        if (timingInfo.prependedContentDuration) {
51410          videoSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
51411        }
51412        self.postMessage({
51413          action: 'videoSegmentTimingInfo',
51414          videoSegmentTimingInfo
51415        });
51416      });
51417      transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
51418        // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
51419        const audioSegmentTimingInfo = {
51420          start: {
51421            decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
51422            presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
51423          },
51424          end: {
51425            decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
51426            presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
51427          },
51428          baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51429        };
51430        if (timingInfo.prependedContentDuration) {
51431          audioSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
51432        }
51433        self.postMessage({
51434          action: 'audioSegmentTimingInfo',
51435          audioSegmentTimingInfo
51436        });
51437      });
51438      transmuxer.on('id3Frame', function (id3Frame) {
51439        self.postMessage({
51440          action: 'id3Frame',
51441          id3Frame
51442        });
51443      });
51444      transmuxer.on('caption', function (caption) {
51445        self.postMessage({
51446          action: 'caption',
51447          caption
51448        });
51449      });
51450      transmuxer.on('trackinfo', function (trackInfo) {
51451        self.postMessage({
51452          action: 'trackinfo',
51453          trackInfo
51454        });
51455      });
51456      transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
51457        // convert to video TS since we prioritize video time over audio
51458        self.postMessage({
51459          action: 'audioTimingInfo',
51460          audioTimingInfo: {
51461            start: clock$2.videoTsToSeconds(audioTimingInfo.start),
51462            end: clock$2.videoTsToSeconds(audioTimingInfo.end)
51463          }
51464        });
51465      });
51466      transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
51467        self.postMessage({
51468          action: 'videoTimingInfo',
51469          videoTimingInfo: {
51470            start: clock$2.videoTsToSeconds(videoTimingInfo.start),
51471            end: clock$2.videoTsToSeconds(videoTimingInfo.end)
51472          }
51473        });
51474      });
51475      transmuxer.on('log', function (log) {
51476        self.postMessage({
51477          action: 'log',
51478          log
51479        });
51480      });
51481    };
51482    /**
51483     * All incoming messages route through this hash. If no function exists
51484     * to handle an incoming message, then we ignore the message.
51485     *
51486     * @class MessageHandlers
51487     * @param {Object} options the options to initialize with
51488     */
51489
51490    class MessageHandlers {
51491      constructor(self, options) {
51492        this.options = options || {};
51493        this.self = self;
51494        this.init();
51495      }
51496      /**
51497       * initialize our web worker and wire all the events.
51498       */
51499
51500      init() {
51501        if (this.transmuxer) {
51502          this.transmuxer.dispose();
51503        }
51504        this.transmuxer = new transmuxer.Transmuxer(this.options);
51505        wireTransmuxerEvents(this.self, this.transmuxer);
51506      }
51507      pushMp4Captions(data) {
51508        if (!this.captionParser) {
51509          this.captionParser = new captionParser();
51510          this.captionParser.init();
51511        }
51512        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51513        const parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
51514        this.self.postMessage({
51515          action: 'mp4Captions',
51516          captions: parsed && parsed.captions || [],
51517          logs: parsed && parsed.logs || [],
51518          data: segment.buffer
51519        }, [segment.buffer]);
51520      }
51521      probeMp4StartTime({
51522        timescales,
51523        data
51524      }) {
51525        const startTime = probe$2.startTime(timescales, data);
51526        this.self.postMessage({
51527          action: 'probeMp4StartTime',
51528          startTime,
51529          data
51530        }, [data.buffer]);
51531      }
51532      probeMp4Tracks({
51533        data
51534      }) {
51535        const tracks = probe$2.tracks(data);
51536        this.self.postMessage({
51537          action: 'probeMp4Tracks',
51538          tracks,
51539          data
51540        }, [data.buffer]);
51541      }
51542      /**
51543       * Probes an mp4 segment for EMSG boxes containing ID3 data.
51544       * https://aomediacodec.github.io/id3-emsg/
51545       *
51546       * @param {Uint8Array} data segment data
51547       * @param {number} offset segment start time
51548       * @return {Object[]} an array of ID3 frames
51549       */
51550
51551      probeEmsgID3({
51552        data,
51553        offset
51554      }) {
51555        const id3Frames = probe$2.getEmsgID3(data, offset);
51556        this.self.postMessage({
51557          action: 'probeEmsgID3',
51558          id3Frames,
51559          emsgData: data
51560        }, [data.buffer]);
51561      }
51562      /**
51563       * Probe an mpeg2-ts segment to determine the start time of the segment in it's
51564       * internal "media time," as well as whether it contains video and/or audio.
51565       *
51566       * @private
51567       * @param {Uint8Array} bytes - segment bytes
51568       * @param {number} baseStartTime
51569       *        Relative reference timestamp used when adjusting frame timestamps for rollover.
51570       *        This value should be in seconds, as it's converted to a 90khz clock within the
51571       *        function body.
51572       * @return {Object} The start time of the current segment in "media time" as well as
51573       *                  whether it contains video and/or audio
51574       */
51575
51576      probeTs({
51577        data,
51578        baseStartTime
51579      }) {
51580        const tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock$2.ONE_SECOND_IN_TS : void 0;
51581        const timeInfo = tsInspector.inspect(data, tsStartTime);
51582        let result = null;
51583        if (timeInfo) {
51584          result = {
51585            // each type's time info comes back as an array of 2 times, start and end
51586            hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
51587            hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
51588          };
51589          if (result.hasVideo) {
51590            result.videoStart = timeInfo.video[0].ptsTime;
51591          }
51592          if (result.hasAudio) {
51593            result.audioStart = timeInfo.audio[0].ptsTime;
51594          }
51595        }
51596        this.self.postMessage({
51597          action: 'probeTs',
51598          result,
51599          data
51600        }, [data.buffer]);
51601      }
51602      clearAllMp4Captions() {
51603        if (this.captionParser) {
51604          this.captionParser.clearAllCaptions();
51605        }
51606      }
51607      clearParsedMp4Captions() {
51608        if (this.captionParser) {
51609          this.captionParser.clearParsedCaptions();
51610        }
51611      }
51612      /**
51613       * Adds data (a ts segment) to the start of the transmuxer pipeline for
51614       * processing.
51615       *
51616       * @param {ArrayBuffer} data data to push into the muxer
51617       */
51618
51619      push(data) {
51620        // Cast array buffer to correct type for transmuxer
51621        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51622        this.transmuxer.push(segment);
51623      }
51624      /**
51625       * Recreate the transmuxer so that the next segment added via `push`
51626       * start with a fresh transmuxer.
51627       */
51628
51629      reset() {
51630        this.transmuxer.reset();
51631      }
51632      /**
51633       * Set the value that will be used as the `baseMediaDecodeTime` time for the
51634       * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
51635       * set relative to the first based on the PTS values.
51636       *
51637       * @param {Object} data used to set the timestamp offset in the muxer
51638       */
51639
51640      setTimestampOffset(data) {
51641        const timestampOffset = data.timestampOffset || 0;
51642        this.transmuxer.setBaseMediaDecodeTime(Math.round(clock$2.secondsToVideoTs(timestampOffset)));
51643      }
51644      setAudioAppendStart(data) {
51645        this.transmuxer.setAudioAppendStart(Math.ceil(clock$2.secondsToVideoTs(data.appendStart)));
51646      }
51647      setRemux(data) {
51648        this.transmuxer.setRemux(data.remux);
51649      }
51650      /**
51651       * Forces the pipeline to finish processing the last segment and emit it's
51652       * results.
51653       *
51654       * @param {Object} data event data, not really used
51655       */
51656
51657      flush(data) {
51658        this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
51659
51660        self.postMessage({
51661          action: 'done',
51662          type: 'transmuxed'
51663        });
51664      }
51665      endTimeline() {
51666        this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
51667        // timelines
51668
51669        self.postMessage({
51670          action: 'endedtimeline',
51671          type: 'transmuxed'
51672        });
51673      }
51674      alignGopsWith(data) {
51675        this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
51676      }
51677    }
51678    /**
51679     * Our web worker interface so that things can talk to mux.js
51680     * that will be running in a web worker. the scope is passed to this by
51681     * webworkify.
51682     *
51683     * @param {Object} self the scope for the web worker
51684     */
51685
51686    self.onmessage = function (event) {
51687      if (event.data.action === 'init' && event.data.options) {
51688        this.messageHandlers = new MessageHandlers(self, event.data.options);
51689        return;
51690      }
51691      if (!this.messageHandlers) {
51692        this.messageHandlers = new MessageHandlers(self);
51693      }
51694      if (event.data && event.data.action && event.data.action !== 'init') {
51695        if (this.messageHandlers[event.data.action]) {
51696          this.messageHandlers[event.data.action](event.data);
51697        }
51698      }
51699    };
51700  }));
51701  var TransmuxWorker = factory(workerCode$1);
51702  /* rollup-plugin-worker-factory end for worker!/
vendor: 40,469 bytes, lines 51702-52864
51702home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
51703
51704  const handleData_ = (event, transmuxedData, callback) => {
51705    const {
51706      type,
51707      initSegment,
51708      captions,
51709      captionStreams,
51710      metadata,
51711      videoFrameDtsTime,
51712      videoFramePtsTime
51713    } = event.data.segment;
51714    transmuxedData.buffer.push({
51715      captions,
51716      captionStreams,
51717      metadata
51718    });
51719    const boxes = event.data.segment.boxes || {
51720      data: event.data.segment.data
51721    };
51722    const result = {
51723      type,
51724      // cast ArrayBuffer to TypedArray
51725      data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
51726      initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
51727    };
51728    if (typeof videoFrameDtsTime !== 'undefined') {
51729      result.videoFrameDtsTime = videoFrameDtsTime;
51730    }
51731    if (typeof videoFramePtsTime !== 'undefined') {
51732      result.videoFramePtsTime = videoFramePtsTime;
51733    }
51734    callback(result);
51735  };
51736  const handleDone_ = ({
51737    transmuxedData,
51738    callback
51739  }) => {
51740    // Previously we only returned data on data events,
51741    // not on done events. Clear out the buffer to keep that consistent.
51742    transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
51743    // have received
51744
51745    callback(transmuxedData);
51746  };
51747  const handleGopInfo_ = (event, transmuxedData) => {
51748    transmuxedData.gopInfo = event.data.gopInfo;
51749  };
51750  const processTransmux = options => {
51751    const {
51752      transmuxer,
51753      bytes,
51754      audioAppendStart,
51755      gopsToAlignWith,
51756      remux,
51757      onData,
51758      onTrackInfo,
51759      onAudioTimingInfo,
51760      onVideoTimingInfo,
51761      onVideoSegmentTimingInfo,
51762      onAudioSegmentTimingInfo,
51763      onId3,
51764      onCaptions,
51765      onDone,
51766      onEndedTimeline,
51767      onTransmuxerLog,
51768      isEndOfTimeline
51769    } = options;
51770    const transmuxedData = {
51771      buffer: []
51772    };
51773    let waitForEndedTimelineEvent = isEndOfTimeline;
51774    const handleMessage = event => {
51775      if (transmuxer.currentTransmux !== options) {
51776        // disposed
51777        return;
51778      }
51779      if (event.data.action === 'data') {
51780        handleData_(event, transmuxedData, onData);
51781      }
51782      if (event.data.action === 'trackinfo') {
51783        onTrackInfo(event.data.trackInfo);
51784      }
51785      if (event.data.action === 'gopInfo') {
51786        handleGopInfo_(event, transmuxedData);
51787      }
51788      if (event.data.action === 'audioTimingInfo') {
51789        onAudioTimingInfo(event.data.audioTimingInfo);
51790      }
51791      if (event.data.action === 'videoTimingInfo') {
51792        onVideoTimingInfo(event.data.videoTimingInfo);
51793      }
51794      if (event.data.action === 'videoSegmentTimingInfo') {
51795        onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
51796      }
51797      if (event.data.action === 'audioSegmentTimingInfo') {
51798        onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
51799      }
51800      if (event.data.action === 'id3Frame') {
51801        onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
51802      }
51803      if (event.data.action === 'caption') {
51804        onCaptions(event.data.caption);
51805      }
51806      if (event.data.action === 'endedtimeline') {
51807        waitForEndedTimelineEvent = false;
51808        onEndedTimeline();
51809      }
51810      if (event.data.action === 'log') {
51811        onTransmuxerLog(event.data.log);
51812      } // wait for the transmuxed event since we may have audio and video
51813
51814      if (event.data.type !== 'transmuxed') {
51815        return;
51816      } // If the "endedtimeline" event has not yet fired, and this segment represents the end
51817      // of a timeline, that means there may still be data events before the segment
51818      // processing can be considerred complete. In that case, the final event should be
51819      // an "endedtimeline" event with the type "transmuxed."
51820
51821      if (waitForEndedTimelineEvent) {
51822        return;
51823      }
51824      transmuxer.onmessage = null;
51825      handleDone_({
51826        transmuxedData,
51827        callback: onDone
51828      });
51829      /* eslint-disable no-use-before-define */
51830
51831      dequeue(transmuxer);
51832      /* eslint-enable */
51833    };
51834
51835    transmuxer.onmessage = handleMessage;
51836    if (audioAppendStart) {
51837      transmuxer.postMessage({
51838        action: 'setAudioAppendStart',
51839        appendStart: audioAppendStart
51840      });
51841    } // allow empty arrays to be passed to clear out GOPs
51842
51843    if (Array.isArray(gopsToAlignWith)) {
51844      transmuxer.postMessage({
51845        action: 'alignGopsWith',
51846        gopsToAlignWith
51847      });
51848    }
51849    if (typeof remux !== 'undefined') {
51850      transmuxer.postMessage({
51851        action: 'setRemux',
51852        remux
51853      });
51854    }
51855    if (bytes.byteLength) {
51856      const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
51857      const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
51858      transmuxer.postMessage({
51859        action: 'push',
51860        // Send the typed-array of data as an ArrayBuffer so that
51861        // it can be sent as a "Transferable" and avoid the costly
51862        // memory copy
51863        data: buffer,
51864        // To recreate the original typed-array, we need information
51865        // about what portion of the ArrayBuffer it was a view into
51866        byteOffset,
51867        byteLength: bytes.byteLength
51868      }, [buffer]);
51869    }
51870    if (isEndOfTimeline) {
51871      transmuxer.postMessage({
51872        action: 'endTimeline'
51873      });
51874    } // even if we didn't push any bytes, we have to make sure we flush in case we reached
51875    // the end of the segment
51876
51877    transmuxer.postMessage({
51878      action: 'flush'
51879    });
51880  };
51881  const dequeue = transmuxer => {
51882    transmuxer.currentTransmux = null;
51883    if (transmuxer.transmuxQueue.length) {
51884      transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
51885      if (typeof transmuxer.currentTransmux === 'function') {
51886        transmuxer.currentTransmux();
51887      } else {
51888        processTransmux(transmuxer.currentTransmux);
51889      }
51890    }
51891  };
51892  const processAction = (transmuxer, action) => {
51893    transmuxer.postMessage({
51894      action
51895    });
51896    dequeue(transmuxer);
51897  };
51898  const enqueueAction = (action, transmuxer) => {
51899    if (!transmuxer.currentTransmux) {
51900      transmuxer.currentTransmux = action;
51901      processAction(transmuxer, action);
51902      return;
51903    }
51904    transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
51905  };
51906  const reset = transmuxer => {
51907    enqueueAction('reset', transmuxer);
51908  };
51909  const endTimeline = transmuxer => {
51910    enqueueAction('endTimeline', transmuxer);
51911  };
51912  const transmux = options => {
51913    if (!options.transmuxer.currentTransmux) {
51914      options.transmuxer.currentTransmux = options;
51915      processTransmux(options);
51916      return;
51917    }
51918    options.transmuxer.transmuxQueue.push(options);
51919  };
51920  const createTransmuxer = options => {
51921    const transmuxer = new TransmuxWorker();
51922    transmuxer.currentTransmux = null;
51923    transmuxer.transmuxQueue = [];
51924    const term = transmuxer.terminate;
51925    transmuxer.terminate = () => {
51926      transmuxer.currentTransmux = null;
51927      transmuxer.transmuxQueue.length = 0;
51928      return term.call(transmuxer);
51929    };
51930    transmuxer.postMessage({
51931      action: 'init',
51932      options
51933    });
51934    return transmuxer;
51935  };
51936  var segmentTransmuxer = {
51937    reset,
51938    endTimeline,
51939    transmux,
51940    createTransmuxer
51941  };
51942  const workerCallback = function (options) {
51943    const transmuxer = options.transmuxer;
51944    const endAction = options.endAction || options.action;
51945    const callback = options.callback;
51946    const message = _extends$1({}, options, {
51947      endAction: null,
51948      transmuxer: null,
51949      callback: null
51950    });
51951    const listenForEndEvent = event => {
51952      if (event.data.action !== endAction) {
51953        return;
51954      }
51955      transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
51956
51957      if (event.data.data) {
51958        event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
51959        if (options.data) {
51960          options.data = event.data.data;
51961        }
51962      }
51963      callback(event.data);
51964    };
51965    transmuxer.addEventListener('message', listenForEndEvent);
51966    if (options.data) {
51967      const isArrayBuffer = options.data instanceof ArrayBuffer;
51968      message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
51969      message.byteLength = options.data.byteLength;
51970      const transfers = [isArrayBuffer ? options.data : options.data.buffer];
51971      transmuxer.postMessage(message, transfers);
51972    } else {
51973      transmuxer.postMessage(message);
51974    }
51975  };
51976  const REQUEST_ERRORS = {
51977    FAILURE: 2,
51978    TIMEOUT: -101,
51979    ABORTED: -102
51980  };
51981  /**
51982   * Abort all requests
51983   *
51984   * @param {Object} activeXhrs - an object that tracks all XHR requests
51985   */
51986
51987  const abortAll = activeXhrs => {
51988    activeXhrs.forEach(xhr => {
51989      xhr.abort();
51990    });
51991  };
51992  /**
51993   * Gather important bandwidth stats once a request has completed
51994   *
51995   * @param {Object} request - the XHR request from which to gather stats
51996   */
51997
51998  const getRequestStats = request => {
51999    return {
52000      bandwidth: request.bandwidth,
52001      bytesReceived: request.bytesReceived || 0,
52002      roundTripTime: request.roundTripTime || 0
52003    };
52004  };
52005  /**
52006   * If possible gather bandwidth stats as a request is in
52007   * progress
52008   *
52009   * @param {Event} progressEvent - an event object from an XHR's progress event
52010   */
52011
52012  const getProgressStats = progressEvent => {
52013    const request = progressEvent.target;
52014    const roundTripTime = Date.now() - request.requestTime;
52015    const stats = {
52016      bandwidth: Infinity,
52017      bytesReceived: 0,
52018      roundTripTime: roundTripTime || 0
52019    };
52020    stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
52021    // because we should only use bandwidth stats on progress to determine when
52022    // abort a request early due to insufficient bandwidth
52023
52024    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
52025    return stats;
52026  };
52027  /**
52028   * Handle all error conditions in one place and return an object
52029   * with all the information
52030   *
52031   * @param {Error|null} error - if non-null signals an error occured with the XHR
52032   * @param {Object} request -  the XHR request that possibly generated the error
52033   */
52034
52035  const handleErrors = (error, request) => {
52036    if (request.timedout) {
52037      return {
52038        status: request.status,
52039        message: 'HLS request timed-out at URL: ' + request.uri,
52040        code: REQUEST_ERRORS.TIMEOUT,
52041        xhr: request
52042      };
52043    }
52044    if (request.aborted) {
52045      return {
52046        status: request.status,
52047        message: 'HLS request aborted at URL: ' + request.uri,
52048        code: REQUEST_ERRORS.ABORTED,
52049        xhr: request
52050      };
52051    }
52052    if (error) {
52053      return {
52054        status: request.status,
52055        message: 'HLS request errored at URL: ' + request.uri,
52056        code: REQUEST_ERRORS.FAILURE,
52057        xhr: request
52058      };
52059    }
52060    if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
52061      return {
52062        status: request.status,
52063        message: 'Empty HLS response at URL: ' + request.uri,
52064        code: REQUEST_ERRORS.FAILURE,
52065        xhr: request
52066      };
52067    }
52068    return null;
52069  };
52070  /**
52071   * Handle responses for key data and convert the key data to the correct format
52072   * for the decryption step later
52073   *
52074   * @param {Object} segment - a simplified copy of the segmentInfo object
52075   *                           from SegmentLoader
52076   * @param {Array} objects - objects to add the key bytes to.
52077   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52078   *                                        this request
52079   */
52080
52081  const handleKeyResponse = (segment, objects, finishProcessingFn) => (error, request) => {
52082    const response = request.response;
52083    const errorObj = handleErrors(error, request);
52084    if (errorObj) {
52085      return finishProcessingFn(errorObj, segment);
52086    }
52087    if (response.byteLength !== 16) {
52088      return finishProcessingFn({
52089        status: request.status,
52090        message: 'Invalid HLS key at URL: ' + request.uri,
52091        code: REQUEST_ERRORS.FAILURE,
52092        xhr: request
52093      }, segment);
52094    }
52095    const view = new DataView(response);
52096    const bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
52097    for (let i = 0; i < objects.length; i++) {
52098      objects[i].bytes = bytes;
52099    }
52100    return finishProcessingFn(null, segment);
52101  };
52102  const parseInitSegment = (segment, callback) => {
52103    const type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
52104    // only know how to parse mp4 init segments at the moment
52105
52106    if (type !== 'mp4') {
52107      const uri = segment.map.resolvedUri || segment.map.uri;
52108      return callback({
52109        internal: true,
52110        message: `Found unsupported ${type || 'unknown'} container for initialization segment at URL: ${uri}`,
52111        code: REQUEST_ERRORS.FAILURE
52112      });
52113    }
52114    workerCallback({
52115      action: 'probeMp4Tracks',
52116      data: segment.map.bytes,
52117      transmuxer: segment.transmuxer,
52118      callback: ({
52119        tracks,
52120        data
52121      }) => {
52122        // transfer bytes back to us
52123        segment.map.bytes = data;
52124        tracks.forEach(function (track) {
52125          segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
52126
52127          if (segment.map.tracks[track.type]) {
52128            return;
52129          }
52130          segment.map.tracks[track.type] = track;
52131          if (typeof track.id === 'number' && track.timescale) {
52132            segment.map.timescales = segment.map.timescales || {};
52133            segment.map.timescales[track.id] = track.timescale;
52134          }
52135        });
52136        return callback(null);
52137      }
52138    });
52139  };
52140  /**
52141   * Handle init-segment responses
52142   *
52143   * @param {Object} segment - a simplified copy of the segmentInfo object
52144   *                           from SegmentLoader
52145   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52146   *                                        this request
52147   */
52148
52149  const handleInitSegmentResponse = ({
52150    segment,
52151    finishProcessingFn
52152  }) => (error, request) => {
52153    const errorObj = handleErrors(error, request);
52154    if (errorObj) {
52155      return finishProcessingFn(errorObj, segment);
52156    }
52157    const bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait
52158    // until the key request is done to decrypt.
52159
52160    if (segment.map.key) {
52161      segment.map.encryptedBytes = bytes;
52162      return finishProcessingFn(null, segment);
52163    }
52164    segment.map.bytes = bytes;
52165    parseInitSegment(segment, function (parseError) {
52166      if (parseError) {
52167        parseError.xhr = request;
52168        parseError.status = request.status;
52169        return finishProcessingFn(parseError, segment);
52170      }
52171      finishProcessingFn(null, segment);
52172    });
52173  };
52174  /**
52175   * Response handler for segment-requests being sure to set the correct
52176   * property depending on whether the segment is encryped or not
52177   * Also records and keeps track of stats that are used for ABR purposes
52178   *
52179   * @param {Object} segment - a simplified copy of the segmentInfo object
52180   *                           from SegmentLoader
52181   * @param {Function} finishProcessingFn - a callback to execute to continue processing
52182   *                                        this request
52183   */
52184
52185  const handleSegmentResponse = ({
52186    segment,
52187    finishProcessingFn,
52188    responseType
52189  }) => (error, request) => {
52190    const errorObj = handleErrors(error, request);
52191    if (errorObj) {
52192      return finishProcessingFn(errorObj, segment);
52193    }
52194    const newBytes =
52195    // although responseText "should" exist, this guard serves to prevent an error being
52196    // thrown for two primary cases:
52197    // 1. the mime type override stops working, or is not implemented for a specific
52198    //    browser
52199    // 2. when using mock XHR libraries like sinon that do not allow the override behavior
52200    responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
52201    segment.stats = getRequestStats(request);
52202    if (segment.key) {
52203      segment.encryptedBytes = new Uint8Array(newBytes);
52204    } else {
52205      segment.bytes = new Uint8Array(newBytes);
52206    }
52207    return finishProcessingFn(null, segment);
52208  };
52209  const transmuxAndNotify = ({
52210    segment,
52211    bytes,
52212    trackInfoFn,
52213    timingInfoFn,
52214    videoSegmentTimingInfoFn,
52215    audioSegmentTimingInfoFn,
52216    id3Fn,
52217    captionsFn,
52218    isEndOfTimeline,
52219    endedTimelineFn,
52220    dataFn,
52221    doneFn,
52222    onTransmuxerLog
52223  }) => {
52224    const fmp4Tracks = segment.map && segment.map.tracks || {};
52225    const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
52226    // One reason for this is that in the case of full segments, we want to trust start
52227    // times from the probe, rather than the transmuxer.
52228
52229    let audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
52230    const audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
52231    let videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
52232    const videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
52233    const finish = () => transmux({
52234      bytes,
52235      transmuxer: segment.transmuxer,
52236      audioAppendStart: segment.audioAppendStart,
52237      gopsToAlignWith: segment.gopsToAlignWith,
52238      remux: isMuxed,
52239      onData: result => {
52240        result.type = result.type === 'combined' ? 'video' : result.type;
52241        dataFn(segment, result);
52242      },
52243      onTrackInfo: trackInfo => {
52244        if (trackInfoFn) {
52245          if (isMuxed) {
52246            trackInfo.isMuxed = true;
52247          }
52248          trackInfoFn(segment, trackInfo);
52249        }
52250      },
52251      onAudioTimingInfo: audioTimingInfo => {
52252        // we only want the first start value we encounter
52253        if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
52254          audioStartFn(audioTimingInfo.start);
52255          audioStartFn = null;
52256        } // we want to continually update the end time
52257
52258        if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
52259          audioEndFn(audioTimingInfo.end);
52260        }
52261      },
52262      onVideoTimingInfo: videoTimingInfo => {
52263        // we only want the first start value we encounter
52264        if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
52265          videoStartFn(videoTimingInfo.start);
52266          videoStartFn = null;
52267        } // we want to continually update the end time
52268
52269        if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
52270          videoEndFn(videoTimingInfo.end);
52271        }
52272      },
52273      onVideoSegmentTimingInfo: videoSegmentTimingInfo => {
52274        videoSegmentTimingInfoFn(videoSegmentTimingInfo);
52275      },
52276      onAudioSegmentTimingInfo: audioSegmentTimingInfo => {
52277        audioSegmentTimingInfoFn(audioSegmentTimingInfo);
52278      },
52279      onId3: (id3Frames, dispatchType) => {
52280        id3Fn(segment, id3Frames, dispatchType);
52281      },
52282      onCaptions: captions => {
52283        captionsFn(segment, [captions]);
52284      },
52285      isEndOfTimeline,
52286      onEndedTimeline: () => {
52287        endedTimelineFn();
52288      },
52289      onTransmuxerLog,
52290      onDone: result => {
52291        if (!doneFn) {
52292          return;
52293        }
52294        result.type = result.type === 'combined' ? 'video' : result.type;
52295        doneFn(null, segment, result);
52296      }
52297    }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
52298    // Meaning cached frame data may corrupt our notion of where this segment
52299    // really starts. To get around this, probe for the info needed.
52300
52301    workerCallback({
52302      action: 'probeTs',
52303      transmuxer: segment.transmuxer,
52304      data: bytes,
52305      baseStartTime: segment.baseStartTime,
52306      callback: data => {
52307        segment.bytes = bytes = data.data;
52308        const probeResult = data.result;
52309        if (probeResult) {
52310          trackInfoFn(segment, {
52311            hasAudio: probeResult.hasAudio,
52312            hasVideo: probeResult.hasVideo,
52313            isMuxed
52314          });
52315          trackInfoFn = null;
52316        }
52317        finish();
52318      }
52319    });
52320  };
52321  const handleSegmentBytes = ({
52322    segment,
52323    bytes,
52324    trackInfoFn,
52325    timingInfoFn,
52326    videoSegmentTimingInfoFn,
52327    audioSegmentTimingInfoFn,
52328    id3Fn,
52329    captionsFn,
52330    isEndOfTimeline,
52331    endedTimelineFn,
52332    dataFn,
52333    doneFn,
52334    onTransmuxerLog
52335  }) => {
52336    let bytesAsUint8Array = new Uint8Array(bytes); // TODO:
52337    // We should have a handler that fetches the number of bytes required
52338    // to check if something is fmp4. This will allow us to save bandwidth
52339    // because we can only exclude a playlist and abort requests
52340    // by codec after trackinfo triggers.
52341
52342    if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
52343      segment.isFmp4 = true;
52344      const {
52345        tracks
52346      } = segment.map;
52347      const trackInfo = {
52348        isFmp4: true,
52349        hasVideo: !!tracks.video,
52350        hasAudio: !!tracks.audio
52351      }; // if we have a audio track, with a codec that is not set to
52352      // encrypted audio
52353
52354      if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
52355        trackInfo.audioCodec = tracks.audio.codec;
52356      } // if we have a video track, with a codec that is not set to
52357      // encrypted video
52358
52359      if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
52360        trackInfo.videoCodec = tracks.video.codec;
52361      }
52362      if (tracks.video && tracks.audio) {
52363        trackInfo.isMuxed = true;
52364      } // since we don't support appending fmp4 data on progress, we know we have the full
52365      // segment here
52366
52367      trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
52368      // time. The end time can be roughly calculated by the receiver using the duration.
52369      //
52370      // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
52371      // that is the true start of the segment (where the playback engine should begin
52372      // decoding).
52373
52374      const finishLoading = (captions, id3Frames) => {
52375        // if the track still has audio at this point it is only possible
52376        // for it to be audio only. See `tracks.video && tracks.audio` if statement
52377        // above.
52378        // we make sure to use segment.bytes here as that
52379        dataFn(segment, {
52380          data: bytesAsUint8Array,
52381          type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
52382        });
52383        if (id3Frames && id3Frames.length) {
52384          id3Fn(segment, id3Frames);
52385        }
52386        if (captions && captions.length) {
52387          captionsFn(segment, captions);
52388        }
52389        doneFn(null, segment, {});
52390      };
52391      workerCallback({
52392        action: 'probeMp4StartTime',
52393        timescales: segment.map.timescales,
52394        data: bytesAsUint8Array,
52395        transmuxer: segment.transmuxer,
52396        callback: ({
52397          data,
52398          startTime
52399        }) => {
52400          // transfer bytes back to us
52401          bytes = data.buffer;
52402          segment.bytes = bytesAsUint8Array = data;
52403          if (trackInfo.hasAudio && !trackInfo.isMuxed) {
52404            timingInfoFn(segment, 'audio', 'start', startTime);
52405          }
52406          if (trackInfo.hasVideo) {
52407            timingInfoFn(segment, 'video', 'start', startTime);
52408          }
52409          workerCallback({
52410            action: 'probeEmsgID3',
52411            data: bytesAsUint8Array,
52412            transmuxer: segment.transmuxer,
52413            offset: startTime,
52414            callback: ({
52415              emsgData,
52416              id3Frames
52417            }) => {
52418              // transfer bytes back to us
52419              bytes = emsgData.buffer;
52420              segment.bytes = bytesAsUint8Array = emsgData; // Run through the CaptionParser in case there are captions.
52421              // Initialize CaptionParser if it hasn't been yet
52422
52423              if (!tracks.video || !emsgData.byteLength || !segment.transmuxer) {
52424                finishLoading(undefined, id3Frames);
52425                return;
52426              }
52427              workerCallback({
52428                action: 'pushMp4Captions',
52429                endAction: 'mp4Captions',
52430                transmuxer: segment.transmuxer,
52431                data: bytesAsUint8Array,
52432                timescales: segment.map.timescales,
52433                trackIds: [tracks.video.id],
52434                callback: message => {
52435                  // transfer bytes back to us
52436                  bytes = message.data.buffer;
52437                  segment.bytes = bytesAsUint8Array = message.data;
52438                  message.logs.forEach(function (log) {
52439                    onTransmuxerLog(merge(log, {
52440                      stream: 'mp4CaptionParser'
52441                    }));
52442                  });
52443                  finishLoading(message.captions, id3Frames);
52444                }
52445              });
52446            }
52447          });
52448        }
52449      });
52450      return;
52451    } // VTT or other segments that don't need processing
52452
52453    if (!segment.transmuxer) {
52454      doneFn(null, segment, {});
52455      return;
52456    }
52457    if (typeof segment.container === 'undefined') {
52458      segment.container = detectContainerForBytes(bytesAsUint8Array);
52459    }
52460    if (segment.container !== 'ts' && segment.container !== 'aac') {
52461      trackInfoFn(segment, {
52462        hasAudio: false,
52463        hasVideo: false
52464      });
52465      doneFn(null, segment, {});
52466      return;
52467    } // ts or aac
52468
52469    transmuxAndNotify({
52470      segment,
52471      bytes,
52472      trackInfoFn,
52473      timingInfoFn,
52474      videoSegmentTimingInfoFn,
52475      audioSegmentTimingInfoFn,
52476      id3Fn,
52477      captionsFn,
52478      isEndOfTimeline,
52479      endedTimelineFn,
52480      dataFn,
52481      doneFn,
52482      onTransmuxerLog
52483    });
52484  };
52485  const decrypt = function ({
52486    id,
52487    key,
52488    encryptedBytes,
52489    decryptionWorker
52490  }, callback) {
52491    const decryptionHandler = event => {
52492      if (event.data.source === id) {
52493        decryptionWorker.removeEventListener('message', decryptionHandler);
52494        const decrypted = event.data.decrypted;
52495        callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
52496      }
52497    };
52498    decryptionWorker.addEventListener('message', decryptionHandler);
52499    let keyBytes;
52500    if (key.bytes.slice) {
52501      keyBytes = key.bytes.slice();
52502    } else {
52503      keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
52504    } // incrementally decrypt the bytes
52505
52506    decryptionWorker.postMessage(createTransferableMessage({
52507      source: id,
52508      encrypted: encryptedBytes,
52509      key: keyBytes,
52510      iv: key.iv
52511    }), [encryptedBytes.buffer, keyBytes.buffer]);
52512  };
52513  /**
52514   * Decrypt the segment via the decryption web worker
52515   *
52516   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52517   *                                       routines
52518   * @param {Object} segment - a simplified copy of the segmentInfo object
52519   *                           from SegmentLoader
52520   * @param {Function} trackInfoFn - a callback that receives track info
52521   * @param {Function} timingInfoFn - a callback that receives timing info
52522   * @param {Function} videoSegmentTimingInfoFn
52523   *                   a callback that receives video timing info based on media times and
52524   *                   any adjustments made by the transmuxer
52525   * @param {Function} audioSegmentTimingInfoFn
52526   *                   a callback that receives audio timing info based on media times and
52527   *                   any adjustments made by the transmuxer
52528   * @param {boolean}  isEndOfTimeline
52529   *                   true if this segment represents the last segment in a timeline
52530   * @param {Function} endedTimelineFn
52531   *                   a callback made when a timeline is ended, will only be called if
52532   *                   isEndOfTimeline is true
52533   * @param {Function} dataFn - a callback that is executed when segment bytes are available
52534   *                            and ready to use
52535   * @param {Function} doneFn - a callback that is executed after decryption has completed
52536   */
52537
52538  const decryptSegment = ({
52539    decryptionWorker,
52540    segment,
52541    trackInfoFn,
52542    timingInfoFn,
52543    videoSegmentTimingInfoFn,
52544    audioSegmentTimingInfoFn,
52545    id3Fn,
52546    captionsFn,
52547    isEndOfTimeline,
52548    endedTimelineFn,
52549    dataFn,
52550    doneFn,
52551    onTransmuxerLog
52552  }) => {
52553    decrypt({
52554      id: segment.requestId,
52555      key: segment.key,
52556      encryptedBytes: segment.encryptedBytes,
52557      decryptionWorker
52558    }, decryptedBytes => {
52559      segment.bytes = decryptedBytes;
52560      handleSegmentBytes({
52561        segment,
52562        bytes: segment.bytes,
52563        trackInfoFn,
52564        timingInfoFn,
52565        videoSegmentTimingInfoFn,
52566        audioSegmentTimingInfoFn,
52567        id3Fn,
52568        captionsFn,
52569        isEndOfTimeline,
52570        endedTimelineFn,
52571        dataFn,
52572        doneFn,
52573        onTransmuxerLog
52574      });
52575    });
52576  };
52577  /**
52578   * This function waits for all XHRs to finish (with either success or failure)
52579   * before continueing processing via it's callback. The function gathers errors
52580   * from each request into a single errors array so that the error status for
52581   * each request can be examined later.
52582   *
52583   * @param {Object} activeXhrs - an object that tracks all XHR requests
52584   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52585   *                                       routines
52586   * @param {Function} trackInfoFn - a callback that receives track info
52587   * @param {Function} timingInfoFn - a callback that receives timing info
52588   * @param {Function} videoSegmentTimingInfoFn
52589   *                   a callback that receives video timing info based on media times and
52590   *                   any adjustments made by the transmuxer
52591   * @param {Function} audioSegmentTimingInfoFn
52592   *                   a callback that receives audio timing info based on media times and
52593   *                   any adjustments made by the transmuxer
52594   * @param {Function} id3Fn - a callback that receives ID3 metadata
52595   * @param {Function} captionsFn - a callback that receives captions
52596   * @param {boolean}  isEndOfTimeline
52597   *                   true if this segment represents the last segment in a timeline
52598   * @param {Function} endedTimelineFn
52599   *                   a callback made when a timeline is ended, will only be called if
52600   *                   isEndOfTimeline is true
52601   * @param {Function} dataFn - a callback that is executed when segment bytes are available
52602   *                            and ready to use
52603   * @param {Function} doneFn - a callback that is executed after all resources have been
52604   *                            downloaded and any decryption completed
52605   */
52606
52607  const waitForCompletion = ({
52608    activeXhrs,
52609    decryptionWorker,
52610    trackInfoFn,
52611    timingInfoFn,
52612    videoSegmentTimingInfoFn,
52613    audioSegmentTimingInfoFn,
52614    id3Fn,
52615    captionsFn,
52616    isEndOfTimeline,
52617    endedTimelineFn,
52618    dataFn,
52619    doneFn,
52620    onTransmuxerLog
52621  }) => {
52622    let count = 0;
52623    let didError = false;
52624    return (error, segment) => {
52625      if (didError) {
52626        return;
52627      }
52628      if (error) {
52629        didError = true; // If there are errors, we have to abort any outstanding requests
52630
52631        abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
52632        // handle the aborted events from those requests, there are some cases where we may
52633        // never get an aborted event. For instance, if the network connection is lost and
52634        // there were two requests, the first may have triggered an error immediately, while
52635        // the second request remains unsent. In that case, the aborted algorithm will not
52636        // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
52637        //
52638        // We also can't rely on the ready state of the XHR, since the request that
52639        // triggered the connection error may also show as a ready state of 0 (unsent).
52640        // Therefore, we have to finish this group of requests immediately after the first
52641        // seen error.
52642
52643        return doneFn(error, segment);
52644      }
52645      count += 1;
52646      if (count === activeXhrs.length) {
52647        const segmentFinish = function () {
52648          if (segment.encryptedBytes) {
52649            return decryptSegment({
52650              decryptionWorker,
52651              segment,
52652              trackInfoFn,
52653              timingInfoFn,
52654              videoSegmentTimingInfoFn,
52655              audioSegmentTimingInfoFn,
52656              id3Fn,
52657              captionsFn,
52658              isEndOfTimeline,
52659              endedTimelineFn,
52660              dataFn,
52661              doneFn,
52662              onTransmuxerLog
52663            });
52664          } // Otherwise, everything is ready just continue
52665
52666          handleSegmentBytes({
52667            segment,
52668            bytes: segment.bytes,
52669            trackInfoFn,
52670            timingInfoFn,
52671            videoSegmentTimingInfoFn,
52672            audioSegmentTimingInfoFn,
52673            id3Fn,
52674            captionsFn,
52675            isEndOfTimeline,
52676            endedTimelineFn,
52677            dataFn,
52678            doneFn,
52679            onTransmuxerLog
52680          });
52681        }; // Keep track of when *all* of the requests have completed
52682
52683        segment.endOfAllRequests = Date.now();
52684        if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
52685          return decrypt({
52686            decryptionWorker,
52687            // add -init to the "id" to differentiate between segment
52688            // and init segment decryption, just in case they happen
52689            // at the same time at some point in the future.
52690            id: segment.requestId + '-init',
52691            encryptedBytes: segment.map.encryptedBytes,
52692            key: segment.map.key
52693          }, decryptedBytes => {
52694            segment.map.bytes = decryptedBytes;
52695            parseInitSegment(segment, parseError => {
52696              if (parseError) {
52697                abortAll(activeXhrs);
52698                return doneFn(parseError, segment);
52699              }
52700              segmentFinish();
52701            });
52702          });
52703        }
52704        segmentFinish();
52705      }
52706    };
52707  };
52708  /**
52709   * Calls the abort callback if any request within the batch was aborted. Will only call
52710   * the callback once per batch of requests, even if multiple were aborted.
52711   *
52712   * @param {Object} loadendState - state to check to see if the abort function was called
52713   * @param {Function} abortFn - callback to call for abort
52714   */
52715
52716  const handleLoadEnd = ({
52717    loadendState,
52718    abortFn
52719  }) => event => {
52720    const request = event.target;
52721    if (request.aborted && abortFn && !loadendState.calledAbortFn) {
52722      abortFn();
52723      loadendState.calledAbortFn = true;
52724    }
52725  };
52726  /**
52727   * Simple progress event callback handler that gathers some stats before
52728   * executing a provided callback with the `segment` object
52729   *
52730   * @param {Object} segment - a simplified copy of the segmentInfo object
52731   *                           from SegmentLoader
52732   * @param {Function} progressFn - a callback that is executed each time a progress event
52733   *                                is received
52734   * @param {Function} trackInfoFn - a callback that receives track info
52735   * @param {Function} timingInfoFn - a callback that receives timing info
52736   * @param {Function} videoSegmentTimingInfoFn
52737   *                   a callback that receives video timing info based on media times and
52738   *                   any adjustments made by the transmuxer
52739   * @param {Function} audioSegmentTimingInfoFn
52740   *                   a callback that receives audio timing info based on media times and
52741   *                   any adjustments made by the transmuxer
52742   * @param {boolean}  isEndOfTimeline
52743   *                   true if this segment represents the last segment in a timeline
52744   * @param {Function} endedTimelineFn
52745   *                   a callback made when a timeline is ended, will only be called if
52746   *                   isEndOfTimeline is true
52747   * @param {Function} dataFn - a callback that is executed when segment bytes are available
52748   *                            and ready to use
52749   * @param {Event} event - the progress event object from XMLHttpRequest
52750   */
52751
52752  const handleProgress = ({
52753    segment,
52754    progressFn,
52755    trackInfoFn,
52756    timingInfoFn,
52757    videoSegmentTimingInfoFn,
52758    audioSegmentTimingInfoFn,
52759    id3Fn,
52760    captionsFn,
52761    isEndOfTimeline,
52762    endedTimelineFn,
52763    dataFn
52764  }) => event => {
52765    const request = event.target;
52766    if (request.aborted) {
52767      return;
52768    }
52769    segment.stats = merge(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
52770
52771    if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
52772      segment.stats.firstBytesReceivedAt = Date.now();
52773    }
52774    return progressFn(event, segment);
52775  };
52776  /**
52777   * Load all resources and does any processing necessary for a media-segment
52778   *
52779   * Features:
52780   *   decrypts the media-segment if it has a key uri and an iv
52781   *   aborts *all* requests if *any* one request fails
52782   *
52783   * The segment object, at minimum, has the following format:
52784   * {
52785   *   resolvedUri: String,
52786   *   [transmuxer]: Object,
52787   *   [byterange]: {
52788   *     offset: Number,
52789   *     length: Number
52790   *   },
52791   *   [key]: {
52792   *     resolvedUri: String
52793   *     [byterange]: {
52794   *       offset: Number,
52795   *       length: Number
52796   *     },
52797   *     iv: {
52798   *       bytes: Uint32Array
52799   *     }
52800   *   },
52801   *   [map]: {
52802   *     resolvedUri: String,
52803   *     [byterange]: {
52804   *       offset: Number,
52805   *       length: Number
52806   *     },
52807   *     [bytes]: Uint8Array
52808   *   }
52809   * }
52810   * ...where [name] denotes optional properties
52811   *
52812   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
52813   * @param {Object} xhrOptions - the base options to provide to all xhr requests
52814   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
52815   *                                       decryption routines
52816   * @param {Object} segment - a simplified copy of the segmentInfo object
52817   *                           from SegmentLoader
52818   * @param {Function} abortFn - a callback called (only once) if any piece of a request was
52819   *                             aborted
52820   * @param {Function} progressFn - a callback that receives progress events from the main
52821   *                                segment's xhr request
52822   * @param {Function} trackInfoFn - a callback that receives track info
52823   * @param {Function} timingInfoFn - a callback that receives timing info
52824   * @param {Function} videoSegmentTimingInfoFn
52825   *                   a callback that receives video timing info based on media times and
52826   *                   any adjustments made by the transmuxer
52827   * @param {Function} audioSegmentTimingInfoFn
52828   *                   a callback that receives audio timing info based on media times and
52829   *                   any adjustments made by the transmuxer
52830   * @param {Function} id3Fn - a callback that receives ID3 metadata
52831   * @param {Function} captionsFn - a callback that receives captions
52832   * @param {boolean}  isEndOfTimeline
52833   *                   true if this segment represents the last segment in a timeline
52834   * @param {Function} endedTimelineFn
52835   *                   a callback made when a timeline is ended, will only be called if
52836   *                   isEndOfTimeline is true
52837   * @param {Function} dataFn - a callback that receives data from the main segment's xhr
52838   *                            request, transmuxed if needed
52839   * @param {Function} doneFn - a callback that is executed only once all requests have
52840   *                            succeeded or failed
52841   * @return {Function} a function that, when invoked, immediately aborts all
52842   *                     outstanding requests
52843   */
52844
52845  const mediaSegmentRequest = ({
52846    xhr,
52847    xhrOptions,
52848    decryptionWorker,
52849    segment,
52850    abortFn,
52851    progressFn,
52852    trackInfoFn,
52853    timingInfoFn,
52854    videoSegmentTimingInfoFn,
52855    audioSegmentTimingInfoFn,
52856    id3Fn,
52857    captionsFn,
52858    isEndOfTimeline,
52859    endedTimelineFn,
52860    dataFn,
52861    doneFn,
52862    onTransmuxerLog
52863  }) => {
52864    const activeXhrs = [];
vendor: 4,917 bytes, lines 52865-52995
52865    const finishProcessingFn = waitForCompletion({
52866      activeXhrs,
52867      decryptionWorker,
52868      trackInfoFn,
52869      timingInfoFn,
52870      videoSegmentTimingInfoFn,
52871      audioSegmentTimingInfoFn,
52872      id3Fn,
52873      captionsFn,
52874      isEndOfTimeline,
52875      endedTimelineFn,
52876      dataFn,
52877      doneFn,
52878      onTransmuxerLog
52879    }); // optionally, request the decryption key
52880
52881    if (segment.key && !segment.key.bytes) {
52882      const objects = [segment.key];
52883      if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
52884        objects.push(segment.map.key);
52885      }
52886      const keyRequestOptions = merge(xhrOptions, {
52887        uri: segment.key.resolvedUri,
52888        responseType: 'arraybuffer'
52889      });
52890      const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn);
52891      const keyXhr = xhr(keyRequestOptions, keyRequestCallback);
52892      activeXhrs.push(keyXhr);
52893    } // optionally, request the associated media init segment
52894
52895    if (segment.map && !segment.map.bytes) {
52896      const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
52897      if (differentMapKey) {
52898        const mapKeyRequestOptions = merge(xhrOptions, {
52899          uri: segment.map.key.resolvedUri,
52900          responseType: 'arraybuffer'
52901        });
52902        const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn);
52903        const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
52904        activeXhrs.push(mapKeyXhr);
52905      }
52906      const initSegmentOptions = merge(xhrOptions, {
52907        uri: segment.map.resolvedUri,
52908        responseType: 'arraybuffer',
52909        headers: segmentXhrHeaders(segment.map)
52910      });
52911      const initSegmentRequestCallback = handleInitSegmentResponse({
52912        segment,
52913        finishProcessingFn
52914      });
52915      const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
52916      activeXhrs.push(initSegmentXhr);
52917    }
52918    const segmentRequestOptions = merge(xhrOptions, {
52919      uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
52920      responseType: 'arraybuffer',
52921      headers: segmentXhrHeaders(segment)
52922    });
52923    const segmentRequestCallback = handleSegmentResponse({
52924      segment,
52925      finishProcessingFn,
52926      responseType: segmentRequestOptions.responseType
52927    });
52928    const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
52929    segmentXhr.addEventListener('progress', handleProgress({
52930      segment,
52931      progressFn,
52932      trackInfoFn,
52933      timingInfoFn,
52934      videoSegmentTimingInfoFn,
52935      audioSegmentTimingInfoFn,
52936      id3Fn,
52937      captionsFn,
52938      isEndOfTimeline,
52939      endedTimelineFn,
52940      dataFn
52941    }));
52942    activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
52943    // multiple times, provide a shared state object
52944
52945    const loadendState = {};
52946    activeXhrs.forEach(activeXhr => {
52947      activeXhr.addEventListener('loadend', handleLoadEnd({
52948        loadendState,
52949        abortFn
52950      }));
52951    });
52952    return () => abortAll(activeXhrs);
52953  };
52954
52955  /**
52956   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
52957   * codec strings, or translating codec strings into objects that can be examined.
52958   */
52959  const logFn$1 = logger('CodecUtils');
52960  /**
52961   * Returns a set of codec strings parsed from the playlist or the default
52962   * codec strings if no codecs were specified in the playlist
52963   *
52964   * @param {Playlist} media the current media playlist
52965   * @return {Object} an object with the video and audio codecs
52966   */
52967
52968  const getCodecs = function (media) {
52969    // if the codecs were explicitly specified, use them instead of the
52970    // defaults
52971    const mediaAttributes = media.attributes || {};
52972    if (mediaAttributes.CODECS) {
52973      return parseCodecs(mediaAttributes.CODECS);
52974    }
52975  };
52976  const isMaat = (main, media) => {
52977    const mediaAttributes = media.attributes || {};
52978    return main && main.mediaGroups && main.mediaGroups.AUDIO && mediaAttributes.AUDIO && main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
52979  };
52980  const isMuxed = (main, media) => {
52981    if (!isMaat(main, media)) {
52982      return true;
52983    }
52984    const mediaAttributes = media.attributes || {};
52985    const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
52986    for (const groupId in audioGroup) {
52987      // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
52988      // or there are listed playlists (the case for DASH, as the manifest will have already
52989      // provided all of the details necessary to generate the audio playlist, as opposed to
52990      // HLS' externally requested playlists), then the content is demuxed.
52991      if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
52992        return true;
52993      }
52994    }
52995    return false;
vendor: 5,328 bytes, lines 52996-53150
52996  };
52997  const unwrapCodecList = function (codecList) {
52998    const codecs = {};
52999    codecList.forEach(({
53000      mediaType,
53001      type,
53002      details
53003    }) => {
53004      codecs[mediaType] = codecs[mediaType] || [];
53005      codecs[mediaType].push(translateLegacyCodec(`${type}${details}`));
53006    });
53007    Object.keys(codecs).forEach(function (mediaType) {
53008      if (codecs[mediaType].length > 1) {
53009        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.`);
53010        codecs[mediaType] = null;
53011        return;
53012      }
53013      codecs[mediaType] = codecs[mediaType][0];
53014    });
53015    return codecs;
53016  };
53017  const codecCount = function (codecObj) {
53018    let count = 0;
53019    if (codecObj.audio) {
53020      count++;
53021    }
53022    if (codecObj.video) {
53023      count++;
53024    }
53025    return count;
53026  };
53027  /**
53028   * Calculates the codec strings for a working configuration of
53029   * SourceBuffers to play variant streams in a main playlist. If
53030   * there is no possible working configuration, an empty object will be
53031   * returned.
53032   *
53033   * @param main {Object} the m3u8 object for the main playlist
53034   * @param media {Object} the m3u8 object for the variant playlist
53035   * @return {Object} the codec strings.
53036   *
53037   * @private
53038   */
53039
53040  const codecsForPlaylist = function (main, media) {
53041    const mediaAttributes = media.attributes || {};
53042    const codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
53043    // Put another way, there is no way to have a video-only multiple-audio HLS!
53044
53045    if (isMaat(main, media) && !codecInfo.audio) {
53046      if (!isMuxed(main, media)) {
53047        // It is possible for codecs to be specified on the audio media group playlist but
53048        // not on the rendition playlist. This is mostly the case for DASH, where audio and
53049        // video are always separate (and separately specified).
53050        const defaultCodecs = unwrapCodecList(codecsFromDefault(main, mediaAttributes.AUDIO) || []);
53051        if (defaultCodecs.audio) {
53052          codecInfo.audio = defaultCodecs.audio;
53053        }
53054      }
53055    }
53056    return codecInfo;
53057  };
53058  const logFn = logger('PlaylistSelector');
53059  const representationToString = function (representation) {
53060    if (!representation || !representation.playlist) {
53061      return;
53062    }
53063    const playlist = representation.playlist;
53064    return JSON.stringify({
53065      id: playlist.id,
53066      bandwidth: representation.bandwidth,
53067      width: representation.width,
53068      height: representation.height,
53069      codecs: playlist.attributes && playlist.attributes.CODECS || ''
53070    });
53071  }; // Utilities
53072
53073  /**
53074   * Returns the CSS value for the specified property on an element
53075   * using `getComputedStyle`. Firefox has a long-standing issue where
53076   * getComputedStyle() may return null when running in an iframe with
53077   * `display: none`.
53078   *
53079   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
53080   * @param {HTMLElement} el the htmlelement to work on
53081   * @param {string} the proprety to get the style for
53082   */
53083
53084  const safeGetComputedStyle = function (el, property) {
53085    if (!el) {
53086      return '';
53087    }
53088    const result = window.getComputedStyle(el);
53089    if (!result) {
53090      return '';
53091    }
53092    return result[property];
53093  };
53094  /**
53095   * Resuable stable sort function
53096   *
53097   * @param {Playlists} array
53098   * @param {Function} sortFn Different comparators
53099   * @function stableSort
53100   */
53101
53102  const stableSort = function (array, sortFn) {
53103    const newArray = array.slice();
53104    array.sort(function (left, right) {
53105      const cmp = sortFn(left, right);
53106      if (cmp === 0) {
53107        return newArray.indexOf(left) - newArray.indexOf(right);
53108      }
53109      return cmp;
53110    });
53111  };
53112  /**
53113   * A comparator function to sort two playlist object by bandwidth.
53114   *
53115   * @param {Object} left a media playlist object
53116   * @param {Object} right a media playlist object
53117   * @return {number} Greater than zero if the bandwidth attribute of
53118   * left is greater than the corresponding attribute of right. Less
53119   * than zero if the bandwidth of right is greater than left and
53120   * exactly zero if the two are equal.
53121   */
53122
53123  const comparePlaylistBandwidth = function (left, right) {
53124    let leftBandwidth;
53125    let rightBandwidth;
53126    if (left.attributes.BANDWIDTH) {
53127      leftBandwidth = left.attributes.BANDWIDTH;
53128    }
53129    leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
53130    if (right.attributes.BANDWIDTH) {
53131      rightBandwidth = right.attributes.BANDWIDTH;
53132    }
53133    rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
53134    return leftBandwidth - rightBandwidth;
53135  };
53136  /**
53137   * A comparator function to sort two playlist object by resolution (width).
53138   *
53139   * @param {Object} left a media playlist object
53140   * @param {Object} right a media playlist object
53141   * @return {number} Greater than zero if the resolution.width attribute of
53142   * left is greater than the corresponding attribute of right. Less
53143   * than zero if the resolution.width of right is greater than left and
53144   * exactly zero if the two are equal.
53145   */
53146
53147  const comparePlaylistResolution = function (left, right) {
53148    let leftWidth;
53149    let rightWidth;
53150    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
53151      leftWidth = left.attributes.RESOLUTION.width;
53152    }
53153    leftWidth = leftWidth || window.Number.MAX_VALUE;
53154    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
53155      rightWidth = right.attributes.RESOLUTION.width;
53156    }
53157    rightWidth = rightWidth || window.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
53158    // have the same media dimensions/ resolution
53159
53160    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
53161      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
53162    }
53163    return leftWidth - rightWidth;
53164  };
53165  /**
53166   * Chooses the appropriate media playlist based on bandwidth and player size
53167   *
53168   * @param {Object} main
53169   *        Object representation of the main manifest
53170   * @param {number} playerBandwidth
53171   *        Current calculated bandwidth of the player
53172   * @param {number} playerWidth
53173   *        Current width of the player element (should account for the device pixel ratio)
53174   * @param {number} playerHeight
53175   *        Current height of the player element (should account for the device pixel ratio)
53176   * @param {boolean} limitRenditionByPlayerDimensions
53177   *        True if the player width and height should be used during the selection, false otherwise
53178   * @param {Object} playlistController
53179   *        the current playlistController object
53180   * @return {Playlist} the highest bitrate playlist less than the
53181   * currently detected bandwidth, accounting for some amount of
53182   * bandwidth variance
53183   */
53184
53185  let simpleSelector = function (main, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, playlistController) {
53186    // If we end up getting called before `main` is available, exit early
53187    if (!main) {
53188      return;
53189    }
53190    const options = {
53191      bandwidth: playerBandwidth,
53192      width: playerWidth,
vendor: 14,553 bytes, lines 53193-53486
53193      height: playerHeight,
53194      limitRenditionByPlayerDimensions
53195    };
53196    let playlists = main.playlists; // if playlist is audio only, select between currently active audio group playlists.
53197
53198    if (Playlist.isAudioOnly(main)) {
53199      playlists = playlistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
53200      // at the buttom of this function for debugging.
53201
53202      options.audioOnly = true;
53203    } // convert the playlists to an intermediary representation to make comparisons easier
53204
53205    let sortedPlaylistReps = playlists.map(playlist => {
53206      let bandwidth;
53207      const width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
53208      const height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
53209      bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
53210      bandwidth = bandwidth || window.Number.MAX_VALUE;
53211      return {
53212        bandwidth,
53213        width,
53214        height,
53215        playlist
53216      };
53217    });
53218    stableSort(sortedPlaylistReps, (left, right) => left.bandwidth - right.bandwidth); // filter out any playlists that have been excluded due to
53219    // incompatible configurations
53220
53221    sortedPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isIncompatible(rep.playlist)); // filter out any playlists that have been disabled manually through the representations
53222    // api or excluded temporarily due to playback errors.
53223
53224    let enabledPlaylistReps = sortedPlaylistReps.filter(rep => Playlist.isEnabled(rep.playlist));
53225    if (!enabledPlaylistReps.length) {
53226      // if there are no enabled playlists, then they have all been excluded or disabled
53227      // by the user through the representations api. In this case, ignore exclusion and
53228      // fallback to what the user wants by using playlists the user has not disabled.
53229      enabledPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isDisabled(rep.playlist));
53230    } // filter out any variant that has greater effective bitrate
53231    // than the current estimated bandwidth
53232
53233    const bandwidthPlaylistReps = enabledPlaylistReps.filter(rep => rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth);
53234    let highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
53235    // and then taking the very first element
53236
53237    const bandwidthBestRep = bandwidthPlaylistReps.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0]; // if we're not going to limit renditions by player size, make an early decision.
53238
53239    if (limitRenditionByPlayerDimensions === false) {
53240      const chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53241      if (chosenRep && chosenRep.playlist) {
53242        let type = 'sortedPlaylistReps';
53243        if (bandwidthBestRep) {
53244          type = 'bandwidthBestRep';
53245        }
53246        if (enabledPlaylistReps[0]) {
53247          type = 'enabledPlaylistReps';
53248        }
53249        logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
53250        return chosenRep.playlist;
53251      }
53252      logFn('could not choose a playlist with options', options);
53253      return null;
53254    } // filter out playlists without resolution information
53255
53256    const haveResolution = bandwidthPlaylistReps.filter(rep => rep.width && rep.height); // sort variants by resolution
53257
53258    stableSort(haveResolution, (left, right) => left.width - right.width); // if we have the exact resolution as the player use it
53259
53260    const resolutionBestRepList = haveResolution.filter(rep => rep.width === playerWidth && rep.height === playerHeight);
53261    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
53262
53263    const resolutionBestRep = resolutionBestRepList.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
53264    let resolutionPlusOneList;
53265    let resolutionPlusOneSmallest;
53266    let resolutionPlusOneRep; // find the smallest variant that is larger than the player
53267    // if there is no match of exact resolution
53268
53269    if (!resolutionBestRep) {
53270      resolutionPlusOneList = haveResolution.filter(rep => rep.width > playerWidth || rep.height > playerHeight); // find all the variants have the same smallest resolution
53271
53272      resolutionPlusOneSmallest = resolutionPlusOneList.filter(rep => rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height); // ensure that we also pick the highest bandwidth variant that
53273      // is just-larger-than the video player
53274
53275      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
53276      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
53277    }
53278    let leastPixelDiffRep; // If this selector proves to be better than others,
53279    // resolutionPlusOneRep and resolutionBestRep and all
53280    // the code involving them should be removed.
53281
53282    if (playlistController.leastPixelDiffSelector) {
53283      // find the variant that is closest to the player's pixel size
53284      const leastPixelDiffList = haveResolution.map(rep => {
53285        rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
53286        return rep;
53287      }); // get the highest bandwidth, closest resolution playlist
53288
53289      stableSort(leastPixelDiffList, (left, right) => {
53290        // sort by highest bandwidth if pixelDiff is the same
53291        if (left.pixelDiff === right.pixelDiff) {
53292          return right.bandwidth - left.bandwidth;
53293        }
53294        return left.pixelDiff - right.pixelDiff;
53295      });
53296      leastPixelDiffRep = leastPixelDiffList[0];
53297    } // fallback chain of variants
53298
53299    const chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53300    if (chosenRep && chosenRep.playlist) {
53301      let type = 'sortedPlaylistReps';
53302      if (leastPixelDiffRep) {
53303        type = 'leastPixelDiffRep';
53304      } else if (resolutionPlusOneRep) {
53305        type = 'resolutionPlusOneRep';
53306      } else if (resolutionBestRep) {
53307        type = 'resolutionBestRep';
53308      } else if (bandwidthBestRep) {
53309        type = 'bandwidthBestRep';
53310      } else if (enabledPlaylistReps[0]) {
53311        type = 'enabledPlaylistReps';
53312      }
53313      logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
53314      return chosenRep.playlist;
53315    }
53316    logFn('could not choose a playlist with options', options);
53317    return null;
53318  };
53319
53320  /**
53321   * Chooses the appropriate media playlist based on the most recent
53322   * bandwidth estimate and the player size.
53323   *
53324   * Expects to be called within the context of an instance of VhsHandler
53325   *
53326   * @return {Playlist} the highest bitrate playlist less than the
53327   * currently detected bandwidth, accounting for some amount of
53328   * bandwidth variance
53329   */
53330
53331  const lastBandwidthSelector = function () {
53332    const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
53333    return simpleSelector(this.playlists.main, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
53334  };
53335  /**
53336   * Chooses the appropriate media playlist based on an
53337   * exponential-weighted moving average of the bandwidth after
53338   * filtering for player size.
53339   *
53340   * Expects to be called within the context of an instance of VhsHandler
53341   *
53342   * @param {number} decay - a number between 0 and 1. Higher values of
53343   * this parameter will cause previous bandwidth estimates to lose
53344   * significance more quickly.
53345   * @return {Function} a function which can be invoked to create a new
53346   * playlist selector function.
53347   * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
53348   */
53349
53350  const movingAverageBandwidthSelector = function (decay) {
53351    let average = -1;
53352    let lastSystemBandwidth = -1;
53353    if (decay < 0 || decay > 1) {
53354      throw new Error('Moving average bandwidth decay must be between 0 and 1.');
53355    }
53356    return function () {
53357      const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
53358      if (average < 0) {
53359        average = this.systemBandwidth;
53360        lastSystemBandwidth = this.systemBandwidth;
53361      } // stop the average value from decaying for every 250ms
53362      // when the systemBandwidth is constant
53363      // and
53364      // stop average from setting to a very low value when the
53365      // systemBandwidth becomes 0 in case of chunk cancellation
53366
53367      if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
53368        average = decay * this.systemBandwidth + (1 - decay) * average;
53369        lastSystemBandwidth = this.systemBandwidth;
53370      }
53371      return simpleSelector(this.playlists.main, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
53372    };
53373  };
53374  /**
53375   * Chooses the appropriate media playlist based on the potential to rebuffer
53376   *
53377   * @param {Object} settings
53378   *        Object of information required to use this selector
53379   * @param {Object} settings.main
53380   *        Object representation of the main manifest
53381   * @param {number} settings.currentTime
53382   *        The current time of the player
53383   * @param {number} settings.bandwidth
53384   *        Current measured bandwidth
53385   * @param {number} settings.duration
53386   *        Duration of the media
53387   * @param {number} settings.segmentDuration
53388   *        Segment duration to be used in round trip time calculations
53389   * @param {number} settings.timeUntilRebuffer
53390   *        Time left in seconds until the player has to rebuffer
53391   * @param {number} settings.currentTimeline
53392   *        The current timeline segments are being loaded from
53393   * @param {SyncController} settings.syncController
53394   *        SyncController for determining if we have a sync point for a given playlist
53395   * @return {Object|null}
53396   *         {Object} return.playlist
53397   *         The highest bandwidth playlist with the least amount of rebuffering
53398   *         {Number} return.rebufferingImpact
53399   *         The amount of time in seconds switching to this playlist will rebuffer. A
53400   *         negative value means that switching will cause zero rebuffering.
53401   */
53402
53403  const minRebufferMaxBandwidthSelector = function (settings) {
53404    const {
53405      main,
53406      currentTime,
53407      bandwidth,
53408      duration,
53409      segmentDuration,
53410      timeUntilRebuffer,
53411      currentTimeline,
53412      syncController
53413    } = settings; // filter out any playlists that have been excluded due to
53414    // incompatible configurations
53415
53416    const compatiblePlaylists = main.playlists.filter(playlist => !Playlist.isIncompatible(playlist)); // filter out any playlists that have been disabled manually through the representations
53417    // api or excluded temporarily due to playback errors.
53418
53419    let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
53420    if (!enabledPlaylists.length) {
53421      // if there are no enabled playlists, then they have all been excluded or disabled
53422      // by the user through the representations api. In this case, ignore exclusion and
53423      // fallback to what the user wants by using playlists the user has not disabled.
53424      enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist));
53425    }
53426    const bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
53427    const rebufferingEstimates = bandwidthPlaylists.map(playlist => {
53428      const syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
53429      // sync request first. This will double the request time
53430
53431      const numRequests = syncPoint ? 1 : 2;
53432      const requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
53433      const rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
53434      return {
53435        playlist,
53436        rebufferingImpact
53437      };
53438    });
53439    const noRebufferingPlaylists = rebufferingEstimates.filter(estimate => estimate.rebufferingImpact <= 0); // Sort by bandwidth DESC
53440
53441    stableSort(noRebufferingPlaylists, (a, b) => comparePlaylistBandwidth(b.playlist, a.playlist));
53442    if (noRebufferingPlaylists.length) {
53443      return noRebufferingPlaylists[0];
53444    }
53445    stableSort(rebufferingEstimates, (a, b) => a.rebufferingImpact - b.rebufferingImpact);
53446    return rebufferingEstimates[0] || null;
53447  };
53448  /**
53449   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
53450   * one with video.  If no renditions with video exist, return the lowest audio rendition.
53451   *
53452   * Expects to be called within the context of an instance of VhsHandler
53453   *
53454   * @return {Object|null}
53455   *         {Object} return.playlist
53456   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
53457   *         exists pick the lowest audio rendition.
53458   */
53459
53460  const lowestBitrateCompatibleVariantSelector = function () {
53461    // filter out any playlists that have been excluded due to
53462    // incompatible configurations or playback errors
53463    const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
53464
53465    stableSort(playlists, (a, b) => comparePlaylistBandwidth(a, b)); // Parse and assume that playlists with no video codec have no video
53466    // (this is not necessarily true, although it is generally true).
53467    //
53468    // If an entire manifest has no valid videos everything will get filtered
53469    // out.
53470
53471    const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.main, playlist).video);
53472    return playlistsWithVideo[0] || null;
53473  };
53474
53475  /**
53476   * Combine all segments into a single Uint8Array
53477   *
53478   * @param {Object} segmentObj
53479   * @return {Uint8Array} concatenated bytes
53480   * @private
53481   */
53482  const concatSegments = segmentObj => {
53483    let offset = 0;
53484    let tempBuffer;
53485    if (segmentObj.bytes) {
53486      tempBuffer = new Uint8Array(segmentObj.bytes);
53486 // combine the individual segments into one large typed-array
53487
53488      segmentObj.segments.forEach(segment => {
53489        tempBuffer.set(segment, offset);
53490        offset += segment.byteLength;
53491      });
53492    }
53493    return tempBuffer;
53494  };
vendor: 15,181 bytes, lines 53494-53960
53494
53495
53496  /**
53497   * @file text-tracks.js
53498   */
53499  /**
53500   * Create captions text tracks on video.js if they do not exist
53501   *
53502   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
53503   * @param {Object} tech the video.js tech
53504   * @param {Object} captionStream the caption stream to create
53505   * @private
53506   */
53507
53508  const createCaptionsTrackIfNotExists = function (inbandTextTracks, tech, captionStream) {
53509    if (!inbandTextTracks[captionStream]) {
53510      tech.trigger({
53511        type: 'usage',
53512        name: 'vhs-608'
53513      });
53514      let instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
53515
53516      if (/^cc708_/.test(captionStream)) {
53517        instreamId = 'SERVICE' + captionStream.split('_')[1];
53518      }
53519      const track = tech.textTracks().getTrackById(instreamId);
53520      if (track) {
53521        // Resuse an existing track with a CC# id because this was
53522        // very likely created by videojs-contrib-hls from information
53523        // in the m3u8 for us to use
53524        inbandTextTracks[captionStream] = track;
53525      } else {
53526        // This section gets called when we have caption services that aren't specified in the manifest.
53527        // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
53528        const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
53529        let label = captionStream;
53530        let language = captionStream;
53531        let def = false;
53532        const captionService = captionServices[instreamId];
53533        if (captionService) {
53534          label = captionService.label;
53535          language = captionService.language;
53536          def = captionService.default;
53537        } // Otherwise, create a track with the default `CC#` label and
53538        // without a language
53539
53540        inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
53541          kind: 'captions',
53542          id: instreamId,
53543          // TODO: investigate why this doesn't seem to turn the caption on by default
53544          default: def,
53545          label,
53546          language
53547        }, false).track;
53548      }
53549    }
53550  };
53551  /**
53552   * Add caption text track data to a source handler given an array of captions
53553   *
53554   * @param {Object}
53555   *   @param {Object} inbandTextTracks the inband text tracks
53556   *   @param {number} timestampOffset the timestamp offset of the source buffer
53557   *   @param {Array} captionArray an array of caption data
53558   * @private
53559   */
53560
53561  const addCaptionData = function ({
53562    inbandTextTracks,
53563    captionArray,
53564    timestampOffset
53565  }) {
53566    if (!captionArray) {
53567      return;
53568    }
53569    const Cue = window.WebKitDataCue || window.VTTCue;
53570    captionArray.forEach(caption => {
53571      const track = caption.stream; // in CEA 608 captions, video.js/mux.js sends a content array
53572      // with positioning data
53573
53574      if (caption.content) {
53575        caption.content.forEach(value => {
53576          const cue = new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, value.text);
53577          cue.line = value.line;
53578          cue.align = 'left';
53579          cue.position = value.position;
53580          cue.positionAlign = 'line-left';
53581          inbandTextTracks[track].addCue(cue);
53582        });
53583      } else {
53584        // otherwise, a text value with combined captions is sent
53585        inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
53586      }
53587    });
53588  };
53589  /**
53590   * Define properties on a cue for backwards compatability,
53591   * but warn the user that the way that they are using it
53592   * is depricated and will be removed at a later date.
53593   *
53594   * @param {Cue} cue the cue to add the properties on
53595   * @private
53596   */
53597
53598  const deprecateOldCue = function (cue) {
53599    Object.defineProperties(cue.frame, {
53600      id: {
53601        get() {
53602          videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
53603          return cue.value.key;
53604        }
53605      },
53606      value: {
53607        get() {
53608          videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
53609          return cue.value.data;
53610        }
53611      },
53612      privateData: {
53613        get() {
53614          videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
53615          return cue.value.data;
53616        }
53617      }
53618    });
53619  };
53620  /**
53621   * Add metadata text track data to a source handler given an array of metadata
53622   *
53623   * @param {Object}
53624   *   @param {Object} inbandTextTracks the inband text tracks
53625   *   @param {Array} metadataArray an array of meta data
53626   *   @param {number} timestampOffset the timestamp offset of the source buffer
53627   *   @param {number} videoDuration the duration of the video
53628   * @private
53629   */
53630
53631  const addMetadata = ({
53632    inbandTextTracks,
53633    metadataArray,
53634    timestampOffset,
53635    videoDuration
53636  }) => {
53637    if (!metadataArray) {
53638      return;
53639    }
53640    const Cue = window.WebKitDataCue || window.VTTCue;
53641    const metadataTrack = inbandTextTracks.metadataTrack_;
53642    if (!metadataTrack) {
53643      return;
53644    }
53645    metadataArray.forEach(metadata => {
53646      const time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
53647      // ignore this bit of metadata.
53648      // This likely occurs when you have an non-timed ID3 tag like TIT2,
53649      // which is the "Title/Songname/Content description" frame
53650
53651      if (typeof time !== 'number' || window.isNaN(time) || time < 0 || !(time < Infinity)) {
53652        return;
53653      } // If we have no frames, we can't create a cue.
53654
53655      if (!metadata.frames || !metadata.frames.length) {
53656        return;
53657      }
53658      metadata.frames.forEach(frame => {
53659        const cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
53660        cue.frame = frame;
53661        cue.value = frame;
53662        deprecateOldCue(cue);
53663        metadataTrack.addCue(cue);
53664      });
53665    });
53666    if (!metadataTrack.cues || !metadataTrack.cues.length) {
53667      return;
53668    } // Updating the metadeta cues so that
53669    // the endTime of each cue is the startTime of the next cue
53670    // the endTime of last cue is the duration of the video
53671
53672    const cues = metadataTrack.cues;
53673    const cuesArray = []; // Create a copy of the TextTrackCueList...
53674    // ...disregarding cues with a falsey value
53675
53676    for (let i = 0; i < cues.length; i++) {
53677      if (cues[i]) {
53678        cuesArray.push(cues[i]);
53679      }
53680    } // Group cues by their startTime value
53681
53682    const cuesGroupedByStartTime = cuesArray.reduce((obj, cue) => {
53683      const timeSlot = obj[cue.startTime] || [];
53684      timeSlot.push(cue);
53685      obj[cue.startTime] = timeSlot;
53686      return obj;
53687    }, {}); // Sort startTimes by ascending order
53688
53689    const sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort((a, b) => Number(a) - Number(b)); // Map each cue group's endTime to the next group's startTime
53690
53691    sortedStartTimes.forEach((startTime, idx) => {
53692      const cueGroup = cuesGroupedByStartTime[startTime];
53693      const finiteDuration = isFinite(videoDuration) ? videoDuration : startTime;
53694      const nextTime = Number(sortedStartTimes[idx + 1]) || finiteDuration; // Map each cue's endTime the next group's startTime
53695
53696      cueGroup.forEach(cue => {
53697        cue.endTime = nextTime;
53698      });
53699    });
53700  }; // object for mapping daterange attributes
53701
53702  const dateRangeAttr = {
53703    id: 'ID',
53704    class: 'CLASS',
53705    startDate: 'START-DATE',
53706    duration: 'DURATION',
53707    endDate: 'END-DATE',
53708    endOnNext: 'END-ON-NEXT',
53709    plannedDuration: 'PLANNED-DURATION',
53710    scte35Out: 'SCTE35-OUT',
53711    scte35In: 'SCTE35-IN'
53712  };
53713  const dateRangeKeysToOmit = new Set(['id', 'class', 'startDate', 'duration', 'endDate', 'endOnNext', 'startTime', 'endTime', 'processDateRange']);
53714  /**
53715   * Add DateRange metadata text track to a source handler given an array of metadata
53716   *
53717   * @param {Object}
53718   *   @param {Object} inbandTextTracks the inband text tracks
53719   *   @param {Array} dateRanges parsed media playlist
53720   * @private
53721   */
53722
53723  const addDateRangeMetadata = ({
53724    inbandTextTracks,
53725    dateRanges
53726  }) => {
53727    const metadataTrack = inbandTextTracks.metadataTrack_;
53728    if (!metadataTrack) {
53729      return;
53730    }
53731    const Cue = window.WebKitDataCue || window.VTTCue;
53732    dateRanges.forEach(dateRange => {
53733      // we generate multiple cues for each date range with different attributes
53734      for (const key of Object.keys(dateRange)) {
53735        if (dateRangeKeysToOmit.has(key)) {
53736          continue;
53737        }
53738        const cue = new Cue(dateRange.startTime, dateRange.endTime, '');
53739        cue.id = dateRange.id;
53740        cue.type = 'com.apple.quicktime.HLS';
53741        cue.value = {
53742          key: dateRangeAttr[key],
53743          data: dateRange[key]
53744        };
53745        if (key === 'scte35Out' || key === 'scte35In') {
53746          cue.value.data = new Uint8Array(cue.value.data.match(/[\da-f]{2}/gi)).buffer;
53747        }
53748        metadataTrack.addCue(cue);
53749      }
53750      dateRange.processDateRange();
53751    });
53752  };
53753  /**
53754   * Create metadata text track on video.js if it does not exist
53755   *
53756   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
53757   * @param {string} dispatchType the inband metadata track dispatch type
53758   * @param {Object} tech the video.js tech
53759   * @private
53760   */
53761
53762  const createMetadataTrackIfNotExists = (inbandTextTracks, dispatchType, tech) => {
53763    if (inbandTextTracks.metadataTrack_) {
53764      return;
53765    }
53766    inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
53767      kind: 'metadata',
53768      label: 'Timed Metadata'
53769    }, false).track;
53770    if (!videojs.browser.IS_ANY_SAFARI) {
53771      inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
53772    }
53773  };
53774  /**
53775   * Remove cues from a track on video.js.
53776   *
53777   * @param {Double} start start of where we should remove the cue
53778   * @param {Double} end end of where the we should remove the cue
53779   * @param {Object} track the text track to remove the cues from
53780   * @private
53781   */
53782
53783  const removeCuesFromTrack = function (start, end, track) {
53784    let i;
53785    let cue;
53786    if (!track) {
53787      return;
53788    }
53789    if (!track.cues) {
53790      return;
53791    }
53792    i = track.cues.length;
53793    while (i--) {
53794      cue = track.cues[i]; // Remove any cue within the provided start and end time
53795
53796      if (cue.startTime >= start && cue.endTime <= end) {
53797        track.removeCue(cue);
53798      }
53799    }
53800  };
53801  /**
53802   * Remove duplicate cues from a track on video.js (a cue is considered a
53803   * duplicate if it has the same time interval and text as another)
53804   *
53805   * @param {Object} track the text track to remove the duplicate cues from
53806   * @private
53807   */
53808
53809  const removeDuplicateCuesFromTrack = function (track) {
53810    const cues = track.cues;
53811    if (!cues) {
53812      return;
53813    }
53814    const uniqueCues = {};
53815    for (let i = cues.length - 1; i >= 0; i--) {
53816      const cue = cues[i];
53817      const cueKey = `${cue.startTime}-${cue.endTime}-${cue.text}`;
53818      if (uniqueCues[cueKey]) {
53819        track.removeCue(cue);
53820      } else {
53821        uniqueCues[cueKey] = cue;
53822      }
53823    }
53824  };
53825
53826  /**
53827   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
53828   * front of current time.
53829   *
53830   * @param {Array} buffer
53831   *        The current buffer of gop information
53832   * @param {number} currentTime
53833   *        The current time
53834   * @param {Double} mapping
53835   *        Offset to map display time to stream presentation time
53836   * @return {Array}
53837   *         List of gops considered safe to append over
53838   */
53839
53840  const gopsSafeToAlignWith = (buffer, currentTime, mapping) => {
53841    if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
53842      return [];
53843    } // pts value for current time + 3 seconds to give a bit more wiggle room
53844
53845    const currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1);
53846    let i;
53847    for (i = 0; i < buffer.length; i++) {
53848      if (buffer[i].pts > currentTimePts) {
53849        break;
53850      }
53851    }
53852    return buffer.slice(i);
53853  };
53854  /**
53855   * Appends gop information (timing and byteLength) received by the transmuxer for the
53856   * gops appended in the last call to appendBuffer
53857   *
53858   * @param {Array} buffer
53859   *        The current buffer of gop information
53860   * @param {Array} gops
53861   *        List of new gop information
53862   * @param {boolean} replace
53863   *        If true, replace the buffer with the new gop information. If false, append the
53864   *        new gop information to the buffer in the right location of time.
53865   * @return {Array}
53866   *         Updated list of gop information
53867   */
53868
53869  const updateGopBuffer = (buffer, gops, replace) => {
53870    if (!gops.length) {
53871      return buffer;
53872    }
53873    if (replace) {
53874      // If we are in safe append mode, then completely overwrite the gop buffer
53875      // with the most recent appeneded data. This will make sure that when appending
53876      // future segments, we only try to align with gops that are both ahead of current
53877      // time and in the last segment appended.
53878      return gops.slice();
53879    }
53880    const start = gops[0].pts;
53881    let i = 0;
53882    for (i; i < buffer.length; i++) {
53883      if (buffer[i].pts >= start) {
53884        break;
53885      }
53886    }
53887    return buffer.slice(0, i).concat(gops);
53888  };
53889  /**
53890   * Removes gop information in buffer that overlaps with provided start and end
53891   *
53892   * @param {Array} buffer
53893   *        The current buffer of gop information
53894   * @param {Double} start
53895   *        position to start the remove at
53896   * @param {Double} end
53897   *        position to end the remove at
53898   * @param {Double} mapping
53899   *        Offset to map display time to stream presentation time
53900   */
53901
53902  const removeGopBuffer = (buffer, start, end, mapping) => {
53903    const startPts = Math.ceil((start - mapping) * clock_1);
53904    const endPts = Math.ceil((end - mapping) * clock_1);
53905    const updatedBuffer = buffer.slice();
53906    let i = buffer.length;
53907    while (i--) {
53908      if (buffer[i].pts <= endPts) {
53909        break;
53910      }
53911    }
53912    if (i === -1) {
53913      // no removal because end of remove range is before start of buffer
53914      return updatedBuffer;
53915    }
53916    let j = i + 1;
53917    while (j--) {
53918      if (buffer[j].pts <= startPts) {
53919        break;
53920      }
53921    } // clamp remove range start to 0 index
53922
53923    j = Math.max(j, 0);
53924    updatedBuffer.splice(j, i - j + 1);
53925    return updatedBuffer;
53926  };
53927  const shallowEqual = function (a, b) {
53928    // if both are undefined
53929    // or one or the other is undefined
53930    // they are not equal
53931    if (!a && !b || !a && b || a && !b) {
53932      return false;
53933    } // they are the same object and thus, equal
53934
53935    if (a === b) {
53936      return true;
53937    } // sort keys so we can make sure they have
53938    // all the same keys later.
53939
53940    const akeys = Object.keys(a).sort();
53941    const bkeys = Object.keys(b).sort(); // different number of keys, not equal
53942
53943    if (akeys.length !== bkeys.length) {
53944      return false;
53945    }
53946    for (let i = 0; i < akeys.length; i++) {
53947      const key = akeys[i]; // different sorted keys, not equal
53948
53949      if (key !== bkeys[i]) {
53950        return false;
53951      } // different values, not equal
53952
53953      if (a[key] !== b[key]) {
53954        return false;
53955      }
53956    }
53957    return true;
53958  };
53959
53960  /
53960/ https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
53961  const QUOTA_EXCEEDED_ERR = 22
vendor: 16,403 bytes, lines 53961-54305
53961;
53962
53963  /**
53964   * The segment loader has no recourse except to fetch a segment in the
53965   * current playlist and use the internal timestamps in that segment to
53966   * generate a syncPoint. This function returns a good candidate index
53967   * for that process.
53968   *
53969   * @param {Array} segments - the segments array from a playlist.
53970   * @return {number} An index of a segment from the playlist to load
53971   */
53972
53973  const getSyncSegmentCandidate = function (currentTimeline, segments, targetTime) {
53974    segments = segments || [];
53975    const timelineSegments = [];
53976    let time = 0;
53977    for (let i = 0; i < segments.length; i++) {
53978      const segment = segments[i];
53979      if (currentTimeline === segment.timeline) {
53980        timelineSegments.push(i);
53981        time += segment.duration;
53982        if (time > targetTime) {
53983          return i;
53984        }
53985      }
53986    }
53987    if (timelineSegments.length === 0) {
53988      return 0;
53989    } // default to the last timeline segment
53990
53991    return timelineSegments[timelineSegments.length - 1];
53992  }; // In the event of a quota exceeded error, keep at least one second of back buffer. This
53993  // number was arbitrarily chosen and may be updated in the future, but seemed reasonable
53994  // as a start to prevent any potential issues with removing content too close to the
53995  // playhead.
53996
53997  const MIN_BACK_BUFFER = 1; // in ms
53998
53999  const CHECK_BUFFER_DELAY = 500;
54000  const finite = num => typeof num === 'number' && isFinite(num); // With most content hovering around 30fps, if a segment has a duration less than a half
54001  // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
54002  // not accurately reflect the rest of the content.
54003
54004  const MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
54005  const illegalMediaSwitch = (loaderType, startingMedia, trackInfo) => {
54006    // Although these checks should most likely cover non 'main' types, for now it narrows
54007    // the scope of our checks.
54008    if (loaderType !== 'main' || !startingMedia || !trackInfo) {
54009      return null;
54010    }
54011    if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
54012      return 'Neither audio nor video found in segment.';
54013    }
54014    if (startingMedia.hasVideo && !trackInfo.hasVideo) {
54015      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.';
54016    }
54017    if (!startingMedia.hasVideo && trackInfo.hasVideo) {
54018      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.';
54019    }
54020    return null;
54021  };
54022  /**
54023   * Calculates a time value that is safe to remove from the back buffer without interrupting
54024   * playback.
54025   *
54026   * @param {TimeRange} seekable
54027   *        The current seekable range
54028   * @param {number} currentTime
54029   *        The current time of the player
54030   * @param {number} targetDuration
54031   *        The target duration of the current playlist
54032   * @return {number}
54033   *         Time that is safe to remove from the back buffer without interrupting playback
54034   */
54035
54036  const safeBackBufferTrimTime = (seekable, currentTime, targetDuration) => {
54037    // 30 seconds before the playhead provides a safe default for trimming.
54038    //
54039    // Choosing a reasonable default is particularly important for high bitrate content and
54040    // VOD videos/live streams with large windows, as the buffer may end up overfilled and
54041    // throw an APPEND_BUFFER_ERR.
54042    let trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
54043    if (seekable.length) {
54044      // Some live playlists may have a shorter window of content than the full allowed back
54045      // buffer. For these playlists, don't save content that's no longer within the window.
54046      trimTime = Math.max(trimTime, seekable.start(0));
54047    } // Don't remove within target duration of the current time to avoid the possibility of
54048    // removing the GOP currently being played, as removing it can cause playback stalls.
54049
54050    const maxTrimTime = currentTime - targetDuration;
54051    return Math.min(maxTrimTime, trimTime);
54052  };
54053  const segmentInfoString = segmentInfo => {
54054    const {
54055      startOfSegment,
54056      duration,
54057      segment,
54058      part,
54059      playlist: {
54060        mediaSequence: seq,
54061        id,
54062        segments = []
54063      },
54064      mediaIndex: index,
54065      partIndex,
54066      timeline
54067    } = segmentInfo;
54068    const segmentLen = segments.length - 1;
54069    let selection = 'mediaIndex/partIndex increment';
54070    if (segmentInfo.getMediaInfoForTime) {
54071      selection = `getMediaInfoForTime (${segmentInfo.getMediaInfoForTime})`;
54072    } else if (segmentInfo.isSyncRequest) {
54073      selection = 'getSyncSegmentCandidate (isSyncRequest)';
54074    }
54075    if (segmentInfo.independent) {
54076      selection += ` with independent ${segmentInfo.independent}`;
54077    }
54078    const hasPartIndex = typeof partIndex === 'number';
54079    const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
54080    const zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
54081      preloadSegment: segment
54082    }) - 1 : 0;
54083    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}]`;
54084  };
54085  const timingInfoPropertyForMedia = mediaType => `${mediaType}TimingInfo`;
54086  /**
54087   * Returns the timestamp offset to use for the segment.
54088   *
54089   * @param {number} segmentTimeline
54090   *        The timeline of the segment
54091   * @param {number} currentTimeline
54092   *        The timeline currently being followed by the loader
54093   * @param {number} startOfSegment
54094   *        The estimated segment start
54095   * @param {TimeRange[]} buffered
54096   *        The loader's buffer
54097   * @param {boolean} overrideCheck
54098   *        If true, no checks are made to see if the timestamp offset value should be set,
54099   *        but sets it directly to a value.
54100   *
54101   * @return {number|null}
54102   *         Either a number representing a new timestamp offset, or null if the segment is
54103   *         part of the same timeline
54104   */
54105
54106  const timestampOffsetForSegment = ({
54107    segmentTimeline,
54108    currentTimeline,
54109    startOfSegment,
54110    buffered,
54111    overrideCheck
54112  }) => {
54113    // Check to see if we are crossing a discontinuity to see if we need to set the
54114    // timestamp offset on the transmuxer and source buffer.
54115    //
54116    // Previously, we changed the timestampOffset if the start of this segment was less than
54117    // the currently set timestampOffset, but this isn't desirable as it can produce bad
54118    // behavior, especially around long running live streams.
54119    if (!overrideCheck && segmentTimeline === currentTimeline) {
54120      return null;
54121    } // When changing renditions, it's possible to request a segment on an older timeline. For
54122    // instance, given two renditions with the following:
54123    //
54124    // #EXTINF:10
54125    // segment1
54126    // #EXT-X-DISCONTINUITY
54127    // #EXTINF:10
54128    // segment2
54129    // #EXTINF:10
54130    // segment3
54131    //
54132    // And the current player state:
54133    //
54134    // current time: 8
54135    // buffer: 0 => 20
54136    //
54137    // The next segment on the current rendition would be segment3, filling the buffer from
54138    // 20s onwards. However, if a rendition switch happens after segment2 was requested,
54139    // then the next segment to be requested will be segment1 from the new rendition in
54140    // order to fill time 8 and onwards. Using the buffered end would result in repeated
54141    // content (since it would position segment1 of the new rendition starting at 20s). This
54142    // case can be identified when the new segment's timeline is a prior value. Instead of
54143    // using the buffered end, the startOfSegment can be used, which, hopefully, will be
54144    // more accurate to the actual start time of the segment.
54145
54146    if (segmentTimeline < currentTimeline) {
54147      return startOfSegment;
54148    } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
54149    // value uses the end of the last segment if it is available. While this value
54150    // should often be correct, it's better to rely on the buffered end, as the new
54151    // content post discontinuity should line up with the buffered end as if it were
54152    // time 0 for the new content.
54153
54154    return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
54155  };
54156  /**
54157   * Returns whether or not the loader should wait for a timeline change from the timeline
54158   * change controller before processing the segment.
54159   *
54160   * Primary timing in VHS goes by video. This is different from most media players, as
54161   * audio is more often used as the primary timing source. For the foreseeable future, VHS
54162   * will continue to use video as the primary timing source, due to the current logic and
54163   * expectations built around it.
54164
54165   * Since the timing follows video, in order to maintain sync, the video loader is
54166   * responsible for setting both audio and video source buffer timestamp offsets.
54167   *
54168   * Setting different values for audio and video source buffers could lead to
54169   * desyncing. The following examples demonstrate some of the situations where this
54170   * distinction is important. Note that all of these cases involve demuxed content. When
54171   * content is muxed, the audio and video are packaged together, therefore syncing
54172   * separate media playlists is not an issue.
54173   *
54174   * CASE 1: Audio prepares to load a new timeline before video:
54175   *
54176   * Timeline:       0                 1
54177   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54178   * Audio Loader:                     ^
54179   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54180   * Video Loader              ^
54181   *
54182   * In the above example, the audio loader is preparing to load the 6th segment, the first
54183   * after a discontinuity, while the video loader is still loading the 5th segment, before
54184   * the discontinuity.
54185   *
54186   * If the audio loader goes ahead and loads and appends the 6th segment before the video
54187   * loader crosses the discontinuity, then when appended, the 6th audio segment will use
54188   * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
54189   * the audio loader must provide the audioAppendStart value to trim the content in the
54190   * transmuxer, and that value relies on the audio timestamp offset. Since the audio
54191   * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
54192   * segment until that value is provided.
54193   *
54194   * CASE 2: Video prepares to load a new timeline before audio:
54195   *
54196   * Timeline:       0                 1
54197   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54198   * Audio Loader:             ^
54199   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54200   * Video Loader                      ^
54201   *
54202   * In the above example, the video loader is preparing to load the 6th segment, the first
54203   * after a discontinuity, while the audio loader is still loading the 5th segment, before
54204   * the discontinuity.
54205   *
54206   * If the video loader goes ahead and loads and appends the 6th segment, then once the
54207   * segment is loaded and processed, both the video and audio timestamp offsets will be
54208   * set, since video is used as the primary timing source. This is to ensure content lines
54209   * up appropriately, as any modifications to the video timing are reflected by audio when
54210   * the video loader sets the audio and video timestamp offsets to the same value. However,
54211   * setting the timestamp offset for audio before audio has had a chance to change
54212   * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
54213   * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
54214   *
54215   * CASE 3: When seeking, audio prepares to load a new timeline before video
54216   *
54217   * Timeline:       0                 1
54218   * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54219   * Audio Loader:           ^
54220   * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54221   * Video Loader            ^
54222   *
54223   * In the above example, both audio and video loaders are loading segments from timeline
54224   * 0, but imagine that the seek originated from timeline 1.
54225   *
54226   * When seeking to a new timeline, the timestamp offset will be set based on the expected
54227   * segment start of the loaded video segment. In order to maintain sync, the audio loader
54228   * must wait for the video loader to load its segment and update both the audio and video
54229   * timestamp offsets before it may load and append its own segment. This is the case
54230   * whether the seek results in a mismatched segment request (e.g., the audio loader
54231   * chooses to load segment 3 and the video loader chooses to load segment 4) or the
54232   * loaders choose to load the same segment index from each playlist, as the segments may
54233   * not be aligned perfectly, even for matching segment indexes.
54234   *
54235   * @param {Object} timelinechangeController
54236   * @param {number} currentTimeline
54237   *        The timeline currently being followed by the loader
54238   * @param {number} segmentTimeline
54239   *        The timeline of the segment being loaded
54240   * @param {('main'|'audio')} loaderType
54241   *        The loader type
54242   * @param {boolean} audioDisabled
54243   *        Whether the audio is disabled for the loader. This should only be true when the
54244   *        loader may have muxed audio in its segment, but should not append it, e.g., for
54245   *        the main loader when an alternate audio playlist is active.
54246   *
54247   * @return {boolean}
54248   *         Whether the loader should wait for a timeline change from the timeline change
54249   *         controller before processing the segment
54250   */
54251
54252  const shouldWaitForTimelineChange = ({
54253    timelineChangeController,
54254    currentTimeline,
54255    segmentTimeline,
54256    loaderType,
54257    audioDisabled
54258  }) => {
54259    if (currentTimeline === segmentTimeline) {
54260      return false;
54261    }
54262    if (loaderType === 'audio') {
54263      const lastMainTimelineChange = timelineChangeController.lastTimelineChange({
54264        type: 'main'
54265      }); // Audio loader should wait if:
54266      //
54267      // * main hasn't had a timeline change yet (thus has not loaded its first segment)
54268      // * main hasn't yet changed to the timeline audio is looking to load
54269
54270      return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
54271    } // The main loader only needs to wait for timeline changes if there's demuxed audio.
54272    // Otherwise, there's nothing to wait for, since audio would be muxed into the main
54273    // loader's segments (or the content is audio/video only and handled by the main
54274    // loader).
54275
54276    if (loaderType === 'main' && audioDisabled) {
54277      const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
54278        type: 'audio'
54279      }); // Main loader should wait for the audio loader if audio is not pending a timeline
54280      // change to the current timeline.
54281      //
54282      // Since the main loader is responsible for setting the timestamp offset for both
54283      // audio and video, the main loader must wait for audio to be about to change to its
54284      // timeline before setting the offset, otherwise, if audio is behind in loading,
54285      // segments from the previous timeline would be adjusted by the new timestamp offset.
54286      //
54287      // This requirement means that video will not cross a timeline until the audio is
54288      // about to cross to it, so that way audio and video will always cross the timeline
54289      // together.
54290      //
54291      // In addition to normal timeline changes, these rules also apply to the start of a
54292      // stream (going from a non-existent timeline, -1, to timeline 0). It's important
54293      // that these rules apply to the first timeline change because if they did not, it's
54294      // possible that the main loader will cross two timelines before the audio loader has
54295      // crossed one. Logic may be implemented to handle the startup as a special case, but
54296      // it's easier to simply treat all timeline changes the same.
54297
54298      if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
54299        return false;
54300      }
54301      return true;
54302    }
54303    return false;
54304  };
54305  const 
vendor: 5,881 bytes, lines 54305-54449
54305mediaDuration = timingInfos => {
54306    let maxDuration = 0;
54307    ['video', 'audio'].forEach(function (type) {
54308      const typeTimingInfo = timingInfos[`${type}TimingInfo`];
54309      if (!typeTimingInfo) {
54310        return;
54311      }
54312      const {
54313        start,
54314        end
54315      } = typeTimingInfo;
54316      let duration;
54317      if (typeof start === 'bigint' || typeof end === 'bigint') {
54318        duration = window.BigInt(end) - window.BigInt(start);
54319      } else if (typeof start === 'number' && typeof end === 'number') {
54320        duration = end - start;
54321      }
54322      if (typeof duration !== 'undefined' && duration > maxDuration) {
54323        maxDuration = duration;
54324      }
54325    }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
54326    // as we only need BigInt when we are above that.
54327
54328    if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
54329      maxDuration = Number(maxDuration);
54330    }
54331    return maxDuration;
54332  };
54333  const segmentTooLong = ({
54334    segmentDuration,
54335    maxDuration
54336  }) => {
54337    // 0 duration segments are most likely due to metadata only segments or a lack of
54338    // information.
54339    if (!segmentDuration) {
54340      return false;
54341    } // For HLS:
54342    //
54343    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
54344    // The EXTINF duration of each Media Segment in the Playlist
54345    // file, when rounded to the nearest integer, MUST be less than or equal
54346    // to the target duration; longer segments can trigger playback stalls
54347    // or other errors.
54348    //
54349    // For DASH, the mpd-parser uses the largest reported segment duration as the target
54350    // duration. Although that reported duration is occasionally approximate (i.e., not
54351    // exact), a strict check may report that a segment is too long more often in DASH.
54352
54353    return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
54354  };
54355  const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => {
54356    // Right now we aren't following DASH's timing model exactly, so only perform
54357    // this check for HLS content.
54358    if (sourceType !== 'hls') {
54359      return null;
54360    }
54361    const segmentDuration = mediaDuration({
54362      audioTimingInfo: segmentInfo.audioTimingInfo,
54363      videoTimingInfo: segmentInfo.videoTimingInfo
54364    }); // Don't report if we lack information.
54365    //
54366    // If the segment has a duration of 0 it is either a lack of information or a
54367    // metadata only segment and shouldn't be reported here.
54368
54369    if (!segmentDuration) {
54370      return null;
54371    }
54372    const targetDuration = segmentInfo.playlist.targetDuration;
54373    const isSegmentWayTooLong = segmentTooLong({
54374      segmentDuration,
54375      maxDuration: targetDuration * 2
54376    });
54377    const isSegmentSlightlyTooLong = segmentTooLong({
54378      segmentDuration,
54379      maxDuration: targetDuration
54380    });
54381    const 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';
54382    if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
54383      return {
54384        severity: isSegmentWayTooLong ? 'warn' : 'info',
54385        message: segmentTooLongMessage
54386      };
54387    }
54388    return null;
54389  };
54390  /**
54391   * An object that manages segment loading and appending.
54392   *
54393   * @class SegmentLoader
54394   * @param {Object} options required and optional options
54395   * @extends videojs.EventTarget
54396   */
54397
54398  class SegmentLoader extends videojs.EventTarget {
54399    constructor(settings, options = {}) {
54400      super(); // check pre-conditions
54401
54402      if (!settings) {
54403        throw new TypeError('Initialization settings are required');
54404      }
54405      if (typeof settings.currentTime !== 'function') {
54406        throw new TypeError('No currentTime getter specified');
54407      }
54408      if (!settings.mediaSource) {
54409        throw new TypeError('No MediaSource specified');
54410      } // public properties
54411
54412      this.bandwidth = settings.bandwidth;
54413      this.throughput = {
54414        rate: 0,
54415        count: 0
54416      };
54417      this.roundTrip = NaN;
54418      this.resetStats_();
54419      this.mediaIndex = null;
54420      this.partIndex = null; // private settings
54421
54422      this.hasPlayed_ = settings.hasPlayed;
54423      this.currentTime_ = settings.currentTime;
54424      this.seekable_ = settings.seekable;
54425      this.seeking_ = settings.seeking;
54426      this.duration_ = settings.duration;
54427      this.mediaSource_ = settings.mediaSource;
54428      this.vhs_ = settings.vhs;
54429      this.loaderType_ = settings.loaderType;
54430      this.currentMediaInfo_ = void 0;
54431      this.startingMediaInfo_ = void 0;
54432      this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
54433      this.goalBufferLength_ = settings.goalBufferLength;
54434      this.sourceType_ = settings.sourceType;
54435      this.sourceUpdater_ = settings.sourceUpdater;
54436      this.inbandTextTracks_ = settings.inbandTextTracks;
54437      this.state_ = 'INIT';
54438      this.timelineChangeController_ = settings.timelineChangeController;
54439      this.shouldSaveSegmentTimingInfo_ = true;
54440      this.parse708captions_ = settings.parse708captions;
54441      this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
54442      this.captionServices_ = settings.captionServices;
54443      this.exactManifestTimings = settings.exactManifestTimings;
54444      this.addMetadataToTextTrack = settings.addMetadataToTextTrack; // private instance variables
54445
54446      this.checkBufferTimeout_ = null;
54447      this.error_ = void 0;
54448      this.currentTimeline_ = -1;
54449      this.shouldForceTimestampOffsetAfterResync_ = false;
vendor: 5,516 bytes, lines 54450-54612
54450      this.pendingSegment_ = null;
54451      this.xhrOptions_ = null;
54452      this.pendingSegments_ = [];
54453      this.audioDisabled_ = false;
54454      this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
54455
54456      this.gopBuffer_ = [];
54457      this.timeMapping_ = 0;
54458      this.safeAppend_ = false;
54459      this.appendInitSegment_ = {
54460        audio: true,
54461        video: true
54462      };
54463      this.playlistOfLastInitSegment_ = {
54464        audio: null,
54465        video: null
54466      };
54467      this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
54468      // information yet to start the loading process (e.g., if the audio loader wants to
54469      // load a segment from the next timeline but the main loader hasn't yet crossed that
54470      // timeline), then the load call will be added to the queue until it is ready to be
54471      // processed.
54472
54473      this.loadQueue_ = [];
54474      this.metadataQueue_ = {
54475        id3: [],
54476        caption: []
54477      };
54478      this.waitingOnRemove_ = false;
54479      this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
54480
54481      this.activeInitSegmentId_ = null;
54482      this.initSegments_ = {}; // HLSe playback
54483
54484      this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
54485      this.keyCache_ = {};
54486      this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
54487      // between a time in the display time and a segment index within
54488      // a playlist
54489
54490      this.syncController_ = settings.syncController;
54491      this.syncPoint_ = {
54492        segmentIndex: 0,
54493        time: 0
54494      };
54495      this.transmuxer_ = this.createTransmuxer_();
54496      this.triggerSyncInfoUpdate_ = () => this.trigger('syncinfoupdate');
54497      this.syncController_.on('syncinfoupdate', this.triggerSyncInfoUpdate_);
54498      this.mediaSource_.addEventListener('sourceopen', () => {
54499        if (!this.isEndOfStream_()) {
54500          this.ended_ = false;
54501        }
54502      }); // ...for determining the fetch location
54503
54504      this.fetchAtBuffer_ = false;
54505      this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
54506      Object.defineProperty(this, 'state', {
54507        get() {
54508          return this.state_;
54509        },
54510        set(newState) {
54511          if (newState !== this.state_) {
54512            this.logger_(`${this.state_} -> ${newState}`);
54513            this.state_ = newState;
54514            this.trigger('statechange');
54515          }
54516        }
54517      });
54518      this.sourceUpdater_.on('ready', () => {
54519        if (this.hasEnoughInfoToAppend_()) {
54520          this.processCallQueue_();
54521        }
54522      }); // Only the main loader needs to listen for pending timeline changes, as the main
54523      // loader should wait for audio to be ready to change its timeline so that both main
54524      // and audio timelines change together. For more details, see the
54525      // shouldWaitForTimelineChange function.
54526
54527      if (this.loaderType_ === 'main') {
54528        this.timelineChangeController_.on('pendingtimelinechange', () => {
54529          if (this.hasEnoughInfoToAppend_()) {
54530            this.processCallQueue_();
54531          }
54532        });
54533      } // The main loader only listens on pending timeline changes, but the audio loader,
54534      // since its loads follow main, needs to listen on timeline changes. For more details,
54535      // see the shouldWaitForTimelineChange function.
54536
54537      if (this.loaderType_ === 'audio') {
54538        this.timelineChangeController_.on('timelinechange', () => {
54539          if (this.hasEnoughInfoToLoad_()) {
54540            this.processLoadQueue_();
54541          }
54542          if (this.hasEnoughInfoToAppend_()) {
54543            this.processCallQueue_();
54544          }
54545        });
54546      }
54547    }
54548    createTransmuxer_() {
54549      return segmentTransmuxer.createTransmuxer({
54550        remux: false,
54551        alignGopsAtEnd: this.safeAppend_,
54552        keepOriginalTimestamps: true,
54553        parse708captions: this.parse708captions_,
54554        captionServices: this.captionServices_
54555      });
54556    }
54557    /**
54558     * reset all of our media stats
54559     *
54560     * @private
54561     */
54562
54563    resetStats_() {
54564      this.mediaBytesTransferred = 0;
54565      this.mediaRequests = 0;
54566      this.mediaRequestsAborted = 0;
54567      this.mediaRequestsTimedout = 0;
54568      this.mediaRequestsErrored = 0;
54569      this.mediaTransferDuration = 0;
54570      this.mediaSecondsLoaded = 0;
54571      this.mediaAppends = 0;
54572    }
54573    /**
54574     * dispose of the SegmentLoader and reset to the default state
54575     */
54576
54577    dispose() {
54578      this.trigger('dispose');
54579      this.state = 'DISPOSED';
54580      this.pause();
54581      this.abort_();
54582      if (this.transmuxer_) {
54583        this.transmuxer_.terminate();
54584      }
54585      this.resetStats_();
54586      if (this.checkBufferTimeout_) {
54587        window.clearTimeout(this.checkBufferTimeout_);
54588      }
54589      if (this.syncController_ && this.triggerSyncInfoUpdate_) {
54590        this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
54591      }
54592      this.off();
54593    }
54594    setAudio(enable) {
54595      this.audioDisabled_ = !enable;
54596      if (enable) {
54597        this.appendInitSegment_.audio = true;
54598      } else {
54599        // remove current track audio if it gets disabled
54600        this.sourceUpdater_.removeAudio(0, this.duration_());
54601      }
54602    }
54603    /**
54604     * abort anything that is currently doing on with the SegmentLoader
54605     * and reset to a default state
54606     */
54607
54608    abort() {
54609      if (this.state !== 'WAITING') {
54610        if (this.pendingSegment_) {
54611          this.pendingSegment_ = null;
54612        }
vendor: 6,902 bytes, lines 54612-54833
54612
54613        return;
54614      }
54615      this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
54616      // since we are no longer "waiting" on any requests. XHR callback is not always run
54617      // when the request is aborted. This will prevent the loader from being stuck in the
54618      // WAITING state indefinitely.
54619
54620      this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
54621      // next segment
54622
54623      if (!this.paused()) {
54624        this.monitorBuffer_();
54625      }
54626    }
54627    /**
54628     * abort all pending xhr requests and null any pending segements
54629     *
54630     * @private
54631     */
54632
54633    abort_() {
54634      if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
54635        this.pendingSegment_.abortRequests();
54636      } // clear out the segment being processed
54637
54638      this.pendingSegment_ = null;
54639      this.callQueue_ = [];
54640      this.loadQueue_ = [];
54641      this.metadataQueue_.id3 = [];
54642      this.metadataQueue_.caption = [];
54643      this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
54644      this.waitingOnRemove_ = false;
54645      window.clearTimeout(this.quotaExceededErrorRetryTimeout_);
54646      this.quotaExceededErrorRetryTimeout_ = null;
54647    }
54648    checkForAbort_(requestId) {
54649      // If the state is APPENDING, then aborts will not modify the state, meaning the first
54650      // callback that happens should reset the state to READY so that loading can continue.
54651      if (this.state === 'APPENDING' && !this.pendingSegment_) {
54652        this.state = 'READY';
54653        return true;
54654      }
54655      if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
54656        return true;
54657      }
54658      return false;
54659    }
54660    /**
54661     * set an error on the segment loader and null out any pending segements
54662     *
54663     * @param {Error} error the error to set on the SegmentLoader
54664     * @return {Error} the error that was set or that is currently set
54665     */
54666
54667    error(error) {
54668      if (typeof error !== 'undefined') {
54669        this.logger_('error occurred:', error);
54670        this.error_ = error;
54671      }
54672      this.pendingSegment_ = null;
54673      return this.error_;
54674    }
54675    endOfStream() {
54676      this.ended_ = true;
54677      if (this.transmuxer_) {
54678        // need to clear out any cached data to prepare for the new segment
54679        segmentTransmuxer.reset(this.transmuxer_);
54680      }
54681      this.gopBuffer_.length = 0;
54682      this.pause();
54683      this.trigger('ended');
54684    }
54685    /**
54686     * Indicates which time ranges are buffered
54687     *
54688     * @return {TimeRange}
54689     *         TimeRange object representing the current buffered ranges
54690     */
54691
54692    buffered_() {
54693      const trackInfo = this.getMediaInfo_();
54694      if (!this.sourceUpdater_ || !trackInfo) {
54695        return createTimeRanges();
54696      }
54697      if (this.loaderType_ === 'main') {
54698        const {
54699          hasAudio,
54700          hasVideo,
54701          isMuxed
54702        } = trackInfo;
54703        if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
54704          return this.sourceUpdater_.buffered();
54705        }
54706        if (hasVideo) {
54707          return this.sourceUpdater_.videoBuffered();
54708        }
54709      } // One case that can be ignored for now is audio only with alt audio,
54710      // as we don't yet have proper support for that.
54711
54712      return this.sourceUpdater_.audioBuffered();
54713    }
54714    /**
54715     * Gets and sets init segment for the provided map
54716     *
54717     * @param {Object} map
54718     *        The map object representing the init segment to get or set
54719     * @param {boolean=} set
54720     *        If true, the init segment for the provided map should be saved
54721     * @return {Object}
54722     *         map object for desired init segment
54723     */
54724
54725    initSegmentForMap(map, set = false) {
54726      if (!map) {
54727        return null;
54728      }
54729      const id = initSegmentId(map);
54730      let storedMap = this.initSegments_[id];
54731      if (set && !storedMap && map.bytes) {
54732        this.initSegments_[id] = storedMap = {
54733          resolvedUri: map.resolvedUri,
54734          byterange: map.byterange,
54735          bytes: map.bytes,
54736          tracks: map.tracks,
54737          timescales: map.timescales
54738        };
54739      }
54740      return storedMap || map;
54741    }
54742    /**
54743     * Gets and sets key for the provided key
54744     *
54745     * @param {Object} key
54746     *        The key object representing the key to get or set
54747     * @param {boolean=} set
54748     *        If true, the key for the provided key should be saved
54749     * @return {Object}
54750     *         Key object for desired key
54751     */
54752
54753    segmentKey(key, set = false) {
54754      if (!key) {
54755        return null;
54756      }
54757      const id = segmentKeyId(key);
54758      let storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
54759      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
54760
54761      if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
54762        this.keyCache_[id] = storedKey = {
54763          resolvedUri: key.resolvedUri,
54764          bytes: key.bytes
54765        };
54766      }
54767      const result = {
54768        resolvedUri: (storedKey || key).resolvedUri
54769      };
54770      if (storedKey) {
54771        result.bytes = storedKey.bytes;
54772      }
54773      return result;
54774    }
54775    /**
54776     * Returns true if all configuration required for loading is present, otherwise false.
54777     *
54778     * @return {boolean} True if the all configuration is ready for loading
54779     * @private
54780     */
54781
54782    couldBeginLoading_() {
54783      return this.playlist_ && !this.paused();
54784    }
54785    /**
54786     * load a playlist and start to fill the buffer
54787     */
54788
54789    load() {
54790      // un-pause
54791      this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
54792      // specified
54793
54794      if (!this.playlist_) {
54795        return;
54796      } // if all the configuration is ready, initialize and begin loading
54797
54798      if (this.state === 'INIT' && this.couldBeginLoading_()) {
54799        return this.init_();
54800      } // if we're in the middle of processing a segment already, don't
54801      // kick off an additional segment request
54802
54803      if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
54804        return;
54805      }
54806      this.state = 'READY';
54807    }
54808    /**
54809     * Once all the starting parameters have been specified, begin
54810     * operation. This method should only be invoked from the INIT
54811     * state.
54812     *
54813     * @private
54814     */
54815
54816    init_() {
54817      this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
54818      // audio data from the muxed content should be removed
54819
54820      this.resetEverything();
54821      return this.monitorBuffer_();
54822    }
54823    /**
54824     * set a playlist on the segment loader
54825     *
54826     * @param {PlaylistLoader} media the playlist to set on the segment loader
54827     */
54828
54829    playlist(newPlaylist, options = {}) {
54830      if (!newPlaylist) {
54831        return;
54832      }
54833      
vendor: 29,654 bytes, lines 54833-55511
54833const oldPlaylist = this.playlist_;
54834      const segmentInfo = this.pendingSegment_;
54835      this.playlist_ = newPlaylist;
54836      this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
54837      // is always our zero-time so force a sync update each time the playlist
54838      // is refreshed from the server
54839      //
54840      // Use the INIT state to determine if playback has started, as the playlist sync info
54841      // should be fixed once requests begin (as sync points are generated based on sync
54842      // info), but not before then.
54843
54844      if (this.state === 'INIT') {
54845        newPlaylist.syncInfo = {
54846          mediaSequence: newPlaylist.mediaSequence,
54847          time: 0
54848        }; // Setting the date time mapping means mapping the program date time (if available)
54849        // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
54850        // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
54851        // be updated as the playlist is refreshed before the loader starts loading, the
54852        // program date time mapping needs to be updated as well.
54853        //
54854        // This mapping is only done for the main loader because a program date time should
54855        // map equivalently between playlists.
54856
54857        if (this.loaderType_ === 'main') {
54858          this.syncController_.setDateTimeMappingForStart(newPlaylist);
54859        }
54860      }
54861      let oldId = null;
54862      if (oldPlaylist) {
54863        if (oldPlaylist.id) {
54864          oldId = oldPlaylist.id;
54865        } else if (oldPlaylist.uri) {
54866          oldId = oldPlaylist.uri;
54867        }
54868      }
54869      this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`);
54870      this.syncController_.updateMediaSequenceMap(newPlaylist, this.currentTime_(), this.loaderType_); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
54871      // in LIVE, we always want to update with new playlists (including refreshes)
54872
54873      this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
54874      // buffering now
54875
54876      if (this.state === 'INIT' && this.couldBeginLoading_()) {
54877        return this.init_();
54878      }
54879      if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
54880        if (this.mediaIndex !== null) {
54881          // we must reset/resync the segment loader when we switch renditions and
54882          // the segment loader is already synced to the previous rendition
54883          // We only want to reset the loader here for LLHLS playback, as resetLoader sets fetchAtBuffer_
54884          // to false, resulting in fetching segments at currentTime and causing repeated
54885          // same-segment requests on playlist change. This erroneously drives up the playback watcher
54886          // stalled segment count, as re-requesting segments at the currentTime or browser cached segments
54887          // will not change the buffer.
54888          // Reference for LLHLS fixes: https://github.com/videojs/http-streaming/pull/1201
54889          const isLLHLS = !newPlaylist.endList && typeof newPlaylist.partTargetDuration === 'number';
54890          if (isLLHLS) {
54891            this.resetLoader();
54892          } else {
54893            this.resyncLoader();
54894          }
54895        }
54896        this.currentMediaInfo_ = void 0;
54897        this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
54898
54899        return;
54900      } // we reloaded the same playlist so we are in a live scenario
54901      // and we will likely need to adjust the mediaIndex
54902
54903      const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
54904      this.logger_(`live window shift [${mediaSequenceDiff}]`); // update the mediaIndex on the SegmentLoader
54905      // this is important because we can abort a request and this value must be
54906      // equal to the last appended mediaIndex
54907
54908      if (this.mediaIndex !== null) {
54909        this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
54910        // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
54911        // new playlist was incremented by 1.
54912
54913        if (this.mediaIndex < 0) {
54914          this.mediaIndex = null;
54915          this.partIndex = null;
54916        } else {
54917          const segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
54918          // unless parts fell off of the playlist for this segment.
54919          // In that case we need to reset partIndex and resync
54920
54921          if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
54922            const mediaIndex = this.mediaIndex;
54923            this.logger_(`currently processing part (index ${this.partIndex}) no longer exists.`);
54924            this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
54925            // as the part was dropped from our current playlists segment.
54926            // The mediaIndex will still be valid so keep that around.
54927
54928            this.mediaIndex = mediaIndex;
54929          }
54930        }
54931      } // update the mediaIndex on the SegmentInfo object
54932      // this is important because we will update this.mediaIndex with this value
54933      // in `handleAppendsDone_` after the segment has been successfully appended
54934
54935      if (segmentInfo) {
54936        segmentInfo.mediaIndex -= mediaSequenceDiff;
54937        if (segmentInfo.mediaIndex < 0) {
54938          segmentInfo.mediaIndex = null;
54939          segmentInfo.partIndex = null;
54940        } else {
54941          // we need to update the referenced segment so that timing information is
54942          // saved for the new playlist's segment, however, if the segment fell off the
54943          // playlist, we can leave the old reference and just lose the timing info
54944          if (segmentInfo.mediaIndex >= 0) {
54945            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
54946          }
54947          if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
54948            segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
54949          }
54950        }
54951      }
54952      this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
54953    }
54954    /**
54955     * Prevent the loader from fetching additional segments. If there
54956     * is a segment request outstanding, it will finish processing
54957     * before the loader halts. A segment loader can be unpaused by
54958     * calling load().
54959     */
54960
54961    pause() {
54962      if (this.checkBufferTimeout_) {
54963        window.clearTimeout(this.checkBufferTimeout_);
54964        this.checkBufferTimeout_ = null;
54965      }
54966    }
54967    /**
54968     * Returns whether the segment loader is fetching additional
54969     * segments when given the opportunity. This property can be
54970     * modified through calls to pause() and load().
54971     */
54972
54973    paused() {
54974      return this.checkBufferTimeout_ === null;
54975    }
54976    /**
54977     * Delete all the buffered data and reset the SegmentLoader
54978     *
54979     * @param {Function} [done] an optional callback to be executed when the remove
54980     * operation is complete
54981     */
54982
54983    resetEverything(done) {
54984      this.ended_ = false;
54985      this.activeInitSegmentId_ = null;
54986      this.appendInitSegment_ = {
54987        audio: true,
54988        video: true
54989      };
54990      this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
54991      // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
54992      // we then clamp the value to duration if necessary.
54993
54994      this.remove(0, Infinity, done); // clears fmp4 captions
54995
54996      if (this.transmuxer_) {
54997        this.transmuxer_.postMessage({
54998          action: 'clearAllMp4Captions'
54999        }); // reset the cache in the transmuxer
55000
55001        this.transmuxer_.postMessage({
55002          action: 'reset'
55003        });
55004      }
55005    }
55006    /**
55007     * Force the SegmentLoader to resync and start loading around the currentTime instead
55008     * of starting at the end of the buffer
55009     *
55010     * Useful for fast quality changes
55011     */
55012
55013    resetLoader() {
55014      this.fetchAtBuffer_ = false;
55015      this.resyncLoader();
55016    }
55017    /**
55018     * Force the SegmentLoader to restart synchronization and make a conservative guess
55019     * before returning to the simple walk-forward method
55020     */
55021
55022    resyncLoader() {
55023      if (this.transmuxer_) {
55024        // need to clear out any cached data to prepare for the new segment
55025        segmentTransmuxer.reset(this.transmuxer_);
55026      }
55027      this.mediaIndex = null;
55028      this.partIndex = null;
55029      this.syncPoint_ = null;
55030      this.isPendingTimestampOffset_ = false;
55031      this.shouldForceTimestampOffsetAfterResync_ = true;
55032      this.callQueue_ = [];
55033      this.loadQueue_ = [];
55034      this.metadataQueue_.id3 = [];
55035      this.metadataQueue_.caption = [];
55036      this.abort();
55037      if (this.transmuxer_) {
55038        this.transmuxer_.postMessage({
55039          action: 'clearParsedMp4Captions'
55040        });
55041      }
55042    }
55043    /**
55044     * Remove any data in the source buffer between start and end times
55045     *
55046     * @param {number} start - the start time of the region to remove from the buffer
55047     * @param {number} end - the end time of the region to remove from the buffer
55048     * @param {Function} [done] - an optional callback to be executed when the remove
55049     * @param {boolean} force - force all remove operations to happen
55050     * operation is complete
55051     */
55052
55053    remove(start, end, done = () => {}, force = false) {
55054      // clamp end to duration if we need to remove everything.
55055      // This is due to a browser bug that causes issues if we remove to Infinity.
55056      // videojs/videojs-contrib-hls#1225
55057      if (end === Infinity) {
55058        end = this.duration_();
55059      } // skip removes that would throw an error
55060      // commonly happens during a rendition switch at the start of a video
55061      // from start 0 to end 0
55062
55063      if (end <= start) {
55064        this.logger_('skipping remove because end ${end} is <= start ${start}');
55065        return;
55066      }
55067      if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
55068        this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
55069
55070        return;
55071      } // set it to one to complete this function's removes
55072
55073      let removesRemaining = 1;
55074      const removeFinished = () => {
55075        removesRemaining--;
55076        if (removesRemaining === 0) {
55077          done();
55078        }
55079      };
55080      if (force || !this.audioDisabled_) {
55081        removesRemaining++;
55082        this.sourceUpdater_.removeAudio(start, end, removeFinished);
55083      } // While it would be better to only remove video if the main loader has video, this
55084      // should be safe with audio only as removeVideo will call back even if there's no
55085      // video buffer.
55086      //
55087      // In theory we can check to see if there's video before calling the remove, but in
55088      // the event that we're switching between renditions and from video to audio only
55089      // (when we add support for that), we may need to clear the video contents despite
55090      // what the new media will contain.
55091
55092      if (force || this.loaderType_ === 'main') {
55093        this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
55094        removesRemaining++;
55095        this.sourceUpdater_.removeVideo(start, end, removeFinished);
55096      } // remove any captions and ID3 tags
55097
55098      for (const track in this.inbandTextTracks_) {
55099        removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
55100      }
55101      removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
55102
55103      removeFinished();
55104    }
55105    /**
55106     * (re-)schedule monitorBufferTick_ to run as soon as possible
55107     *
55108     * @private
55109     */
55110
55111    monitorBuffer_() {
55112      if (this.checkBufferTimeout_) {
55113        window.clearTimeout(this.checkBufferTimeout_);
55114      }
55115      this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), 1);
55116    }
55117    /**
55118     * As long as the SegmentLoader is in the READY state, periodically
55119     * invoke fillBuffer_().
55120     *
55121     * @private
55122     */
55123
55124    monitorBufferTick_() {
55125      if (this.state === 'READY') {
55126        this.fillBuffer_();
55127      }
55128      if (this.checkBufferTimeout_) {
55129        window.clearTimeout(this.checkBufferTimeout_);
55130      }
55131      this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
55132    }
55133    /**
55134     * fill the buffer with segements unless the sourceBuffers are
55135     * currently updating
55136     *
55137     * Note: this function should only ever be called by monitorBuffer_
55138     * and never directly
55139     *
55140     * @private
55141     */
55142
55143    fillBuffer_() {
55144      // TODO since the source buffer maintains a queue, and we shouldn't call this function
55145      // except when we're ready for the next segment, this check can most likely be removed
55146      if (this.sourceUpdater_.updating()) {
55147        return;
55148      } // see if we need to begin loading immediately
55149
55150      const segmentInfo = this.chooseNextRequest_();
55151      if (!segmentInfo) {
55152        return;
55153      }
55154      if (typeof segmentInfo.timestampOffset === 'number') {
55155        this.isPendingTimestampOffset_ = false;
55156        this.timelineChangeController_.pendingTimelineChange({
55157          type: this.loaderType_,
55158          from: this.currentTimeline_,
55159          to: segmentInfo.timeline
55160        });
55161      }
55162      this.loadSegment_(segmentInfo);
55163    }
55164    /**
55165     * Determines if we should call endOfStream on the media source based
55166     * on the state of the buffer or if appened segment was the final
55167     * segment in the playlist.
55168     *
55169     * @param {number} [mediaIndex] the media index of segment we last appended
55170     * @param {Object} [playlist] a media playlist object
55171     * @return {boolean} do we need to call endOfStream on the MediaSource
55172     */
55173
55174    isEndOfStream_(mediaIndex = this.mediaIndex, playlist = this.playlist_, partIndex = this.partIndex) {
55175      if (!playlist || !this.mediaSource_) {
55176        return false;
55177      }
55178      const segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
55179
55180      const appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
55181
55182      const appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream
55183      // so that MediaSources can trigger the `ended` event when it runs out of
55184      // buffered data instead of waiting for me
55185
55186      return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
55187    }
55188    /**
55189     * Determines what request should be made given current segment loader state.
55190     *
55191     * @return {Object} a request object that describes the segment/part to load
55192     */
55193
55194    chooseNextRequest_() {
55195      const buffered = this.buffered_();
55196      const bufferedEnd = lastBufferedEnd(buffered) || 0;
55197      const bufferedTime = timeAheadOf(buffered, this.currentTime_());
55198      const preloaded = !this.hasPlayed_() && bufferedTime >= 1;
55199      const haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
55200      const segments = this.playlist_.segments; // return no segment if:
55201      // 1. we don't have segments
55202      // 2. The video has not yet played and we already downloaded a segment
55203      // 3. we already have enough buffered time
55204
55205      if (!segments.length || preloaded || haveEnoughBuffer) {
55206        return null;
55207      }
55208      this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_(), this.loaderType_);
55209      const next = {
55210        partIndex: null,
55211        mediaIndex: null,
55212        startOfSegment: null,
55213        playlist: this.playlist_,
55214        isSyncRequest: Boolean(!this.syncPoint_)
55215      };
55216      if (next.isSyncRequest) {
55217        next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
55218        this.logger_(`choose next request. Can not find sync point. Fallback to media Index: ${next.mediaIndex}`);
55219      } else if (this.mediaIndex !== null) {
55220        const segment = segments[this.mediaIndex];
55221        const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
55222        next.startOfSegment = segment.end ? segment.end : bufferedEnd;
55223        if (segment.parts && segment.parts[partIndex + 1]) {
55224          next.mediaIndex = this.mediaIndex;
55225          next.partIndex = partIndex + 1;
55226        } else {
55227          next.mediaIndex = this.mediaIndex + 1;
55228        }
55229      } else {
55230        // Find the segment containing the end of the buffer or current time.
55231        const {
55232          segmentIndex,
55233          startTime,
55234          partIndex
55235        } = Playlist.getMediaInfoForTime({
55236          exactManifestTimings: this.exactManifestTimings,
55237          playlist: this.playlist_,
55238          currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
55239          startingPartIndex: this.syncPoint_.partIndex,
55240          startingSegmentIndex: this.syncPoint_.segmentIndex,
55241          startTime: this.syncPoint_.time
55242        });
55243        next.getMediaInfoForTime = this.fetchAtBuffer_ ? `bufferedEnd ${bufferedEnd}` : `currentTime ${this.currentTime_()}`;
55244        next.mediaIndex = segmentIndex;
55245        next.startOfSegment = startTime;
55246        next.partIndex = partIndex;
55247        this.logger_(`choose next request. Playlist switched and we have a sync point. Media Index: ${next.mediaIndex} `);
55248      }
55249      const nextSegment = segments[next.mediaIndex];
55250      let nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
55251      // the next partIndex is invalid do not choose a next segment.
55252
55253      if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
55254        return null;
55255      } // if the next segment has parts, and we don't have a partIndex.
55256      // Set partIndex to 0
55257
55258      if (typeof next.partIndex !== 'number' && nextSegment.parts) {
55259        next.partIndex = 0;
55260        nextPart = nextSegment.parts[0];
55261      } // independentSegments applies to every segment in a playlist. If independentSegments appears in a main playlist,
55262      // it applies to each segment in each media playlist.
55263      // https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.5.1
55264
55265      const hasIndependentSegments = this.vhs_.playlists && this.vhs_.playlists.main && this.vhs_.playlists.main.independentSegments || this.playlist_.independentSegments; // if we have no buffered data then we need to make sure
55266      // that the next part we append is "independent" if possible.
55267      // So we check if the previous part is independent, and request
55268      // it if it is.
55269
55270      if (!bufferedTime && nextPart && !hasIndependentSegments && !nextPart.independent) {
55271        if (next.partIndex === 0) {
55272          const lastSegment = segments[next.mediaIndex - 1];
55273          const lastSegmentLastPart = lastSegment.parts && lastSegment.parts.length && lastSegment.parts[lastSegment.parts.length - 1];
55274          if (lastSegmentLastPart && lastSegmentLastPart.independent) {
55275            next.mediaIndex -= 1;
55276            next.partIndex = lastSegment.parts.length - 1;
55277            next.independent = 'previous segment';
55278          }
55279        } else if (nextSegment.parts[next.partIndex - 1].independent) {
55280          next.partIndex -= 1;
55281          next.independent = 'previous part';
55282        }
55283      }
55284      const ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
55285      // 1. this is the last segment in the playlist
55286      // 2. end of stream has been called on the media source already
55287      // 3. the player is not seeking
55288
55289      if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
55290        return null;
55291      }
55292      if (this.shouldForceTimestampOffsetAfterResync_) {
55293        this.shouldForceTimestampOffsetAfterResync_ = false;
55294        next.forceTimestampOffset = true;
55295        this.logger_('choose next request. Force timestamp offset after loader resync');
55296      }
55297      return this.generateSegmentInfo_(next);
55298    }
55299    generateSegmentInfo_(options) {
55300      const {
55301        independent,
55302        playlist,
55303        mediaIndex,
55304        startOfSegment,
55305        isSyncRequest,
55306        partIndex,
55307        forceTimestampOffset,
55308        getMediaInfoForTime
55309      } = options;
55310      const segment = playlist.segments[mediaIndex];
55311      const part = typeof partIndex === 'number' && segment.parts[partIndex];
55312      const segmentInfo = {
55313        requestId: 'segment-loader-' + Math.random(),
55314        // resolve the segment URL relative to the playlist
55315        uri: part && part.resolvedUri || segment.resolvedUri,
55316        // the segment's mediaIndex at the time it was requested
55317        mediaIndex,
55318        partIndex: part ? partIndex : null,
55319        // whether or not to update the SegmentLoader's state with this
55320        // segment's mediaIndex
55321        isSyncRequest,
55322        startOfSegment,
55323        // the segment's playlist
55324        playlist,
55325        // unencrypted bytes of the segment
55326        bytes: null,
55327        // when a key is defined for this segment, the encrypted bytes
55328        encryptedBytes: null,
55329        // The target timestampOffset for this segment when we append it
55330        // to the source buffer
55331        timestampOffset: null,
55332        // The timeline that the segment is in
55333        timeline: segment.timeline,
55334        // The expected duration of the segment in seconds
55335        duration: part && part.duration || segment.duration,
55336        // retain the segment in case the playlist updates while doing an async process
55337        segment,
55338        part,
55339        byteLength: 0,
55340        transmuxer: this.transmuxer_,
55341        // type of getMediaInfoForTime that was used to get this segment
55342        getMediaInfoForTime,
55343        independent
55344      };
55345      const overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
55346      segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
55347        segmentTimeline: segment.timeline,
55348        currentTimeline: this.currentTimeline_,
55349        startOfSegment,
55350        buffered: this.buffered_(),
55351        overrideCheck
55352      });
55353      const audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
55354      if (typeof audioBufferedEnd === 'number') {
55355        // since the transmuxer is using the actual timing values, but the buffer is
55356        // adjusted by the timestamp offset, we must adjust the value here
55357        segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
55358      }
55359      if (this.sourceUpdater_.videoBuffered().length) {
55360        segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_,
55361        // since the transmuxer is using the actual timing values, but the time is
55362        // adjusted by the timestmap offset, we must adjust the value here
55363        this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
55364      }
55365      return segmentInfo;
55366    } // get the timestampoffset for a segment,
55367    // added so that vtt segment loader can override and prevent
55368    // adding timestamp offsets.
55369
55370    timestampOffsetForSegment_(options) {
55371      return timestampOffsetForSegment(options);
55372    }
55373    /**
55374     * Determines if the network has enough bandwidth to complete the current segment
55375     * request in a timely manner. If not, the request will be aborted early and bandwidth
55376     * updated to trigger a playlist switch.
55377     *
55378     * @param {Object} stats
55379     *        Object containing stats about the request timing and size
55380     * @private
55381     */
55382
55383    earlyAbortWhenNeeded_(stats) {
55384      if (this.vhs_.tech_.paused() ||
55385      // Don't abort if the current playlist is on the lowestEnabledRendition
55386      // TODO: Replace using timeout with a boolean indicating whether this playlist is
55387      //       the lowestEnabledRendition.
55388      !this.xhrOptions_.timeout ||
55389      // Don't abort if we have no bandwidth information to estimate segment sizes
55390      !this.playlist_.attributes.BANDWIDTH) {
55391        return;
55392      } // Wait at least 1 second since the first byte of data has been received before
55393      // using the calculated bandwidth from the progress event to allow the bitrate
55394      // to stabilize
55395
55396      if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
55397        return;
55398      }
55399      const currentTime = this.currentTime_();
55400      const measuredBandwidth = stats.bandwidth;
55401      const segmentDuration = this.pendingSegment_.duration;
55402      const requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
55403      // if we are only left with less than 1 second when the request completes.
55404      // A negative timeUntilRebuffering indicates we are already rebuffering
55405
55406      const timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
55407      // is larger than the estimated time until the player runs out of forward buffer
55408
55409      if (requestTimeRemaining <= timeUntilRebuffer$1) {
55410        return;
55411      }
55412      const switchCandidate = minRebufferMaxBandwidthSelector({
55413        main: this.vhs_.playlists.main,
55414        currentTime,
55415        bandwidth: measuredBandwidth,
55416        duration: this.duration_(),
55417        segmentDuration,
55418        timeUntilRebuffer: timeUntilRebuffer$1,
55419        currentTimeline: this.currentTimeline_,
55420        syncController: this.syncController_
55421      });
55422      if (!switchCandidate) {
55423        return;
55424      }
55425      const rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
55426      const timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
55427      let minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
55428      // potential round trip time of the new request so that we are not too aggressive
55429      // with switching to a playlist that might save us a fraction of a second.
55430
55431      if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
55432        minimumTimeSaving = 1;
55433      }
55434      if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
55435        return;
55436      } // set the bandwidth to that of the desired playlist being sure to scale by
55437      // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
55438      // don't trigger a bandwidthupdate as the bandwidth is artifial
55439
55440      this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
55441      this.trigger('earlyabort');
55442    }
55443    handleAbort_(segmentInfo) {
55444      this.logger_(`Aborting ${segmentInfoString(segmentInfo)}`);
55445      this.mediaRequestsAborted += 1;
55446    }
55447    /**
55448     * XHR `progress` event handler
55449     *
55450     * @param {Event}
55451     *        The XHR `progress` event
55452     * @param {Object} simpleSegment
55453     *        A simplified segment object copy
55454     * @private
55455     */
55456
55457    handleProgress_(event, simpleSegment) {
55458      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55459      if (this.checkForAbort_(simpleSegment.requestId)) {
55460        return;
55461      }
55462      this.trigger('progress');
55463    }
55464    handleTrackInfo_(simpleSegment, trackInfo) {
55465      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55466      if (this.checkForAbort_(simpleSegment.requestId)) {
55467        return;
55468      }
55469      if (this.checkForIllegalMediaSwitch(trackInfo)) {
55470        return;
55471      }
55472      trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
55473      // Guard against cases where we're not getting track info at all until we are
55474      // certain that all streams will provide it.
55475
55476      if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
55477        this.appendInitSegment_ = {
55478          audio: true,
55479          video: true
55480        };
55481        this.startingMediaInfo_ = trackInfo;
55482        this.currentMediaInfo_ = trackInfo;
55483        this.logger_('trackinfo update', trackInfo);
55484        this.trigger('trackinfo');
55485      } // trackinfo may cause an abort if the trackinfo
55486      // causes a codec change to an unsupported codec.
55487
55488      if (this.checkForAbort_(simpleSegment.requestId)) {
55489        return;
55490      } // set trackinfo on the pending segment so that
55491      // it can append.
55492
55493      this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
55494
55495      if (this.hasEnoughInfoToAppend_()) {
55496        this.processCallQueue_();
55497      }
55498    }
55499    handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
55500      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55501      if (this.checkForAbort_(simpleSegment.requestId)) {
55502        return;
55503      }
55504      const segmentInfo = this.pendingSegment_;
55505      const timingInfoProperty = timingInfoPropertyForMedia(mediaType);
55506      segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
55507      segmentInfo[timingInfoProperty][timeType] = time;
55508      this.logger_(`timinginfo: ${mediaType} - ${timeType} - ${time}`); // check if any calls were waiting on the timing info
55509
55510      if (this.hasEnoughInfoToAppend_()) {
55511        this.processCallQueue_(
vendor: 8,510 bytes, lines 55511-55703
55511);
55512      }
55513    }
55514    handleCaptions_(simpleSegment, captionData) {
55515      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55516      if (this.checkForAbort_(simpleSegment.requestId)) {
55517        return;
55518      } // This could only happen with fmp4 segments, but
55519      // should still not happen in general
55520
55521      if (captionData.length === 0) {
55522        this.logger_('SegmentLoader received no captions from a caption event');
55523        return;
55524      }
55525      const segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
55526      // can be adjusted by the timestamp offset
55527
55528      if (!segmentInfo.hasAppendedData_) {
55529        this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
55530        return;
55531      }
55532      const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
55533      const captionTracks = {}; // get total start/end and captions for each track/stream
55534
55535      captionData.forEach(caption => {
55536        // caption.stream is actually a track name...
55537        // set to the existing values in tracks or default values
55538        captionTracks[caption.stream] = captionTracks[caption.stream] || {
55539          // Infinity, as any other value will be less than this
55540          startTime: Infinity,
55541          captions: [],
55542          // 0 as an other value will be more than this
55543          endTime: 0
55544        };
55545        const captionTrack = captionTracks[caption.stream];
55546        captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
55547        captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
55548        captionTrack.captions.push(caption);
55549      });
55550      Object.keys(captionTracks).forEach(trackName => {
55551        const {
55552          startTime,
55553          endTime,
55554          captions
55555        } = captionTracks[trackName];
55556        const inbandTextTracks = this.inbandTextTracks_;
55557        this.logger_(`adding cues from ${startTime} -> ${endTime} for ${trackName}`);
55558        createCaptionsTrackIfNotExists(inbandTextTracks, this.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
55559        // We do this because a rendition change that also changes the timescale for captions
55560        // will result in captions being re-parsed for certain segments. If we add them again
55561        // without clearing we will have two of the same captions visible.
55562
55563        removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
55564        addCaptionData({
55565          captionArray: captions,
55566          inbandTextTracks,
55567          timestampOffset
55568        });
55569      }); // Reset stored captions since we added parsed
55570      // captions to a text track at this point
55571
55572      if (this.transmuxer_) {
55573        this.transmuxer_.postMessage({
55574          action: 'clearParsedMp4Captions'
55575        });
55576      }
55577    }
55578    handleId3_(simpleSegment, id3Frames, dispatchType) {
55579      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55580      if (this.checkForAbort_(simpleSegment.requestId)) {
55581        return;
55582      }
55583      const segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
55584
55585      if (!segmentInfo.hasAppendedData_) {
55586        this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
55587        return;
55588      }
55589      this.addMetadataToTextTrack(dispatchType, id3Frames, this.duration_());
55590    }
55591    processMetadataQueue_() {
55592      this.metadataQueue_.id3.forEach(fn => fn());
55593      this.metadataQueue_.caption.forEach(fn => fn());
55594      this.metadataQueue_.id3 = [];
55595      this.metadataQueue_.caption = [];
55596    }
55597    processCallQueue_() {
55598      const callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
55599      // functions may check the length of the load queue and default to pushing themselves
55600      // back onto the queue.
55601
55602      this.callQueue_ = [];
55603      callQueue.forEach(fun => fun());
55604    }
55605    processLoadQueue_() {
55606      const loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
55607      // functions may check the length of the load queue and default to pushing themselves
55608      // back onto the queue.
55609
55610      this.loadQueue_ = [];
55611      loadQueue.forEach(fun => fun());
55612    }
55613    /**
55614     * Determines whether the loader has enough info to load the next segment.
55615     *
55616     * @return {boolean}
55617     *         Whether or not the loader has enough info to load the next segment
55618     */
55619
55620    hasEnoughInfoToLoad_() {
55621      // Since primary timing goes by video, only the audio loader potentially needs to wait
55622      // to load.
55623      if (this.loaderType_ !== 'audio') {
55624        return true;
55625      }
55626      const segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
55627      // enough info to load.
55628
55629      if (!segmentInfo) {
55630        return false;
55631      } // The first segment can and should be loaded immediately so that source buffers are
55632      // created together (before appending). Source buffer creation uses the presence of
55633      // audio and video data to determine whether to create audio/video source buffers, and
55634      // uses processed (transmuxed or parsed) media to determine the types required.
55635
55636      if (!this.getCurrentMediaInfo_()) {
55637        return true;
55638      }
55639      if (
55640      // Technically, instead of waiting to load a segment on timeline changes, a segment
55641      // can be requested and downloaded and only wait before it is transmuxed or parsed.
55642      // But in practice, there are a few reasons why it is better to wait until a loader
55643      // is ready to append that segment before requesting and downloading:
55644      //
55645      // 1. Because audio and main loaders cross discontinuities together, if this loader
55646      //    is waiting for the other to catch up, then instead of requesting another
55647      //    segment and using up more bandwidth, by not yet loading, more bandwidth is
55648      //    allotted to the loader currently behind.
55649      // 2. media-segment-request doesn't have to have logic to consider whether a segment
55650      // is ready to be processed or not, isolating the queueing behavior to the loader.
55651      // 3. The audio loader bases some of its segment properties on timing information
55652      //    provided by the main loader, meaning that, if the logic for waiting on
55653      //    processing was in media-segment-request, then it would also need to know how
55654      //    to re-generate the segment information after the main loader caught up.
55655      shouldWaitForTimelineChange({
55656        timelineChangeController: this.timelineChangeController_,
55657        currentTimeline: this.currentTimeline_,
55658        segmentTimeline: segmentInfo.timeline,
55659        loaderType: this.loaderType_,
55660        audioDisabled: this.audioDisabled_
55661      })) {
55662        return false;
55663      }
55664      return true;
55665    }
55666    getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) {
55667      return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
55668    }
55669    getMediaInfo_(segmentInfo = this.pendingSegment_) {
55670      return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
55671    }
55672    getPendingSegmentPlaylist() {
55673      return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
55674    }
55675    hasEnoughInfoToAppend_() {
55676      if (!this.sourceUpdater_.ready()) {
55677        return false;
55678      } // If content needs to be removed or the loader is waiting on an append reattempt,
55679      // then no additional content should be appended until the prior append is resolved.
55680
55681      if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
55682        return false;
55683      }
55684      const segmentInfo = this.pendingSegment_;
55685      const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
55686      // we do not have information on this specific
55687      // segment yet
55688
55689      if (!segmentInfo || !trackInfo) {
55690        return false;
55691      }
55692      const {
55693        hasAudio,
55694        hasVideo,
55695        isMuxed
55696      } = trackInfo;
55697      if (hasVideo && !segmentInfo.videoTimingInfo) {
55698        return false;
55699      } // muxed content only relies on video timing information for now.
55700
55701      if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
55702        return false;
55703      }
vendor: 24,829 bytes, lines 55703-56235
55703
55704      if (shouldWaitForTimelineChange({
55705        timelineChangeController: this.timelineChangeController_,
55706        currentTimeline: this.currentTimeline_,
55707        segmentTimeline: segmentInfo.timeline,
55708        loaderType: this.loaderType_,
55709        audioDisabled: this.audioDisabled_
55710      })) {
55711        return false;
55712      }
55713      return true;
55714    }
55715    handleData_(simpleSegment, result) {
55716      this.earlyAbortWhenNeeded_(simpleSegment.stats);
55717      if (this.checkForAbort_(simpleSegment.requestId)) {
55718        return;
55719      } // If there's anything in the call queue, then this data came later and should be
55720      // executed after the calls currently queued.
55721
55722      if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
55723        this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
55724        return;
55725      }
55726      const segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
55727
55728      this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
55729
55730      this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
55731      // logic may change behavior depending on the state, and changing state too early may
55732      // inflate our estimates of bandwidth. In the future this should be re-examined to
55733      // note more granular states.
55734      // don't process and append data if the mediaSource is closed
55735
55736      if (this.mediaSource_.readyState === 'closed') {
55737        return;
55738      } // if this request included an initialization segment, save that data
55739      // to the initSegment cache
55740
55741      if (simpleSegment.map) {
55742        simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
55743
55744        segmentInfo.segment.map = simpleSegment.map;
55745      } // if this request included a segment key, save that data in the cache
55746
55747      if (simpleSegment.key) {
55748        this.segmentKey(simpleSegment.key, true);
55749      }
55750      segmentInfo.isFmp4 = simpleSegment.isFmp4;
55751      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
55752      if (segmentInfo.isFmp4) {
55753        this.trigger('fmp4');
55754        segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
55755      } else {
55756        const trackInfo = this.getCurrentMediaInfo_();
55757        const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
55758        let firstVideoFrameTimeForData;
55759        if (useVideoTimingInfo) {
55760          firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
55761        } // Segment loader knows more about segment timing than the transmuxer (in certain
55762        // aspects), so make any changes required for a more accurate start time.
55763        // Don't set the end time yet, as the segment may not be finished processing.
55764
55765        segmentInfo.timingInfo.start = this.trueSegmentStart_({
55766          currentStart: segmentInfo.timingInfo.start,
55767          playlist: segmentInfo.playlist,
55768          mediaIndex: segmentInfo.mediaIndex,
55769          currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
55770          useVideoTimingInfo,
55771          firstVideoFrameTimeForData,
55772          videoTimingInfo: segmentInfo.videoTimingInfo,
55773          audioTimingInfo: segmentInfo.audioTimingInfo
55774        });
55775      } // Init segments for audio and video only need to be appended in certain cases. Now
55776      // that data is about to be appended, we can check the final cases to determine
55777      // whether we should append an init segment.
55778
55779      this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
55780      // as we use the start of the segment to offset the best guess (playlist provided)
55781      // timestamp offset.
55782
55783      this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
55784      // be appended or not.
55785
55786      if (segmentInfo.isSyncRequest) {
55787        // first save/update our timing info for this segment.
55788        // this is what allows us to choose an accurate segment
55789        // and the main reason we make a sync request.
55790        this.updateTimingInfoEnd_(segmentInfo);
55791        this.syncController_.saveSegmentTimingInfo({
55792          segmentInfo,
55793          shouldSaveTimelineMapping: this.loaderType_ === 'main'
55794        });
55795        const next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
55796        // after taking into account its timing info, do not append it.
55797
55798        if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
55799          this.logger_('sync segment was incorrect, not appending');
55800          return;
55801        } // otherwise append it like any other segment as our guess was correct.
55802
55803        this.logger_('sync segment was correct, appending');
55804      } // Save some state so that in the future anything waiting on first append (and/or
55805      // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
55806      // we need some notion of whether the timestamp offset or other relevant information
55807      // has had a chance to be set.
55808
55809      segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
55810
55811      this.processMetadataQueue_();
55812      this.appendData_(segmentInfo, result);
55813    }
55814    updateAppendInitSegmentStatus(segmentInfo, type) {
55815      // alt audio doesn't manage timestamp offset
55816      if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' &&
55817      // in the case that we're handling partial data, we don't want to append an init
55818      // segment for each chunk
55819      !segmentInfo.changedTimestampOffset) {
55820        // if the timestamp offset changed, the timeline may have changed, so we have to re-
55821        // append init segments
55822        this.appendInitSegment_ = {
55823          audio: true,
55824          video: true
55825        };
55826      }
55827      if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
55828        // make sure we append init segment on playlist changes, in case the media config
55829        // changed
55830        this.appendInitSegment_[type] = true;
55831      }
55832    }
55833    getInitSegmentAndUpdateState_({
55834      type,
55835      initSegment,
55836      map,
55837      playlist
55838    }) {
55839      // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
55840      // (Section 3) required to parse the applicable Media Segments.  It applies to every
55841      // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
55842      // or until the end of the playlist."
55843      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
55844      if (map) {
55845        const id = initSegmentId(map);
55846        if (this.activeInitSegmentId_ === id) {
55847          // don't need to re-append the init segment if the ID matches
55848          return null;
55849        } // a map-specified init segment takes priority over any transmuxed (or otherwise
55850        // obtained) init segment
55851        //
55852        // this also caches the init segment for later use
55853
55854        initSegment = this.initSegmentForMap(map, true).bytes;
55855        this.activeInitSegmentId_ = id;
55856      } // We used to always prepend init segments for video, however, that shouldn't be
55857      // necessary. Instead, we should only append on changes, similar to what we've always
55858      // done for audio. This is more important (though may not be that important) for
55859      // frame-by-frame appending for LHLS, simply because of the increased quantity of
55860      // appends.
55861
55862      if (initSegment && this.appendInitSegment_[type]) {
55863        // Make sure we track the playlist that we last used for the init segment, so that
55864        // we can re-append the init segment in the event that we get data from a new
55865        // playlist. Discontinuities and track changes are handled in other sections.
55866        this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
55867
55868        this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
55869        // we are appending the muxer init segment
55870
55871        this.activeInitSegmentId_ = null;
55872        return initSegment;
55873      }
55874      return null;
55875    }
55876    handleQuotaExceededError_({
55877      segmentInfo,
55878      type,
55879      bytes
55880    }, error) {
55881      const audioBuffered = this.sourceUpdater_.audioBuffered();
55882      const videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
55883      // should be cleared out during the buffer removals. However, log in case it helps
55884      // debug.
55885
55886      if (audioBuffered.length > 1) {
55887        this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
55888      }
55889      if (videoBuffered.length > 1) {
55890        this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
55891      }
55892      const audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
55893      const audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
55894      const videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
55895      const videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
55896      if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
55897        // Can't remove enough buffer to make room for new segment (or the browser doesn't
55898        // allow for appends of segments this size). In the future, it may be possible to
55899        // split up the segment and append in pieces, but for now, error out this playlist
55900        // in an attempt to switch to a more manageable rendition.
55901        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(', ')}, `);
55902        this.error({
55903          message: 'Quota exceeded error with append of a single segment of content',
55904          excludeUntil: Infinity
55905        });
55906        this.trigger('error');
55907        return;
55908      } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
55909      // that the segment-loader should block on future events until this one is handled, so
55910      // that it doesn't keep moving onto further segments. Adding the call to the call
55911      // queue will prevent further appends until waitingOnRemove_ and
55912      // quotaExceededErrorRetryTimeout_ are cleared.
55913      //
55914      // Note that this will only block the current loader. In the case of demuxed content,
55915      // the other load may keep filling as fast as possible. In practice, this should be
55916      // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
55917      // source buffer, or video fills without enough room for audio to append (and without
55918      // the availability of clearing out seconds of back buffer to make room for audio).
55919      // But it might still be good to handle this case in the future as a TODO.
55920
55921      this.waitingOnRemove_ = true;
55922      this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
55923        segmentInfo,
55924        type,
55925        bytes
55926      }));
55927      const currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
55928      // before retrying.
55929
55930      const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
55931      this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`);
55932      this.remove(0, timeToRemoveUntil, () => {
55933        this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`);
55934        this.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
55935        // attempts (since we can't clear less than the minimum)
55936
55937        this.quotaExceededErrorRetryTimeout_ = window.setTimeout(() => {
55938          this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
55939          this.quotaExceededErrorRetryTimeout_ = null;
55940          this.processCallQueue_();
55941        }, MIN_BACK_BUFFER * 1000);
55942      }, true);
55943    }
55944    handleAppendError_({
55945      segmentInfo,
55946      type,
55947      bytes
55948    }, error) {
55949      // if there's no error, nothing to do
55950      if (!error) {
55951        return;
55952      }
55953      if (error.code === QUOTA_EXCEEDED_ERR) {
55954        this.handleQuotaExceededError_({
55955          segmentInfo,
55956          type,
55957          bytes
55958        }); // A quota exceeded error should be recoverable with a future re-append, so no need
55959        // to trigger an append error.
55960
55961        return;
55962      }
55963      this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error);
55964      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.
55965      // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
55966      //
55967      // Trigger a special error so that it can be handled separately from normal,
55968      // recoverable errors.
55969
55970      this.trigger('appenderror');
55971    }
55972    appendToSourceBuffer_({
55973      segmentInfo,
55974      type,
55975      initSegment,
55976      data,
55977      bytes
55978    }) {
55979      // If this is a re-append, bytes were already created and don't need to be recreated
55980      if (!bytes) {
55981        const segments = [data];
55982        let byteLength = data.byteLength;
55983        if (initSegment) {
55984          // if the media initialization segment is changing, append it before the content
55985          // segment
55986          segments.unshift(initSegment);
55987          byteLength += initSegment.byteLength;
55988        } // Technically we should be OK appending the init segment separately, however, we
55989        // haven't yet tested that, and prepending is how we have always done things.
55990
55991        bytes = concatSegments({
55992          bytes: byteLength,
55993          segments
55994        });
55995      }
55996      this.sourceUpdater_.appendBuffer({
55997        segmentInfo,
55998        type,
55999        bytes
56000      }, this.handleAppendError_.bind(this, {
56001        segmentInfo,
56002        type,
56003        bytes
56004      }));
56005    }
56006    handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
56007      if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
56008        return;
56009      }
56010      const segment = this.pendingSegment_.segment;
56011      const timingInfoProperty = `${type}TimingInfo`;
56012      if (!segment[timingInfoProperty]) {
56013        segment[timingInfoProperty] = {};
56014      }
56015      segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
56016      segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
56017      segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
56018      segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
56019      segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
56020
56021      segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
56022    }
56023    appendData_(segmentInfo, result) {
56024      const {
56025        type,
56026        data
56027      } = result;
56028      if (!data || !data.byteLength) {
56029        return;
56030      }
56031      if (type === 'audio' && this.audioDisabled_) {
56032        return;
56033      }
56034      const initSegment = this.getInitSegmentAndUpdateState_({
56035        type,
56036        initSegment: result.initSegment,
56037        playlist: segmentInfo.playlist,
56038        map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
56039      });
56040      this.appendToSourceBuffer_({
56041        segmentInfo,
56042        type,
56043        initSegment,
56044        data
56045      });
56046    }
56047    /**
56048     * load a specific segment from a request into the buffer
56049     *
56050     * @private
56051     */
56052
56053    loadSegment_(segmentInfo) {
56054      this.state = 'WAITING';
56055      this.pendingSegment_ = segmentInfo;
56056      this.trimBackBuffer_(segmentInfo);
56057      if (typeof segmentInfo.timestampOffset === 'number') {
56058        if (this.transmuxer_) {
56059          this.transmuxer_.postMessage({
56060            action: 'clearAllMp4Captions'
56061          });
56062        }
56063      }
56064      if (!this.hasEnoughInfoToLoad_()) {
56065        this.loadQueue_.push(() => {
56066          // regenerate the audioAppendStart, timestampOffset, etc as they
56067          // may have changed since this function was added to the queue.
56068          const options = _extends$1({}, segmentInfo, {
56069            forceTimestampOffset: true
56070          });
56071          _extends$1(segmentInfo, this.generateSegmentInfo_(options));
56072          this.isPendingTimestampOffset_ = false;
56073          this.updateTransmuxerAndRequestSegment_(segmentInfo);
56074        });
56075        return;
56076      }
56077      this.updateTransmuxerAndRequestSegment_(segmentInfo);
56078    }
56079    updateTransmuxerAndRequestSegment_(segmentInfo) {
56080      // We'll update the source buffer's timestamp offset once we have transmuxed data, but
56081      // the transmuxer still needs to be updated before then.
56082      //
56083      // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
56084      // offset must be passed to the transmuxer for stream correcting adjustments.
56085      if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
56086        this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
56087
56088        segmentInfo.gopsToAlignWith = [];
56089        this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
56090
56091        this.transmuxer_.postMessage({
56092          action: 'reset'
56093        });
56094        this.transmuxer_.postMessage({
56095          action: 'setTimestampOffset',
56096          timestampOffset: segmentInfo.timestampOffset
56097        });
56098      }
56099      const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
56100      const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
56101      const isWalkingForward = this.mediaIndex !== null;
56102      const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
56103      // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
56104      // the first timeline
56105      segmentInfo.timeline > 0;
56106      const isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
56107      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),
56108      // then this init segment has never been seen before and should be appended.
56109      //
56110      // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
56111      // both to true and leave the decision of whether to append the init segment to append time.
56112
56113      if (simpleSegment.map && !simpleSegment.map.bytes) {
56114        this.logger_('going to request init segment.');
56115        this.appendInitSegment_ = {
56116          video: true,
56117          audio: true
56118        };
56119      }
56120      segmentInfo.abortRequests = mediaSegmentRequest({
56121        xhr: this.vhs_.xhr,
56122        xhrOptions: this.xhrOptions_,
56123        decryptionWorker: this.decrypter_,
56124        segment: simpleSegment,
56125        abortFn: this.handleAbort_.bind(this, segmentInfo),
56126        progressFn: this.handleProgress_.bind(this),
56127        trackInfoFn: this.handleTrackInfo_.bind(this),
56128        timingInfoFn: this.handleTimingInfo_.bind(this),
56129        videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
56130        audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
56131        captionsFn: this.handleCaptions_.bind(this),
56132        isEndOfTimeline,
56133        endedTimelineFn: () => {
56134          this.logger_('received endedtimeline callback');
56135        },
56136        id3Fn: this.handleId3_.bind(this),
56137        dataFn: this.handleData_.bind(this),
56138        doneFn: this.segmentRequestFinished_.bind(this),
56139        onTransmuxerLog: ({
56140          message,
56141          level,
56142          stream
56143        }) => {
56144          this.logger_(`${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`);
56145        }
56146      });
56147    }
56148    /**
56149     * trim the back buffer so that we don't have too much data
56150     * in the source buffer
56151     *
56152     * @private
56153     *
56154     * @param {Object} segmentInfo - the current segment
56155     */
56156
56157    trimBackBuffer_(segmentInfo) {
56158      const removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
56159      // buffer and a very conservative "garbage collector"
56160      // We manually clear out the old buffer to ensure
56161      // we don't trigger the QuotaExceeded error
56162      // on the source buffer during subsequent appends
56163
56164      if (removeToTime > 0) {
56165        this.remove(0, removeToTime);
56166      }
56167    }
56168    /**
56169     * created a simplified copy of the segment object with just the
56170     * information necessary to perform the XHR and decryption
56171     *
56172     * @private
56173     *
56174     * @param {Object} segmentInfo - the current segment
56175     * @return {Object} a simplified segment object copy
56176     */
56177
56178    createSimplifiedSegmentObj_(segmentInfo) {
56179      const segment = segmentInfo.segment;
56180      const part = segmentInfo.part;
56181      const simpleSegment = {
56182        resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
56183        byterange: part ? part.byterange : segment.byterange,
56184        requestId: segmentInfo.requestId,
56185        transmuxer: segmentInfo.transmuxer,
56186        audioAppendStart: segmentInfo.audioAppendStart,
56187        gopsToAlignWith: segmentInfo.gopsToAlignWith,
56188        part: segmentInfo.part
56189      };
56190      const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
56191      if (previousSegment && previousSegment.timeline === segment.timeline) {
56192        // The baseStartTime of a segment is used to handle rollover when probing the TS
56193        // segment to retrieve timing information. Since the probe only looks at the media's
56194        // times (e.g., PTS and DTS values of the segment), and doesn't consider the
56195        // player's time (e.g., player.currentTime()), baseStartTime should reflect the
56196        // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
56197        // seconds of media time, so should be used here. The previous segment is used since
56198        // the end of the previous segment should represent the beginning of the current
56199        // segment, so long as they are on the same timeline.
56200        if (previousSegment.videoTimingInfo) {
56201          simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
56202        } else if (previousSegment.audioTimingInfo) {
56203          simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
56204        }
56205      }
56206      if (segment.key) {
56207        // if the media sequence is greater than 2^32, the IV will be incorrect
56208        // assuming 10s segments, that would be about 1300 years
56209        const iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
56210        simpleSegment.key = this.segmentKey(segment.key);
56211        simpleSegment.key.iv = iv;
56212      }
56213      if (segment.map) {
56214        simpleSegment.map = this.initSegmentForMap(segment.map);
56215      }
56216      return simpleSegment;
56217    }
56218    saveTransferStats_(stats) {
56219      // every request counts as a media request even if it has been aborted
56220      // or canceled due to a timeout
56221      this.mediaRequests += 1;
56222      if (stats) {
56223        this.mediaBytesTransferred += stats.bytesReceived;
56224        this.mediaTransferDuration += stats.roundTripTime;
56225      }
56226    }
56227    saveBandwidthRelatedStats_(duration, stats) {
56228      // byteLength will be used for throughput, and should be based on bytes receieved,
56229      // which we only know at the end of the request and should reflect total bytes
56230      // downloaded rather than just bytes processed from components of the segment
56231      this.pendingSegment_.byteLength = stats.bytesReceived;
56232      if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
56233        this.logger_(`Ignoring segment's bandwidth because its duration of ${duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
56234        return;
56235      }
vendor: 12,384 bytes, lines 56235-56534
56235
56236      this.bandwidth = stats.bandwidth;
56237      this.roundTrip = stats.roundTripTime;
56238    }
56239    handleTimeout_() {
56240      // although the VTT segment loader bandwidth isn't really used, it's good to
56241      // maintain functinality between segment loaders
56242      this.mediaRequestsTimedout += 1;
56243      this.bandwidth = 1;
56244      this.roundTrip = NaN;
56245      this.trigger('bandwidthupdate');
56246      this.trigger('timeout');
56247    }
56248    /**
56249     * Handle the callback from the segmentRequest function and set the
56250     * associated SegmentLoader state and errors if necessary
56251     *
56252     * @private
56253     */
56254
56255    segmentRequestFinished_(error, simpleSegment, result) {
56256      // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
56257      // check the call queue directly since this function doesn't need to deal with any
56258      // data, and can continue even if the source buffers are not set up and we didn't get
56259      // any data from the segment
56260      if (this.callQueue_.length) {
56261        this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
56262        return;
56263      }
56264      this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
56265
56266      if (!this.pendingSegment_) {
56267        return;
56268      } // the request was aborted and the SegmentLoader has already started
56269      // another request. this can happen when the timeout for an aborted
56270      // request triggers due to a limitation in the XHR library
56271      // do not count this as any sort of request or we risk double-counting
56272
56273      if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
56274        return;
56275      } // an error occurred from the active pendingSegment_ so reset everything
56276
56277      if (error) {
56278        this.pendingSegment_ = null;
56279        this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
56280
56281        if (error.code === REQUEST_ERRORS.ABORTED) {
56282          return;
56283        }
56284        this.pause(); // the error is really just that at least one of the requests timed-out
56285        // set the bandwidth to a very low value and trigger an ABR switch to
56286        // take emergency action
56287
56288        if (error.code === REQUEST_ERRORS.TIMEOUT) {
56289          this.handleTimeout_();
56290          return;
56291        } // if control-flow has arrived here, then the error is real
56292        // emit an error event to exclude the current playlist
56293
56294        this.mediaRequestsErrored += 1;
56295        this.error(error);
56296        this.trigger('error');
56297        return;
56298      }
56299      const segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
56300      // generated for ABR purposes
56301
56302      this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
56303      segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
56304      if (result.gopInfo) {
56305        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
56306      } // Although we may have already started appending on progress, we shouldn't switch the
56307      // state away from loading until we are officially done loading the segment data.
56308
56309      this.state = 'APPENDING'; // used for testing
56310
56311      this.trigger('appending');
56312      this.waitForAppendsToComplete_(segmentInfo);
56313    }
56314    setTimeMapping_(timeline) {
56315      const timelineMapping = this.syncController_.mappingForTimeline(timeline);
56316      if (timelineMapping !== null) {
56317        this.timeMapping_ = timelineMapping;
56318      }
56319    }
56320    updateMediaSecondsLoaded_(segment) {
56321      if (typeof segment.start === 'number' && typeof segment.end === 'number') {
56322        this.mediaSecondsLoaded += segment.end - segment.start;
56323      } else {
56324        this.mediaSecondsLoaded += segment.duration;
56325      }
56326    }
56327    shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
56328      if (timestampOffset === null) {
56329        return false;
56330      } // note that we're potentially using the same timestamp offset for both video and
56331      // audio
56332
56333      if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
56334        return true;
56335      }
56336      if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
56337        return true;
56338      }
56339      return false;
56340    }
56341    trueSegmentStart_({
56342      currentStart,
56343      playlist,
56344      mediaIndex,
56345      firstVideoFrameTimeForData,
56346      currentVideoTimestampOffset,
56347      useVideoTimingInfo,
56348      videoTimingInfo,
56349      audioTimingInfo
56350    }) {
56351      if (typeof currentStart !== 'undefined') {
56352        // if start was set once, keep using it
56353        return currentStart;
56354      }
56355      if (!useVideoTimingInfo) {
56356        return audioTimingInfo.start;
56357      }
56358      const previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
56359      // within that segment. Since the transmuxer maintains a cache of incomplete data
56360      // from and/or the last frame seen, the start time may reflect a frame that starts
56361      // in the previous segment. Check for that case and ensure the start time is
56362      // accurate for the segment.
56363
56364      if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
56365        return firstVideoFrameTimeForData;
56366      }
56367      return videoTimingInfo.start;
56368    }
56369    waitForAppendsToComplete_(segmentInfo) {
56370      const trackInfo = this.getCurrentMediaInfo_(segmentInfo);
56371      if (!trackInfo) {
56372        this.error({
56373          message: 'No starting media returned, likely due to an unsupported media format.',
56374          playlistExclusionDuration: Infinity
56375        });
56376        this.trigger('error');
56377        return;
56378      } // Although transmuxing is done, appends may not yet be finished. Throw a marker
56379      // on each queue this loader is responsible for to ensure that the appends are
56380      // complete.
56381
56382      const {
56383        hasAudio,
56384        hasVideo,
56385        isMuxed
56386      } = trackInfo;
56387      const waitForVideo = this.loaderType_ === 'main' && hasVideo;
56388      const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
56389      segmentInfo.waitingOnAppends = 0; // segments with no data
56390
56391      if (!segmentInfo.hasAppendedData_) {
56392        if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
56393          // When there's no audio or video data in the segment, there's no audio or video
56394          // timing information.
56395          //
56396          // If there's no audio or video timing information, then the timestamp offset
56397          // can't be adjusted to the appropriate value for the transmuxer and source
56398          // buffers.
56399          //
56400          // Therefore, the next segment should be used to set the timestamp offset.
56401          this.isPendingTimestampOffset_ = true;
56402        } // override settings for metadata only segments
56403
56404        segmentInfo.timingInfo = {
56405          start: 0
56406        };
56407        segmentInfo.waitingOnAppends++;
56408        if (!this.isPendingTimestampOffset_) {
56409          // update the timestampoffset
56410          this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
56411          // no video/audio data.
56412
56413          this.processMetadataQueue_();
56414        } // append is "done" instantly with no data.
56415
56416        this.checkAppendsDone_(segmentInfo);
56417        return;
56418      } // Since source updater could call back synchronously, do the increments first.
56419
56420      if (waitForVideo) {
56421        segmentInfo.waitingOnAppends++;
56422      }
56423      if (waitForAudio) {
56424        segmentInfo.waitingOnAppends++;
56425      }
56426      if (waitForVideo) {
56427        this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
56428      }
56429      if (waitForAudio) {
56430        this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
56431      }
56432    }
56433    checkAppendsDone_(segmentInfo) {
56434      if (this.checkForAbort_(segmentInfo.requestId)) {
56435        return;
56436      }
56437      segmentInfo.waitingOnAppends--;
56438      if (segmentInfo.waitingOnAppends === 0) {
56439        this.handleAppendsDone_();
56440      }
56441    }
56442    checkForIllegalMediaSwitch(trackInfo) {
56443      const illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
56444      if (illegalMediaSwitchError) {
56445        this.error({
56446          message: illegalMediaSwitchError,
56447          playlistExclusionDuration: Infinity
56448        });
56449        this.trigger('error');
56450        return true;
56451      }
56452      return false;
56453    }
56454    updateSourceBufferTimestampOffset_(segmentInfo) {
56455      if (segmentInfo.timestampOffset === null ||
56456      // we don't yet have the start for whatever media type (video or audio) has
56457      // priority, timing-wise, so we must wait
56458      typeof segmentInfo.timingInfo.start !== 'number' ||
56459      // already updated the timestamp offset for this segment
56460      segmentInfo.changedTimestampOffset ||
56461      // the alt audio loader should not be responsible for setting the timestamp offset
56462      this.loaderType_ !== 'main') {
56463        return;
56464      }
56465      let didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
56466      // the timing info here comes from video. In the event that the audio is longer than
56467      // the video, this will trim the start of the audio.
56468      // This also trims any offset from 0 at the beginning of the media
56469
56470      segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
56471        videoTimingInfo: segmentInfo.segment.videoTimingInfo,
56472        audioTimingInfo: segmentInfo.segment.audioTimingInfo,
56473        timingInfo: segmentInfo.timingInfo
56474      }); // In the event that there are part segment downloads, each will try to update the
56475      // timestamp offset. Retaining this bit of state prevents us from updating in the
56476      // future (within the same segment), however, there may be a better way to handle it.
56477
56478      segmentInfo.changedTimestampOffset = true;
56479      if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
56480        this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
56481        didChange = true;
56482      }
56483      if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
56484        this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
56485        didChange = true;
56486      }
56487      if (didChange) {
56488        this.trigger('timestampoffset');
56489      }
56490    }
56491    getSegmentStartTimeForTimestampOffsetCalculation_({
56492      videoTimingInfo,
56493      audioTimingInfo,
56494      timingInfo
56495    }) {
56496      if (!this.useDtsForTimestampOffset_) {
56497        return timingInfo.start;
56498      }
56499      if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
56500        return videoTimingInfo.transmuxedDecodeStart;
56501      } // handle audio only
56502
56503      if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
56504        return audioTimingInfo.transmuxedDecodeStart;
56505      } // handle content not transmuxed (e.g., MP4)
56506
56507      return timingInfo.start;
56508    }
56509    updateTimingInfoEnd_(segmentInfo) {
56510      segmentInfo.timingInfo = segmentInfo.timingInfo || {};
56511      const trackInfo = this.getMediaInfo_();
56512      const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
56513      const prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
56514      if (!prioritizedTimingInfo) {
56515        return;
56516      }
56517      segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ?
56518      // End time may not exist in a case where we aren't parsing the full segment (one
56519      // current example is the case of fmp4), so use the rough duration to calculate an
56520      // end time.
56521      prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
56522    }
56523    /**
56524     * callback to run when appendBuffer is finished. detects if we are
56525     * in a good state to do things with the data we got, or if we need
56526     * to wait for more
56527     *
56528     * @private
56529     */
56530
56531    handleAppendsDone_() {
56532      // appendsdone can cause an abort
56533      if (this.pendingSegment_) {
56534        
56534this.trigger('appendsdone');
56535      }
56536      if (!this.pendingSegment_) {
56537        this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
56538        // all appending cases?
56539
56540        if (!this.paused()) {
56541          this.monitorBuffer_();
56542        }
56543        return;
56544      }
56545      const segmentInfo = this.pendingSegment_;
vendor: 10,130 bytes, lines 56545-56772
56545 // Now that the end of the segment has been reached, we can set the end time. It's
56546      // best to wait until all appends are done so we're sure that the primary media is
56547      // finished (and we have its end time).
56548
56549      this.updateTimingInfoEnd_(segmentInfo);
56550      if (this.shouldSaveSegmentTimingInfo_) {
56551        // Timeline mappings should only be saved for the main loader. This is for multiple
56552        // reasons:
56553        //
56554        // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
56555        //    and the main loader try to save the timeline mapping, whichever comes later
56556        //    will overwrite the first. In theory this is OK, as the mappings should be the
56557        //    same, however, it breaks for (2)
56558        // 2) In the event of a live stream, the initial live point will make for a somewhat
56559        //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
56560        //    the mapping will be off for one of the streams, dependent on which one was
56561        //    first saved (see (1)).
56562        // 3) Primary timing goes by video in VHS, so the mapping should be video.
56563        //
56564        // Since the audio loader will wait for the main loader to load the first segment,
56565        // the main loader will save the first timeline mapping, and ensure that there won't
56566        // be a case where audio loads two segments without saving a mapping (thus leading
56567        // to missing segment timing info).
56568        this.syncController_.saveSegmentTimingInfo({
56569          segmentInfo,
56570          shouldSaveTimelineMapping: this.loaderType_ === 'main'
56571        });
56572      }
56573      const segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
56574      if (segmentDurationMessage) {
56575        if (segmentDurationMessage.severity === 'warn') {
56576          videojs.log.warn(segmentDurationMessage.message);
56577        } else {
56578          this.logger_(segmentDurationMessage.message);
56579        }
56580      }
56581      this.recordThroughput_(segmentInfo);
56582      this.pendingSegment_ = null;
56583      this.state = 'READY';
56584      if (segmentInfo.isSyncRequest) {
56585        this.trigger('syncinfoupdate'); // if the sync request was not appended
56586        // then it was not the correct segment.
56587        // throw it away and use the data it gave us
56588        // to get the correct one.
56589
56590        if (!segmentInfo.hasAppendedData_) {
56591          this.logger_(`Throwing away un-appended sync request ${segmentInfoString(segmentInfo)}`);
56592          return;
56593        }
56594      }
56595      this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
56596      this.addSegmentMetadataCue_(segmentInfo);
56597      this.fetchAtBuffer_ = true;
56598      if (this.currentTimeline_ !== segmentInfo.timeline) {
56599        this.timelineChangeController_.lastTimelineChange({
56600          type: this.loaderType_,
56601          from: this.currentTimeline_,
56602          to: segmentInfo.timeline
56603        }); // If audio is not disabled, the main segment loader is responsible for updating
56604        // the audio timeline as well. If the content is video only, this won't have any
56605        // impact.
56606
56607        if (this.loaderType_ === 'main' && !this.audioDisabled_) {
56608          this.timelineChangeController_.lastTimelineChange({
56609            type: 'audio',
56610            from: this.currentTimeline_,
56611            to: segmentInfo.timeline
56612          });
56613        }
56614      }
56615      this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
56616      // the following conditional otherwise it may consider this a bad "guess"
56617      // and attempt to resync when the post-update seekable window and live
56618      // point would mean that this was the perfect segment to fetch
56619
56620      this.trigger('syncinfoupdate');
56621      const segment = segmentInfo.segment;
56622      const part = segmentInfo.part;
56623      const badSegmentGuess = segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
56624      const badPartGuess = part && part.end && this.currentTime_() - part.end > segmentInfo.playlist.partTargetDuration * 3; // If we previously appended a segment/part that ends more than 3 part/targetDurations before
56625      // the currentTime_ that means that our conservative guess was too conservative.
56626      // In that case, reset the loader state so that we try to use any information gained
56627      // from the previous request to create a new, more accurate, sync-point.
56628
56629      if (badSegmentGuess || badPartGuess) {
56630        this.logger_(`bad ${badSegmentGuess ? 'segment' : 'part'} ${segmentInfoString(segmentInfo)}`);
56631        this.resetEverything();
56632        return;
56633      }
56634      const isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
56635      // and conservatively guess
56636
56637      if (isWalkingForward) {
56638        this.trigger('bandwidthupdate');
56639      }
56640      this.trigger('progress');
56641      this.mediaIndex = segmentInfo.mediaIndex;
56642      this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
56643      // buffer, end the stream. this ensures the "ended" event will
56644      // fire if playback reaches that point.
56645
56646      if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
56647        this.endOfStream();
56648      } // used for testing
56649
56650      this.trigger('appended');
56651      if (segmentInfo.hasAppendedData_) {
56652        this.mediaAppends++;
56653      }
56654      if (!this.paused()) {
56655        this.monitorBuffer_();
56656      }
56657    }
56658    /**
56659     * Records the current throughput of the decrypt, transmux, and append
56660     * portion of the semgment pipeline. `throughput.rate` is a the cumulative
56661     * moving average of the throughput. `throughput.count` is the number of
56662     * data points in the average.
56663     *
56664     * @private
56665     * @param {Object} segmentInfo the object returned by loadSegment
56666     */
56667
56668    recordThroughput_(segmentInfo) {
56669      if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
56670        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}`);
56671        return;
56672      }
56673      const rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
56674      // by zero in the case where the throughput is ridiculously high
56675
56676      const segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
56677
56678      const segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
56679      //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
56680
56681      this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
56682    }
56683    /**
56684     * Adds a cue to the segment-metadata track with some metadata information about the
56685     * segment
56686     *
56687     * @private
56688     * @param {Object} segmentInfo
56689     *        the object returned by loadSegment
56690     * @method addSegmentMetadataCue_
56691     */
56692
56693    addSegmentMetadataCue_(segmentInfo) {
56694      if (!this.segmentMetadataTrack_) {
56695        return;
56696      }
56697      const segment = segmentInfo.segment;
56698      const start = segment.start;
56699      const end = segment.end; // Do not try adding the cue if the start and end times are invalid.
56700
56701      if (!finite(start) || !finite(end)) {
56702        return;
56703      }
56704      removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
56705      const Cue = window.WebKitDataCue || window.VTTCue;
56706      const value = {
56707        custom: segment.custom,
56708        dateTimeObject: segment.dateTimeObject,
56709        dateTimeString: segment.dateTimeString,
56710        programDateTime: segment.programDateTime,
56711        bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
56712        resolution: segmentInfo.playlist.attributes.RESOLUTION,
56713        codecs: segmentInfo.playlist.attributes.CODECS,
56714        byteLength: segmentInfo.byteLength,
56715        uri: segmentInfo.uri,
56716        timeline: segmentInfo.timeline,
56717        playlist: segmentInfo.playlist.id,
56718        start,
56719        end
56720      };
56721      const data = JSON.stringify(value);
56722      const cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
56723      // the differences of WebKitDataCue in safari and VTTCue in other browsers
56724
56725      cue.value = value;
56726      this.segmentMetadataTrack_.addCue(cue);
56727    }
56728  }
56729  function noop() {}
56730  const toTitleCase = function (string) {
56731    if (typeof string !== 'string') {
56732      return string;
56733    }
56734    return string.replace(/./, w => w.toUpperCase());
56735  };
56736
56737  /**
56738   * @file source-updater.js
56739   */
56740  const bufferTypes = ['video', 'audio'];
56741  const updating = (type, sourceUpdater) => {
56742    const sourceBuffer = sourceUpdater[`${type}Buffer`];
56743    return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
56744  };
56745  const nextQueueIndexOfType = (type, queue) => {
56746    for (let i = 0; i < queue.length; i++) {
56747      const queueEntry = queue[i];
56748      if (queueEntry.type === 'mediaSource') {
56749        // If the next entry is a media source entry (uses multiple source buffers), block
56750        // processing to allow it to go through first.
56751        return null;
56752      }
56753      if (queueEntry.type === type) {
56754        return i;
56755      }
56756    }
56757    return null;
56758  };
56759  const shiftQueue = (type, sourceUpdater) => {
56760    if (sourceUpdater.queue.length === 0) {
56761      return;
56762    }
56763    let queueIndex = 0;
56764    let queueEntry = sourceUpdater.queue[queueIndex];
56765    if (queueEntry.type === 'mediaSource') {
56766      if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
56767        sourceUpdater.queue.shift();
56768        queueEntry.action(sourceUpdater);
56769        if (queueEntry.doneFn) {
56770          queueEntry.doneFn();
56771        } // Only specific source buffer actions must wait for async updateend events. Media
56772        // Source actions process synchronously. Therefore, both audio and video source
vendor: 8,055 bytes, lines 56773-56947
56773        // buffers are now clear to process the next queue entries.
56774
56775        shiftQueue('audio', sourceUpdater);
56776        shiftQueue('video', sourceUpdater);
56777      } // Media Source actions require both source buffers, so if the media source action
56778      // couldn't process yet (because one or both source buffers are busy), block other
56779      // queue actions until both are available and the media source action can process.
56780
56781      return;
56782    }
56783    if (type === 'mediaSource') {
56784      // If the queue was shifted by a media source action (this happens when pushing a
56785      // media source action onto the queue), then it wasn't from an updateend event from an
56786      // audio or video source buffer, so there's no change from previous state, and no
56787      // processing should be done.
56788      return;
56789    } // Media source queue entries don't need to consider whether the source updater is
56790    // started (i.e., source buffers are created) as they don't need the source buffers, but
56791    // source buffer queue entries do.
56792
56793    if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || updating(type, sourceUpdater)) {
56794      return;
56795    }
56796    if (queueEntry.type !== type) {
56797      queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
56798      if (queueIndex === null) {
56799        // Either there's no queue entry that uses this source buffer type in the queue, or
56800        // there's a media source queue entry before the next entry of this type, in which
56801        // case wait for that action to process first.
56802        return;
56803      }
56804      queueEntry = sourceUpdater.queue[queueIndex];
56805    }
56806    sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
56807    //
56808    // The queue pending operation must be set before the action is performed in the event
56809    // that the action results in a synchronous event that is acted upon. For instance, if
56810    // an exception is thrown that can be handled, it's possible that new actions will be
56811    // appended to an empty queue and immediately executed, but would not have the correct
56812    // pending information if this property was set after the action was performed.
56813
56814    sourceUpdater.queuePending[type] = queueEntry;
56815    queueEntry.action(type, sourceUpdater);
56816    if (!queueEntry.doneFn) {
56817      // synchronous operation, process next entry
56818      sourceUpdater.queuePending[type] = null;
56819      shiftQueue(type, sourceUpdater);
56820      return;
56821    }
56822  };
56823  const cleanupBuffer = (type, sourceUpdater) => {
56824    const buffer = sourceUpdater[`${type}Buffer`];
56825    const titleType = toTitleCase(type);
56826    if (!buffer) {
56827      return;
56828    }
56829    buffer.removeEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
56830    buffer.removeEventListener('error', sourceUpdater[`on${titleType}Error_`]);
56831    sourceUpdater.codecs[type] = null;
56832    sourceUpdater[`${type}Buffer`] = null;
56833  };
56834  const inSourceBuffers = (mediaSource, sourceBuffer) => mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
56835  const actions = {
56836    appendBuffer: (bytes, segmentInfo, onError) => (type, sourceUpdater) => {
56837      const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56838      // or the media source does not contain this source buffer.
56839
56840      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56841        return;
56842      }
56843      sourceUpdater.logger_(`Appending segment ${segmentInfo.mediaIndex}'s ${bytes.length} bytes to ${type}Buffer`);
56844      try {
56845        sourceBuffer.appendBuffer(bytes);
56846      } catch (e) {
56847        sourceUpdater.logger_(`Error with code ${e.code} ` + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + `when appending segment ${segmentInfo.mediaIndex} to ${type}Buffer`);
56848        sourceUpdater.queuePending[type] = null;
56849        onError(e);
56850      }
56851    },
56852    remove: (start, end) => (type, sourceUpdater) => {
56853      const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56854      // or the media source does not contain this source buffer.
56855
56856      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56857        return;
56858      }
56859      sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`);
56860      try {
56861        sourceBuffer.remove(start, end);
56862      } catch (e) {
56863        sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`);
56864      }
56865    },
56866    timestampOffset: offset => (type, sourceUpdater) => {
56867      const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56868      // or the media source does not contain this source buffer.
56869
56870      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56871        return;
56872      }
56873      sourceUpdater.logger_(`Setting ${type}timestampOffset to ${offset}`);
56874      sourceBuffer.timestampOffset = offset;
56875    },
56876    callback: callback => (type, sourceUpdater) => {
56877      callback();
56878    },
56879    endOfStream: error => sourceUpdater => {
56880      if (sourceUpdater.mediaSource.readyState !== 'open') {
56881        return;
56882      }
56883      sourceUpdater.logger_(`Calling mediaSource endOfStream(${error || ''})`);
56884      try {
56885        sourceUpdater.mediaSource.endOfStream(error);
56886      } catch (e) {
56887        videojs.log.warn('Failed to call media source endOfStream', e);
56888      }
56889    },
56890    duration: duration => sourceUpdater => {
56891      sourceUpdater.logger_(`Setting mediaSource duration to ${duration}`);
56892      try {
56893        sourceUpdater.mediaSource.duration = duration;
56894      } catch (e) {
56895        videojs.log.warn('Failed to set media source duration', e);
56896      }
56897    },
56898    abort: () => (type, sourceUpdater) => {
56899      if (sourceUpdater.mediaSource.readyState !== 'open') {
56900        return;
56901      }
56902      const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56903      // or the media source does not contain this source buffer.
56904
56905      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56906        return;
56907      }
56908      sourceUpdater.logger_(`calling abort on ${type}Buffer`);
56909      try {
56910        sourceBuffer.abort();
56911      } catch (e) {
56912        videojs.log.warn(`Failed to abort on ${type}Buffer`, e);
56913      }
56914    },
56915    addSourceBuffer: (type, codec) => sourceUpdater => {
56916      const titleType = toTitleCase(type);
56917      const mime = getMimeForCodec(codec);
56918      sourceUpdater.logger_(`Adding ${type}Buffer with codec ${codec} to mediaSource`);
56919      const sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
56920      sourceBuffer.addEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
56921      sourceBuffer.addEventListener('error', sourceUpdater[`on${titleType}Error_`]);
56922      sourceUpdater.codecs[type] = codec;
56923      sourceUpdater[`${type}Buffer`] = sourceBuffer;
56924    },
56925    removeSourceBuffer: type => sourceUpdater => {
56926      const sourceBuffer = sourceUpdater[`${type}Buffer`];
56927      cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
56928      // or the media source does not contain this source buffer.
56929
56930      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56931        return;
56932      }
56933      sourceUpdater.logger_(`Removing ${type}Buffer with codec ${sourceUpdater.codecs[type]} from mediaSource`);
56934      try {
56935        sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
56936      } catch (e) {
56937        videojs.log.warn(`Failed to removeSourceBuffer ${type}Buffer`, e);
56938      }
56939    },
56940    changeType: codec => (type, sourceUpdater) => {
56941      const sourceBuffer = sourceUpdater[`${type}Buffer`];
56942      const mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
56943      // or the media source does not contain this source buffer.
56944
56945      if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56946        return;
56947      } // do not update codec if we don't need to.
vendor: 6,410 bytes, lines 56948-57146
56948
56949      if (sourceUpdater.codecs[type] === codec) {
56950        return;
56951      }
56952      sourceUpdater.logger_(`changing ${type}Buffer codec from ${sourceUpdater.codecs[type]} to ${codec}`);
56953      sourceBuffer.changeType(mime);
56954      sourceUpdater.codecs[type] = codec;
56955    }
56956  };
56957  const pushQueue = ({
56958    type,
56959    sourceUpdater,
56960    action,
56961    doneFn,
56962    name
56963  }) => {
56964    sourceUpdater.queue.push({
56965      type,
56966      action,
56967      doneFn,
56968      name
56969    });
56970    shiftQueue(type, sourceUpdater);
56971  };
56972  const onUpdateend = (type, sourceUpdater) => e => {
56973    // Although there should, in theory, be a pending action for any updateend receieved,
56974    // there are some actions that may trigger updateend events without set definitions in
56975    // the w3c spec. For instance, setting the duration on the media source may trigger
56976    // updateend events on source buffers. This does not appear to be in the spec. As such,
56977    // if we encounter an updateend without a corresponding pending action from our queue
56978    // for that source buffer type, process the next action.
56979    if (sourceUpdater.queuePending[type]) {
56980      const doneFn = sourceUpdater.queuePending[type].doneFn;
56981      sourceUpdater.queuePending[type] = null;
56982      if (doneFn) {
56983        // if there's an error, report it
56984        doneFn(sourceUpdater[`${type}Error_`]);
56985      }
56986    }
56987    shiftQueue(type, sourceUpdater);
56988  };
56989  /**
56990   * A queue of callbacks to be serialized and applied when a
56991   * MediaSource and its associated SourceBuffers are not in the
56992   * updating state. It is used by the segment loader to update the
56993   * underlying SourceBuffers when new data is loaded, for instance.
56994   *
56995   * @class SourceUpdater
56996   * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
56997   * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
56998   */
56999
57000  class SourceUpdater extends videojs.EventTarget {
57001    constructor(mediaSource) {
57002      super();
57003      this.mediaSource = mediaSource;
57004      this.sourceopenListener_ = () => shiftQueue('mediaSource', this);
57005      this.mediaSource.addEventListener('sourceopen', this.sourceopenListener_);
57006      this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
57007
57008      this.audioTimestampOffset_ = 0;
57009      this.videoTimestampOffset_ = 0;
57010      this.queue = [];
57011      this.queuePending = {
57012        audio: null,
57013        video: null
57014      };
57015      this.delayedAudioAppendQueue_ = [];
57016      this.videoAppendQueued_ = false;
57017      this.codecs = {};
57018      this.onVideoUpdateEnd_ = onUpdateend('video', this);
57019      this.onAudioUpdateEnd_ = onUpdateend('audio', this);
57020      this.onVideoError_ = e => {
57021        // used for debugging
57022        this.videoError_ = e;
57023      };
57024      this.onAudioError_ = e => {
57025        // used for debugging
57026        this.audioError_ = e;
57027      };
57028      this.createdSourceBuffers_ = false;
57029      this.initializedEme_ = false;
57030      this.triggeredReady_ = false;
57031    }
57032    initializedEme() {
57033      this.initializedEme_ = true;
57034      this.triggerReady();
57035    }
57036    hasCreatedSourceBuffers() {
57037      // if false, likely waiting on one of the segment loaders to get enough data to create
57038      // source buffers
57039      return this.createdSourceBuffers_;
57040    }
57041    hasInitializedAnyEme() {
57042      return this.initializedEme_;
57043    }
57044    ready() {
57045      return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
57046    }
57047    createSourceBuffers(codecs) {
57048      if (this.hasCreatedSourceBuffers()) {
57049        // already created them before
57050        return;
57051      } // the intial addOrChangeSourceBuffers will always be
57052      // two add buffers.
57053
57054      this.addOrChangeSourceBuffers(codecs);
57055      this.createdSourceBuffers_ = true;
57056      this.trigger('createdsourcebuffers');
57057      this.triggerReady();
57058    }
57059    triggerReady() {
57060      // only allow ready to be triggered once, this prevents the case
57061      // where:
57062      // 1. we trigger createdsourcebuffers
57063      // 2. ie 11 synchronously initializates eme
57064      // 3. the synchronous initialization causes us to trigger ready
57065      // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
57066      if (this.ready() && !this.triggeredReady_) {
57067        this.triggeredReady_ = true;
57068        this.trigger('ready');
57069      }
57070    }
57071    /**
57072     * Add a type of source buffer to the media source.
57073     *
57074     * @param {string} type
57075     *        The type of source buffer to add.
57076     *
57077     * @param {string} codec
57078     *        The codec to add the source buffer with.
57079     */
57080
57081    addSourceBuffer(type, codec) {
57082      pushQueue({
57083        type: 'mediaSource',
57084        sourceUpdater: this,
57085        action: actions.addSourceBuffer(type, codec),
57086        name: 'addSourceBuffer'
57087      });
57088    }
57089    /**
57090     * call abort on a source buffer.
57091     *
57092     * @param {string} type
57093     *        The type of source buffer to call abort on.
57094     */
57095
57096    abort(type) {
57097      pushQueue({
57098        type,
57099        sourceUpdater: this,
57100        action: actions.abort(type),
57101        name: 'abort'
57102      });
57103    }
57104    /**
57105     * Call removeSourceBuffer and remove a specific type
57106     * of source buffer on the mediaSource.
57107     *
57108     * @param {string} type
57109     *        The type of source buffer to remove.
57110     */
57111
57112    removeSourceBuffer(type) {
57113      if (!this.canRemoveSourceBuffer()) {
57114        videojs.log.error('removeSourceBuffer is not supported!');
57115        return;
57116      }
57117      pushQueue({
57118        type: 'mediaSource',
57119        sourceUpdater: this,
57120        action: actions.removeSourceBuffer(type),
57121        name: 'removeSourceBuffer'
57122      });
57123    }
57124    /**
57125     * Whether or not the removeSourceBuffer function is supported
57126     * on the mediaSource.
57127     *
57128     * @return {boolean}
57129     *          if removeSourceBuffer can be called.
57130     */
57131
57132    canRemoveSourceBuffer() {
57133      // As of Firefox 83 removeSourceBuffer
57134      // throws errors, so we report that it does not support this.
57135      return !videojs.browser.IS_FIREFOX && window.MediaSource && window.MediaSource.prototype && typeof window.MediaSource.prototype.removeSourceBuffer === 'function';
57136    }
57137    /**
57138     * Whether or not the changeType function is supported
57139     * on our SourceBuffers.
57140     *
57141     * @return {boolean}
57142     *         if changeType can be called.
57143     */
57144
57145    static canChangeType() {
57146      return window.SourceBuffer && window.SourceBuffer.prototype && typeof window
vendor: 9,491 bytes, lines 57146-57436
57146.SourceBuffer.prototype.changeType === 'function';
57147    }
57148    /**
57149     * Whether or not the changeType function is supported
57150     * on our SourceBuffers.
57151     *
57152     * @return {boolean}
57153     *         if changeType can be called.
57154     */
57155
57156    canChangeType() {
57157      return this.constructor.canChangeType();
57158    }
57159    /**
57160     * Call the changeType function on a source buffer, given the code and type.
57161     *
57162     * @param {string} type
57163     *        The type of source buffer to call changeType on.
57164     *
57165     * @param {string} codec
57166     *        The codec string to change type with on the source buffer.
57167     */
57168
57169    changeType(type, codec) {
57170      if (!this.canChangeType()) {
57171        videojs.log.error('changeType is not supported!');
57172        return;
57173      }
57174      pushQueue({
57175        type,
57176        sourceUpdater: this,
57177        action: actions.changeType(codec),
57178        name: 'changeType'
57179      });
57180    }
57181    /**
57182     * Add source buffers with a codec or, if they are already created,
57183     * call changeType on source buffers using changeType.
57184     *
57185     * @param {Object} codecs
57186     *        Codecs to switch to
57187     */
57188
57189    addOrChangeSourceBuffers(codecs) {
57190      if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
57191        throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
57192      }
57193      Object.keys(codecs).forEach(type => {
57194        const codec = codecs[type];
57195        if (!this.hasCreatedSourceBuffers()) {
57196          return this.addSourceBuffer(type, codec);
57197        }
57198        if (this.canChangeType()) {
57199          this.changeType(type, codec);
57200        }
57201      });
57202    }
57203    /**
57204     * Queue an update to append an ArrayBuffer.
57205     *
57206     * @param {MediaObject} object containing audioBytes and/or videoBytes
57207     * @param {Function} done the function to call when done
57208     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
57209     */
57210
57211    appendBuffer(options, doneFn) {
57212      const {
57213        segmentInfo,
57214        type,
57215        bytes
57216      } = options;
57217      this.processedAppend_ = true;
57218      if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
57219        this.delayedAudioAppendQueue_.push([options, doneFn]);
57220        this.logger_(`delayed audio append of ${bytes.length} until video append`);
57221        return;
57222      } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
57223      // not be fired. This means that the queue will be blocked until the next action
57224      // taken by the segment-loader. Provide a mechanism for segment-loader to handle
57225      // these errors by calling the doneFn with the specific error.
57226
57227      const onError = doneFn;
57228      pushQueue({
57229        type,
57230        sourceUpdater: this,
57231        action: actions.appendBuffer(bytes, segmentInfo || {
57232          mediaIndex: -1
57233        }, onError),
57234        doneFn,
57235        name: 'appendBuffer'
57236      });
57237      if (type === 'video') {
57238        this.videoAppendQueued_ = true;
57239        if (!this.delayedAudioAppendQueue_.length) {
57240          return;
57241        }
57242        const queue = this.delayedAudioAppendQueue_.slice();
57243        this.logger_(`queuing delayed audio ${queue.length} appendBuffers`);
57244        this.delayedAudioAppendQueue_.length = 0;
57245        queue.forEach(que => {
57246          this.appendBuffer.apply(this, que);
57247        });
57248      }
57249    }
57250    /**
57251     * Get the audio buffer's buffered timerange.
57252     *
57253     * @return {TimeRange}
57254     *         The audio buffer's buffered time range
57255     */
57256
57257    audioBuffered() {
57258      // no media source/source buffer or it isn't in the media sources
57259      // source buffer list
57260      if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
57261        return createTimeRanges();
57262      }
57263      return this.audioBuffer.buffered ? this.audioBuffer.buffered : createTimeRanges();
57264    }
57265    /**
57266     * Get the video buffer's buffered timerange.
57267     *
57268     * @return {TimeRange}
57269     *         The video buffer's buffered time range
57270     */
57271
57272    videoBuffered() {
57273      // no media source/source buffer or it isn't in the media sources
57274      // source buffer list
57275      if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
57276        return createTimeRanges();
57277      }
57278      return this.videoBuffer.buffered ? this.videoBuffer.buffered : createTimeRanges();
57279    }
57280    /**
57281     * Get a combined video/audio buffer's buffered timerange.
57282     *
57283     * @return {TimeRange}
57284     *         the combined time range
57285     */
57286
57287    buffered() {
57288      const video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
57289      const audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
57290      if (audio && !video) {
57291        return this.audioBuffered();
57292      }
57293      if (video && !audio) {
57294        return this.videoBuffered();
57295      }
57296      return bufferIntersection(this.audioBuffered(), this.videoBuffered());
57297    }
57298    /**
57299     * Add a callback to the queue that will set duration on the mediaSource.
57300     *
57301     * @param {number} duration
57302     *        The duration to set
57303     *
57304     * @param {Function} [doneFn]
57305     *        function to run after duration has been set.
57306     */
57307
57308    setDuration(duration, doneFn = noop) {
57309      // In order to set the duration on the media source, it's necessary to wait for all
57310      // source buffers to no longer be updating. "If the updating attribute equals true on
57311      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
57312      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
57313      pushQueue({
57314        type: 'mediaSource',
57315        sourceUpdater: this,
57316        action: actions.duration(duration),
57317        name: 'duration',
57318        doneFn
57319      });
57320    }
57321    /**
57322     * Add a mediaSource endOfStream call to the queue
57323     *
57324     * @param {Error} [error]
57325     *        Call endOfStream with an error
57326     *
57327     * @param {Function} [doneFn]
57328     *        A function that should be called when the
57329     *        endOfStream call has finished.
57330     */
57331
57332    endOfStream(error = null, doneFn = noop) {
57333      if (typeof error !== 'string') {
57334        error = undefined;
57335      } // In order to set the duration on the media source, it's necessary to wait for all
57336      // source buffers to no longer be updating. "If the updating attribute equals true on
57337      // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
57338      // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
57339
57340      pushQueue({
57341        type: 'mediaSource',
57342        sourceUpdater: this,
57343        action: actions.endOfStream(error),
57344        name: 'endOfStream',
57345        doneFn
57346      });
57347    }
57348    /**
57349     * Queue an update to remove a time range from the buffer.
57350     *
57351     * @param {number} start where to start the removal
57352     * @param {number} end where to end the removal
57353     * @param {Function} [done=noop] optional callback to be executed when the remove
57354     * operation is complete
57355     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
57356     */
57357
57358    removeAudio(start, end, done = noop) {
57359      if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
57360        done();
57361        return;
57362      }
57363      pushQueue({
57364        type: 'audio',
57365        sourceUpdater: this,
57366        action: actions.remove(start, end),
57367        doneFn: done,
57368        name: 'remove'
57369      });
57370    }
57371    /**
57372     * Queue an update to remove a time range from the buffer.
57373     *
57374     * @param {number} start where to start the removal
57375     * @param {number} end where to end the removal
57376     * @param {Function} [done=noop] optional callback to be executed when the remove
57377     * operation is complete
57378     * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
57379     */
57380
57381    removeVideo(start, end, done = noop) {
57382      if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
57383        done();
57384        return;
57385      }
57386      pushQueue({
57387        type: 'video',
57388        sourceUpdater: this,
57389        action: actions.remove(start, end),
57390        doneFn: done,
57391        name: 'remove'
57392      });
57393    }
57394    /**
57395     * Whether the underlying sourceBuffer is updating or not
57396     *
57397     * @return {boolean} the updating status of the SourceBuffer
57398     */
57399
57400    updating() {
57401      // the audio/video source buffer is updating
57402      if (updating('audio', this) || updating('video', this)) {
57403        return true;
57404      }
57405      return false;
57406    }
57407    /**
57408     * Set/get the timestampoffset on the audio SourceBuffer
57409     *
57410     * @return {number} the timestamp offset
57411     */
57412
57413    audioTimestampOffset(offset) {
57414      if (typeof offset !== 'undefined' && this.audioBuffer &&
57415      // no point in updating if it's the same
57416      this.audioTimestampOffset_ !== offset) {
57417        pushQueue({
57418          type: 'audio',
57419          sourceUpdater: this,
57420          action: actions.timestampOffset(offset),
57421          name: 'timestampOffset'
57422        });
57423        this.audioTimestampOffset_ = offset;
57424      }
57425      return this.audioTimestampOffset_;
57426    }
57427    /**
57428     * Set/get the timestampoffset on the video SourceBuffer
57429     *
57430     * @return {number} the timestamp offset
57431     */
57432
57433    videoTimestampOffset(offset) {
57434      if (typeof offset !== 'undefined' && this.videoBuffer &&
57435      // no point in updating if it's the same
57436      this.videoTimestampOffset
vendor: 28,116 bytes, lines 57436-58234
57436 !== offset) {
57437        pushQueue({
57438          type: 'video',
57439          sourceUpdater: this,
57440          action: actions.timestampOffset(offset),
57441          name: 'timestampOffset'
57442        });
57443        this.videoTimestampOffset_ = offset;
57444      }
57445      return this.videoTimestampOffset_;
57446    }
57447    /**
57448     * Add a function to the queue that will be called
57449     * when it is its turn to run in the audio queue.
57450     *
57451     * @param {Function} callback
57452     *        The callback to queue.
57453     */
57454
57455    audioQueueCallback(callback) {
57456      if (!this.audioBuffer) {
57457        return;
57458      }
57459      pushQueue({
57460        type: 'audio',
57461        sourceUpdater: this,
57462        action: actions.callback(callback),
57463        name: 'callback'
57464      });
57465    }
57466    /**
57467     * Add a function to the queue that will be called
57468     * when it is its turn to run in the video queue.
57469     *
57470     * @param {Function} callback
57471     *        The callback to queue.
57472     */
57473
57474    videoQueueCallback(callback) {
57475      if (!this.videoBuffer) {
57476        return;
57477      }
57478      pushQueue({
57479        type: 'video',
57480        sourceUpdater: this,
57481        action: actions.callback(callback),
57482        name: 'callback'
57483      });
57484    }
57485    /**
57486     * dispose of the source updater and the underlying sourceBuffer
57487     */
57488
57489    dispose() {
57490      this.trigger('dispose');
57491      bufferTypes.forEach(type => {
57492        this.abort(type);
57493        if (this.canRemoveSourceBuffer()) {
57494          this.removeSourceBuffer(type);
57495        } else {
57496          this[`${type}QueueCallback`](() => cleanupBuffer(type, this));
57497        }
57498      });
57499      this.videoAppendQueued_ = false;
57500      this.delayedAudioAppendQueue_.length = 0;
57501      if (this.sourceopenListener_) {
57502        this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
57503      }
57504      this.off();
57505    }
57506  }
57507  const uint8ToUtf8 = uintArray => decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
57508  const bufferToHexString = buffer => {
57509    const uInt8Buffer = new Uint8Array(buffer);
57510    return Array.from(uInt8Buffer).map(byte => byte.toString(16).padStart(2, '0')).join('');
57511  };
57512
57513  /**
57514   * @file vtt-segment-loader.js
57515   */
57516  const VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0)));
57517  class NoVttJsError extends Error {
57518    constructor() {
57519      super('Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.');
57520    }
57521  }
57522  /**
57523   * An object that manages segment loading and appending.
57524   *
57525   * @class VTTSegmentLoader
57526   * @param {Object} options required and optional options
57527   * @extends videojs.EventTarget
57528   */
57529
57530  class VTTSegmentLoader extends SegmentLoader {
57531    constructor(settings, options = {}) {
57532      super(settings, options); // SegmentLoader requires a MediaSource be specified or it will throw an error;
57533      // however, VTTSegmentLoader has no need of a media source, so delete the reference
57534
57535      this.mediaSource_ = null;
57536      this.subtitlesTrack_ = null;
57537      this.loaderType_ = 'subtitle';
57538      this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
57539      this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
57540      // the sync controller leads to improper behavior.
57541
57542      this.shouldSaveSegmentTimingInfo_ = false;
57543    }
57544    createTransmuxer_() {
57545      // don't need to transmux any subtitles
57546      return null;
57547    }
57548    /**
57549     * Indicates which time ranges are buffered
57550     *
57551     * @return {TimeRange}
57552     *         TimeRange object representing the current buffered ranges
57553     */
57554
57555    buffered_() {
57556      if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
57557        return createTimeRanges();
57558      }
57559      const cues = this.subtitlesTrack_.cues;
57560      const start = cues[0].startTime;
57561      const end = cues[cues.length - 1].startTime;
57562      return createTimeRanges([[start, end]]);
57563    }
57564    /**
57565     * Gets and sets init segment for the provided map
57566     *
57567     * @param {Object} map
57568     *        The map object representing the init segment to get or set
57569     * @param {boolean=} set
57570     *        If true, the init segment for the provided map should be saved
57571     * @return {Object}
57572     *         map object for desired init segment
57573     */
57574
57575    initSegmentForMap(map, set = false) {
57576      if (!map) {
57577        return null;
57578      }
57579      const id = initSegmentId(map);
57580      let storedMap = this.initSegments_[id];
57581      if (set && !storedMap && map.bytes) {
57582        // append WebVTT line terminators to the media initialization segment if it exists
57583        // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
57584        // requires two or more WebVTT line terminators between the WebVTT header and the
57585        // rest of the file
57586        const combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
57587        const combinedSegment = new Uint8Array(combinedByteLength);
57588        combinedSegment.set(map.bytes);
57589        combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
57590        this.initSegments_[id] = storedMap = {
57591          resolvedUri: map.resolvedUri,
57592          byterange: map.byterange,
57593          bytes: combinedSegment
57594        };
57595      }
57596      return storedMap || map;
57597    }
57598    /**
57599     * Returns true if all configuration required for loading is present, otherwise false.
57600     *
57601     * @return {boolean} True if the all configuration is ready for loading
57602     * @private
57603     */
57604
57605    couldBeginLoading_() {
57606      return this.playlist_ && this.subtitlesTrack_ && !this.paused();
57607    }
57608    /**
57609     * Once all the starting parameters have been specified, begin
57610     * operation. This method should only be invoked from the INIT
57611     * state.
57612     *
57613     * @private
57614     */
57615
57616    init_() {
57617      this.state = 'READY';
57618      this.resetEverything();
57619      return this.monitorBuffer_();
57620    }
57621    /**
57622     * Set a subtitle track on the segment loader to add subtitles to
57623     *
57624     * @param {TextTrack=} track
57625     *        The text track to add loaded subtitles to
57626     * @return {TextTrack}
57627     *        Returns the subtitles track
57628     */
57629
57630    track(track) {
57631      if (typeof track === 'undefined') {
57632        return this.subtitlesTrack_;
57633      }
57634      this.subtitlesTrack_ = track; // if we were unpaused but waiting for a sourceUpdater, start
57635      // buffering now
57636
57637      if (this.state === 'INIT' && this.couldBeginLoading_()) {
57638        this.init_();
57639      }
57640      return this.subtitlesTrack_;
57641    }
57642    /**
57643     * Remove any data in the source buffer between start and end times
57644     *
57645     * @param {number} start - the start time of the region to remove from the buffer
57646     * @param {number} end - the end time of the region to remove from the buffer
57647     */
57648
57649    remove(start, end) {
57650      removeCuesFromTrack(start, end, this.subtitlesTrack_);
57651    }
57652    /**
57653     * fill the buffer with segements unless the sourceBuffers are
57654     * currently updating
57655     *
57656     * Note: this function should only ever be called by monitorBuffer_
57657     * and never directly
57658     *
57659     * @private
57660     */
57661
57662    fillBuffer_() {
57663      // see if we need to begin loading immediately
57664      const segmentInfo = this.chooseNextRequest_();
57665      if (!segmentInfo) {
57666        return;
57667      }
57668      if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
57669        // We don't have the timestamp offset that we need to sync subtitles.
57670        // Rerun on a timestamp offset or user interaction.
57671        const checkTimestampOffset = () => {
57672          this.state = 'READY';
57673          if (!this.paused()) {
57674            // if not paused, queue a buffer check as soon as possible
57675            this.monitorBuffer_();
57676          }
57677        };
57678        this.syncController_.one('timestampoffset', checkTimestampOffset);
57679        this.state = 'WAITING_ON_TIMELINE';
57680        return;
57681      }
57682      this.loadSegment_(segmentInfo);
57683    } // never set a timestamp offset for vtt segments.
57684
57685    timestampOffsetForSegment_() {
57686      return null;
57687    }
57688    chooseNextRequest_() {
57689      return this.skipEmptySegments_(super.chooseNextRequest_());
57690    }
57691    /**
57692     * Prevents the segment loader from requesting segments we know contain no subtitles
57693     * by walking forward until we find the next segment that we don't know whether it is
57694     * empty or not.
57695     *
57696     * @param {Object} segmentInfo
57697     *        a segment info object that describes the current segment
57698     * @return {Object}
57699     *         a segment info object that describes the current segment
57700     */
57701
57702    skipEmptySegments_(segmentInfo) {
57703      while (segmentInfo && segmentInfo.segment.empty) {
57704        // stop at the last possible segmentInfo
57705        if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
57706          segmentInfo = null;
57707          break;
57708        }
57709        segmentInfo = this.generateSegmentInfo_({
57710          playlist: segmentInfo.playlist,
57711          mediaIndex: segmentInfo.mediaIndex + 1,
57712          startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
57713          isSyncRequest: segmentInfo.isSyncRequest
57714        });
57715      }
57716      return segmentInfo;
57717    }
57718    stopForError(error) {
57719      this.error(error);
57720      this.state = 'READY';
57721      this.pause();
57722      this.trigger('error');
57723    }
57724    /**
57725     * append a decrypted segement to the SourceBuffer through a SourceUpdater
57726     *
57727     * @private
57728     */
57729
57730    segmentRequestFinished_(error, simpleSegment, result) {
57731      if (!this.subtitlesTrack_) {
57732        this.state = 'READY';
57733        return;
57734      }
57735      this.saveTransferStats_(simpleSegment.stats); // the request was aborted
57736
57737      if (!this.pendingSegment_) {
57738        this.state = 'READY';
57739        this.mediaRequestsAborted += 1;
57740        return;
57741      }
57742      if (error) {
57743        if (error.code === REQUEST_ERRORS.TIMEOUT) {
57744          this.handleTimeout_();
57745        }
57746        if (error.code === REQUEST_ERRORS.ABORTED) {
57747          this.mediaRequestsAborted += 1;
57748        } else {
57749          this.mediaRequestsErrored += 1;
57750        }
57751        this.stopForError(error);
57752        return;
57753      }
57754      const segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to
57755      // maintain functionality between segment loaders
57756
57757      this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); // if this request included a segment key, save that data in the cache
57758
57759      if (simpleSegment.key) {
57760        this.segmentKey(simpleSegment.key, true);
57761      }
57762      this.state = 'APPENDING'; // used for tests
57763
57764      this.trigger('appending');
57765      const segment = segmentInfo.segment;
57766      if (segment.map) {
57767        segment.map.bytes = simpleSegment.map.bytes;
57768      }
57769      segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
57770
57771      if (typeof window.WebVTT !== 'function' && typeof this.loadVttJs === 'function') {
57772        this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
57773        // script will be loaded once but multiple listeners will be added to the queue, which is expected.
57774
57775        this.loadVttJs().then(() => this.segmentRequestFinished_(error, simpleSegment, result), () => this.stopForError({
57776          message: 'Error loading vtt.js'
57777        }));
57778        return;
57779      }
57780      segment.requested = true;
57781      try {
57782        this.parseVTTCues_(segmentInfo);
57783      } catch (e) {
57784        this.stopForError({
57785          message: e.message
57786        });
57787        return;
57788      }
57789      this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
57790      if (segmentInfo.cues.length) {
57791        segmentInfo.timingInfo = {
57792          start: segmentInfo.cues[0].startTime,
57793          end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
57794        };
57795      } else {
57796        segmentInfo.timingInfo = {
57797          start: segmentInfo.startOfSegment,
57798          end: segmentInfo.startOfSegment + segmentInfo.duration
57799        };
57800      }
57801      if (segmentInfo.isSyncRequest) {
57802        this.trigger('syncinfoupdate');
57803        this.pendingSegment_ = null;
57804        this.state = 'READY';
57805        return;
57806      }
57807      segmentInfo.byteLength = segmentInfo.bytes.byteLength;
57808      this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
57809      // the subtitle track
57810
57811      segmentInfo.cues.forEach(cue => {
57812        this.subtitlesTrack_.addCue(this.featuresNativeTextTracks_ ? new window.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
57813      }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
57814      // cues to have identical time-intervals, but if the text is also identical
57815      // we can safely assume it is a duplicate that can be removed (ex. when a cue
57816      // "overlaps" VTT segments)
57817
57818      removeDuplicateCuesFromTrack(this.subtitlesTrack_);
57819      this.handleAppendsDone_();
57820    }
57821    handleData_() {// noop as we shouldn't be getting video/audio data captions
57822      // that we do not support here.
57823    }
57824    updateTimingInfoEnd_() {// noop
57825    }
57826    /**
57827     * Uses the WebVTT parser to parse the segment response
57828     *
57829     * @throws NoVttJsError
57830     *
57831     * @param {Object} segmentInfo
57832     *        a segment info object that describes the current segment
57833     * @private
57834     */
57835
57836    parseVTTCues_(segmentInfo) {
57837      let decoder;
57838      let decodeBytesToString = false;
57839      if (typeof window.WebVTT !== 'function') {
57840        // caller is responsible for exception handling.
57841        throw new NoVttJsError();
57842      }
57843      if (typeof window.TextDecoder === 'function') {
57844        decoder = new window.TextDecoder('utf8');
57845      } else {
57846        decoder = window.WebVTT.StringDecoder();
57847        decodeBytesToString = true;
57848      }
57849      const parser = new window.WebVTT.Parser(window, window.vttjs, decoder);
57850      segmentInfo.cues = [];
57851      segmentInfo.timestampmap = {
57852        MPEGTS: 0,
57853        LOCAL: 0
57854      };
57855      parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
57856      parser.ontimestampmap = map => {
57857        segmentInfo.timestampmap = map;
57858      };
57859      parser.onparsingerror = error => {
57860        videojs.log.warn('Error encountered when parsing cues: ' + error.message);
57861      };
57862      if (segmentInfo.segment.map) {
57863        let mapData = segmentInfo.segment.map.bytes;
57864        if (decodeBytesToString) {
57865          mapData = uint8ToUtf8(mapData);
57866        }
57867        parser.parse(mapData);
57868      }
57869      let segmentData = segmentInfo.bytes;
57870      if (decodeBytesToString) {
57871        segmentData = uint8ToUtf8(segmentData);
57872      }
57873      parser.parse(segmentData);
57874      parser.flush();
57875    }
57876    /**
57877     * Updates the start and end times of any cues parsed by the WebVTT parser using
57878     * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
57879     * from the SyncController
57880     *
57881     * @param {Object} segmentInfo
57882     *        a segment info object that describes the current segment
57883     * @param {Object} mappingObj
57884     *        object containing a mapping from TS to media time
57885     * @param {Object} playlist
57886     *        the playlist object containing the segment
57887     * @private
57888     */
57889
57890    updateTimeMapping_(segmentInfo, mappingObj, playlist) {
57891      const segment = segmentInfo.segment;
57892      if (!mappingObj) {
57893        // If the sync controller does not have a mapping of TS to Media Time for the
57894        // timeline, then we don't have enough information to update the cue
57895        // start/end times
57896        return;
57897      }
57898      if (!segmentInfo.cues.length) {
57899        // If there are no cues, we also do not have enough information to figure out
57900        // segment timing. Mark that the segment contains no cues so we don't re-request
57901        // an empty segment.
57902        segment.empty = true;
57903        return;
57904      }
57905      const timestampmap = segmentInfo.timestampmap;
57906      const diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping;
57907      segmentInfo.cues.forEach(cue => {
57908        // First convert cue time to TS time using the timestamp-map provided within the vtt
57909        cue.startTime += diff;
57910        cue.endTime += diff;
57911      });
57912      if (!playlist.syncInfo) {
57913        const firstStart = segmentInfo.cues[0].startTime;
57914        const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
57915        playlist.syncInfo = {
57916          mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
57917          time: Math.min(firstStart, lastStart - segment.duration)
57918        };
57919      }
57920    }
57921  }
57922
57923  /**
57924   * @file ad-cue-tags.js
57925   */
57926  /**
57927   * Searches for an ad cue that overlaps with the given mediaTime
57928   *
57929   * @param {Object} track
57930   *        the track to find the cue for
57931   *
57932   * @param {number} mediaTime
57933   *        the time to find the cue at
57934   *
57935   * @return {Object|null}
57936   *         the found cue or null
57937   */
57938
57939  const findAdCue = function (track, mediaTime) {
57940    const cues = track.cues;
57941    for (let i = 0; i < cues.length; i++) {
57942      const cue = cues[i];
57943      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
57944        return cue;
57945      }
57946    }
57947    return null;
57948  };
57949  const updateAdCues = function (media, track, offset = 0) {
57950    if (!media.segments) {
57951      return;
57952    }
57953    let mediaTime = offset;
57954    let cue;
57955    for (let i = 0; i < media.segments.length; i++) {
57956      const segment = media.segments[i];
57957      if (!cue) {
57958        // Since the cues will span for at least the segment duration, adding a fudge
57959        // factor of half segment duration will prevent duplicate cues from being
57960        // created when timing info is not exact (e.g. cue start time initialized
57961        // at 10.006677, but next call mediaTime is 10.003332 )
57962        cue = findAdCue(track, mediaTime + segment.duration / 2);
57963      }
57964      if (cue) {
57965        if ('cueIn' in segment) {
57966          // Found a CUE-IN so end the cue
57967          cue.endTime = mediaTime;
57968          cue.adEndTime = mediaTime;
57969          mediaTime += segment.duration;
57970          cue = null;
57971          continue;
57972        }
57973        if (mediaTime < cue.endTime) {
57974          // Already processed this mediaTime for this cue
57975          mediaTime += segment.duration;
57976          continue;
57977        } // otherwise extend cue until a CUE-IN is found
57978
57979        cue.endTime += segment.duration;
57980      } else {
57981        if ('cueOut' in segment) {
57982          cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
57983          cue.adStartTime = mediaTime; // Assumes tag format to be
57984          // #EXT-X-CUE-OUT:30
57985
57986          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
57987          track.addCue(cue);
57988        }
57989        if ('cueOutCont' in segment) {
57990          // Entered into the middle of an ad cue
57991          // Assumes tag formate to be
57992          // #EXT-X-CUE-OUT-CONT:10/30
57993          const [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat);
57994          cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, '');
57995          cue.adStartTime = mediaTime - adOffset;
57996          cue.adEndTime = cue.adStartTime + adTotal;
57997          track.addCue(cue);
57998        }
57999      }
58000      mediaTime += segment.duration;
58001    }
58002  };
58003
58004  /**
58005   * @file sync-controller.js
58006   */
58007  // synchronize expired playlist segments.
58008  // the max media sequence diff is 48 hours of live stream
58009  // content with two second segments. Anything larger than that
58010  // will likely be invalid.
58011
58012  const MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
58013  const syncPointStrategies = [
58014  // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
58015  //                the equivalence display-time 0 === segment-index 0
58016  {
58017    name: 'VOD',
58018    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58019      if (duration !== Infinity) {
58020        const syncPoint = {
58021          time: 0,
58022          segmentIndex: 0,
58023          partIndex: null
58024        };
58025        return syncPoint;
58026      }
58027      return null;
58028    }
58029  }, {
58030    name: 'MediaSequence',
58031    /**
58032     * run media sequence strategy
58033     *
58034     * @param {SyncController} syncController
58035     * @param {Object} playlist
58036     * @param {number} duration
58037     * @param {number} currentTimeline
58038     * @param {number} currentTime
58039     * @param {string} type
58040     */
58041    run: (syncController, playlist, duration, currentTimeline, currentTime, type) => {
58042      if (!type) {
58043        return null;
58044      }
58045      const mediaSequenceMap = syncController.getMediaSequenceMap(type);
58046      if (!mediaSequenceMap || mediaSequenceMap.size === 0) {
58047        return null;
58048      }
58049      if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
58050        return null;
58051      }
58052      let currentMediaSequence = playlist.mediaSequence;
58053      let segmentIndex = 0;
58054      for (const segment of playlist.segments) {
58055        const range = mediaSequenceMap.get(currentMediaSequence);
58056        if (!range) {
58057          // unexpected case
58058          // we expect this playlist to be the same playlist in the map
58059          // just break from the loop and move forward to the next strategy
58060          break;
58061        }
58062        if (currentTime >= range.start && currentTime < range.end) {
58063          // we found segment
58064          if (Array.isArray(segment.parts) && segment.parts.length) {
58065            let currentPartStart = range.start;
58066            let partIndex = 0;
58067            for (const part of segment.parts) {
58068              const start = currentPartStart;
58069              const end = start + part.duration;
58070              if (currentTime >= start && currentTime < end) {
58071                return {
58072                  time: range.start,
58073                  segmentIndex,
58074                  partIndex
58075                };
58076              }
58077              partIndex++;
58078              currentPartStart = end;
58079            }
58080          } // no parts found, return sync point for segment
58081
58082          return {
58083            time: range.start,
58084            segmentIndex,
58085            partIndex: null
58086          };
58087        }
58088        segmentIndex++;
58089        currentMediaSequence++;
58090      } // we didn't find any segments for provided current time
58091
58092      return null;
58093    }
58094  },
58095  // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
58096  {
58097    name: 'ProgramDateTime',
58098    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58099      if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
58100        return null;
58101      }
58102      let syncPoint = null;
58103      let lastDistance = null;
58104      const partsAndSegments = getPartsAndSegments(playlist);
58105      currentTime = currentTime || 0;
58106      for (let i = 0; i < partsAndSegments.length; i++) {
58107        // start from the end and loop backwards for live
58108        // or start from the front and loop forwards for non-live
58109        const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58110        const partAndSegment = partsAndSegments[index];
58111        const segment = partAndSegment.segment;
58112        const datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
58113        if (!datetimeMapping || !segment.dateTimeObject) {
58114          continue;
58115        }
58116        const segmentTime = segment.dateTimeObject.getTime() / 1000;
58117        let start = segmentTime + datetimeMapping; // take part duration into account.
58118
58119        if (segment.parts && typeof partAndSegment.partIndex === 'number') {
58120          for (let z = 0; z < partAndSegment.partIndex; z++) {
58121            start += segment.parts[z].duration;
58122          }
58123        }
58124        const distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
58125        // currentTime and can stop looking for better candidates
58126
58127        if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
58128          break;
58129        }
58130        lastDistance = distance;
58131        syncPoint = {
58132          time: start,
58133          segmentIndex: partAndSegment.segmentIndex,
58134          partIndex: partAndSegment.partIndex
58135        };
58136      }
58137      return syncPoint;
58138    }
58139  },
58140  // Stategy "Segment": We have a known time mapping for a timeline and a
58141  //                    segment in the current timeline with timing data
58142  {
58143    name: 'Segment',
58144    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58145      let syncPoint = null;
58146      let lastDistance = null;
58147      currentTime = currentTime || 0;
58148      const partsAndSegments = getPartsAndSegments(playlist);
58149      for (let i = 0; i < partsAndSegments.length; i++) {
58150        // start from the end and loop backwards for live
58151        // or start from the front and loop forwards for non-live
58152        const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58153        const partAndSegment = partsAndSegments[index];
58154        const segment = partAndSegment.segment;
58155        const start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
58156        if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
58157          const distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
58158          // currentTime and can stop looking for better candidates
58159
58160          if (lastDistance !== null && lastDistance < distance) {
58161            break;
58162          }
58163          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
58164            lastDistance = distance;
58165            syncPoint = {
58166              time: start,
58167              segmentIndex: partAndSegment.segmentIndex,
58168              partIndex: partAndSegment.partIndex
58169            };
58170          }
58171        }
58172      }
58173      return syncPoint;
58174    }
58175  },
58176  // Stategy "Discontinuity": We have a discontinuity with a known
58177  //                          display-time
58178  {
58179    name: 'Discontinuity',
58180    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58181      let syncPoint = null;
58182      currentTime = currentTime || 0;
58183      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
58184        let lastDistance = null;
58185        for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
58186          const segmentIndex = playlist.discontinuityStarts[i];
58187          const discontinuity = playlist.discontinuitySequence + i + 1;
58188          const discontinuitySync = syncController.discontinuities[discontinuity];
58189          if (discontinuitySync) {
58190            const distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
58191            // currentTime and can stop looking for better candidates
58192
58193            if (lastDistance !== null && lastDistance < distance) {
58194              break;
58195            }
58196            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
58197              lastDistance = distance;
58198              syncPoint = {
58199                time: discontinuitySync.time,
58200                segmentIndex,
58201                partIndex: null
58202              };
58203            }
58204          }
58205        }
58206      }
58207      return syncPoint;
58208    }
58209  },
58210  // Stategy "Playlist": We have a playlist with a known mapping of
58211  //                     segment index to display time
58212  {
58213    name: 'Playlist',
58214    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58215      if (playlist.syncInfo) {
58216        const syncPoint = {
58217          time: playlist.syncInfo.time,
58218          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
58219          partIndex: null
58220        };
58221        return syncPoint;
58222      }
58223      return null;
58224    }
58225  }];
58226  class SyncController extends videojs.EventTarget {
58227    constructor(options = {}) {
58228      super(); // ...for synching across variants
58229
58230      this.timelines = [];
58231      this.discontinuities = [];
58232      this.timelineToDatetimeMappings = {};
58233      /**
58234       * @type {Map<string, Map<number, { start: number, end: number }>
58234>}
58235       * @private
58236       */
58237
58238      this.mediaSequenceStorage_ = new Map();
58239      this.logger_ = logger('SyncController');
58240    }
58241    /**
58242     * Get media sequence map by type
58243     *
58244     * @param {string} type - segment loader type
58245     * @return {Map<number, { start: number, end: number }> | undefined}
58246     */
58247
58248    getMediaSequenceMap(type) {
58249      return this.mediaSequenceStorage_.get(type);
58250    }
58251    /**
58252     * Update Media Sequence Map -> <MediaSequence, Range>
58253     *
58254     * @param {Object} playlist - parsed playlist
58255     * @param {number} currentTime - current player's time
58256     * @param {string} type - segment loader type
58257     * @return {void}
58258     */
58259
58260    updateMediaSequenceMap(playlist, currentTime, type) {
58261      // we should not process this playlist if it does not have mediaSequence or segments
58262      if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
58263        return;
58264      }
58265      const currentMap = this.getMediaSequenceMap(type);
58266      const result = new Map();
58267      let currentMediaSequence = playlist.mediaSequence;
58268      let currentBaseTime;
58269      if (!currentMap) {
58270        // first playlist setup:
58271        currentBaseTime = 0;
58272      } else if (currentMap.has(playlist.mediaSequence)) {
58273        // further playlists setup:
58274        currentBaseTime = currentMap.get(playlist.mediaSequence).start;
58275      } else {
58276        // it seems like we have a gap between playlists, use current time as a fallback:
58277        this.logger_(`MediaSequence sync for ${type} segment loader - received a gap between playlists.
58278Fallback base time to: ${currentTime}.
58279Received media sequence: ${currentMediaSequence}.
58280Current map: `, currentMap);
58281        currentBaseTime = currentTime;
58282      }
58283      this.logger_(`MediaSequence sync for ${type} segment loader.
58284Received media sequence: ${currentMediaSequence}.
58285base time is ${currentBaseTime}
58286Current map: `, currentMap);
58287      playlist.segments.forEach(segment => {
58288        const start = currentBaseTime;
58289        const end = start + segment.duration;
58290        const range = {
58291          start,
58292          end
58293        };
58294        result.set(currentMediaSequence, range);
58295        currentMediaSequence++;
58296        currentBaseTime = end;
58297      });
58298      this.mediaSequenceStorage_.set(type, result);
58299    }
58300    /**
58301     * Find a sync-point for the playlist specified
58302     *
58303     * A sync-point is defined as a known mapping from display-time to
58304     * a segment-index in the current playlist.
58305     *
58306     * @param {Playlist} playlist
58307     *        The playlist that needs a sync-point
58308     * @param {number} duration
58309     *        Duration of the MediaSource (Infinite if playing a live source)
58310     * @param {number} currentTimeline
58311     *        The last timeline from which a segment was loaded
58312     * @param {number} currentTime
58313     *        Current player's time
58314     * @param {string} type
58315     *        Segment loader type
58316     * @return {Object}
58317     *          A sync-point object
58318     */
58319
58320    getSyncPoint(playlist, duration, currentTimeline, currentTime, type) {
58321      // Always use VOD sync point for VOD
58322      if (duration !== Infinity) {
58323        const vodSyncPointStrategy = syncPointStrategies.find(({
58324          name
58325        }) => name === 'VOD');
58326        return vodSyncPointStrategy.run(this, playlist, duration);
58327      }
58328      const syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime, type);
58329      if (!syncPoints.length) {
58330        // Signal that we need to attempt to get a sync-point manually
58331        // by fetching a segment in the playlist and constructing
58332        // a sync-point from that information
58333        return null;
58334      } // If we have exact match just return it instead of finding the nearest distance
58335
58336      for (const syncPointInfo of syncPoints) {
58337        const {
58338          syncPoint,
58339          strategy
58340        } = syncPointInfo;
58341        const {
58342          segmentIndex,
58343          time
58344        } = syncPoint;
58345        if (segmentIndex < 0) {
58346          continue;
58347        }
58348        const selectedSegment = playlist.segments[segmentIndex];
58349        const start = time;
58350        const end = start + selectedSegment.duration;
58351        this.logger_(`Strategy: ${strategy}. Current time: ${currentTime}. selected segment: ${segmentIndex}. Time: [${start} -> ${end}]}`);
58352        if (currentTime >= start && currentTime < end) {
58353          this.logger_('Found sync point with exact match: ', syncPoint);
58354          return syncPoint;
58355        }
58356      } // Now find the sync-point that is closest to the currentTime because
58357      // that should result in the most accurate guess about which segment
58358      // to fetch
58359
58360      return this.selectSyncPoint_(syncPoints, {
58361        key: 'time',
58362        value: currentTime
58363      });
58364    }
58365    /**
58366     * Calculate the amount of time that has expired off the playlist during playback
58367     *
58368     * @param {Playlist} playlist
58369     *        Playlist object to calculate expired from
58370     * @param {number} duration
58371     *        Duration of the MediaSource (Infinity if playling a live source)
58372     * @return {number|null}
58373     *          The amount of time that has expired off the playlist during playback. Null
58374     *          if no sync-points for the playlist can be found.
58375     */
58376
58377    getExpiredTime(playlist, duration) {
58378      if (!playlist || !playlist.segments) {
58379        return null;
58380      }
58381      const syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0, 'main'); // Without sync-points, there is not enough information to determine the expired time
58382
58383      if (!syncPoints.length) {
58384        return null;
58385      }
58386      const syncPoint = this.selectSyncPoint_(syncPoints, {
58387        key: 'segmentIndex',
58388        value: 0
58389      }); // If the sync-point is beyond the start of the playlist, we want to subtract the
58390      // duration from index 0 to syncPoint.segmentIndex instead of adding.
58391
58392      if (syncPoint.segmentIndex > 0) {
58393        syncPoint.time *= -1;
58394      }
58395      return Math.abs(syncPoint.time + sumDurations({
58396        defaultDuration: playlist.targetDuration,
58397        durationList: playlist.segments,
58398        startIndex: syncPoint.segmentIndex,
58399        endIndex: 0
58400      }));
58401    }
58402    /**
58403     * Runs each sync-point strategy and returns a list of sync-points returned by the
58404     * strategies
58405     *
58406     * @private
58407     * @param {Playlist} playlist
58408     *        The playlist that needs a sync-point
58409     * @param {number} duration
58410     *        Duration of the MediaSource (Infinity if playing a live source)
58411     * @param {number} currentTimeline
58412     *        The last timeline from which a segment was loaded
58413     * @param {number} currentTime
vendor: 16,973 bytes, lines 58414-58861
58414     *        Current player's time
58415     * @param {string} type
58416     *        Segment loader type
58417     * @return {Array}
58418     *          A list of sync-point objects
58419     */
58420
58421    runStrategies_(playlist, duration, currentTimeline, currentTime, type) {
58422      const syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
58423
58424      for (let i = 0; i < syncPointStrategies.length; i++) {
58425        const strategy = syncPointStrategies[i];
58426        const syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime, type);
58427        if (syncPoint) {
58428          syncPoint.strategy = strategy.name;
58429          syncPoints.push({
58430            strategy: strategy.name,
58431            syncPoint
58432          });
58433        }
58434      }
58435      return syncPoints;
58436    }
58437    /**
58438     * Selects the sync-point nearest the specified target
58439     *
58440     * @private
58441     * @param {Array} syncPoints
58442     *        List of sync-points to select from
58443     * @param {Object} target
58444     *        Object specifying the property and value we are targeting
58445     * @param {string} target.key
58446     *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
58447     * @param {number} target.value
58448     *        The value to target for the specified key.
58449     * @return {Object}
58450     *          The sync-point nearest the target
58451     */
58452
58453    selectSyncPoint_(syncPoints, target) {
58454      let bestSyncPoint = syncPoints[0].syncPoint;
58455      let bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
58456      let bestStrategy = syncPoints[0].strategy;
58457      for (let i = 1; i < syncPoints.length; i++) {
58458        const newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
58459        if (newDistance < bestDistance) {
58460          bestDistance = newDistance;
58461          bestSyncPoint = syncPoints[i].syncPoint;
58462          bestStrategy = syncPoints[i].strategy;
58463        }
58464      }
58465      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}` : '') + ']');
58466      return bestSyncPoint;
58467    }
58468    /**
58469     * Save any meta-data present on the segments when segments leave
58470     * the live window to the playlist to allow for synchronization at the
58471     * playlist level later.
58472     *
58473     * @param {Playlist} oldPlaylist - The previous active playlist
58474     * @param {Playlist} newPlaylist - The updated and most current playlist
58475     */
58476
58477    saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
58478      const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
58479
58480      if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
58481        videojs.log.warn(`Not saving expired segment info. Media sequence gap ${mediaSequenceDiff} is too large.`);
58482        return;
58483      } // When a segment expires from the playlist and it has a start time
58484      // save that information as a possible sync-point reference in future
58485
58486      for (let i = mediaSequenceDiff - 1; i >= 0; i--) {
58487        const lastRemovedSegment = oldPlaylist.segments[i];
58488        if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
58489          newPlaylist.syncInfo = {
58490            mediaSequence: oldPlaylist.mediaSequence + i,
58491            time: lastRemovedSegment.start
58492          };
58493          this.logger_(`playlist refresh sync: [time:${newPlaylist.syncInfo.time},` + ` mediaSequence: ${newPlaylist.syncInfo.mediaSequence}]`);
58494          this.trigger('syncinfoupdate');
58495          break;
58496        }
58497      }
58498    }
58499    /**
58500     * Save the mapping from playlist's ProgramDateTime to display. This should only happen
58501     * before segments start to load.
58502     *
58503     * @param {Playlist} playlist - The currently active playlist
58504     */
58505
58506    setDateTimeMappingForStart(playlist) {
58507      // It's possible for the playlist to be updated before playback starts, meaning time
58508      // zero is not yet set. If, during these playlist refreshes, a discontinuity is
58509      // crossed, then the old time zero mapping (for the prior timeline) would be retained
58510      // unless the mappings are cleared.
58511      this.timelineToDatetimeMappings = {};
58512      if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
58513        const firstSegment = playlist.segments[0];
58514        const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
58515        this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
58516      }
58517    }
58518    /**
58519     * Calculates and saves timeline mappings, playlist sync info, and segment timing values
58520     * based on the latest timing information.
58521     *
58522     * @param {Object} options
58523     *        Options object
58524     * @param {SegmentInfo} options.segmentInfo
58525     *        The current active request information
58526     * @param {boolean} options.shouldSaveTimelineMapping
58527     *        If there's a timeline change, determines if the timeline mapping should be
58528     *        saved for timeline mapping and program date time mappings.
58529     */
58530
58531    saveSegmentTimingInfo({
58532      segmentInfo,
58533      shouldSaveTimelineMapping
58534    }) {
58535      const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
58536      const segment = segmentInfo.segment;
58537      if (didCalculateSegmentTimeMapping) {
58538        this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
58539        // now with segment timing information
58540
58541        if (!segmentInfo.playlist.syncInfo) {
58542          segmentInfo.playlist.syncInfo = {
58543            mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
58544            time: segment.start
58545          };
58546        }
58547      }
58548      const dateTime = segment.dateTimeObject;
58549      if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
58550        this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
58551      }
58552    }
58553    timestampOffsetForTimeline(timeline) {
58554      if (typeof this.timelines[timeline] === 'undefined') {
58555        return null;
58556      }
58557      return this.timelines[timeline].time;
58558    }
58559    mappingForTimeline(timeline) {
58560      if (typeof this.timelines[timeline] === 'undefined') {
58561        return null;
58562      }
58563      return this.timelines[timeline].mapping;
58564    }
58565    /**
58566     * Use the "media time" for a segment to generate a mapping to "display time" and
58567     * save that display time to the segment.
58568     *
58569     * @private
58570     * @param {SegmentInfo} segmentInfo
58571     *        The current active request information
58572     * @param {Object} timingInfo
58573     *        The start and end time of the current segment in "media time"
58574     * @param {boolean} shouldSaveTimelineMapping
58575     *        If there's a timeline change, determines if the timeline mapping should be
58576     *        saved in timelines.
58577     * @return {boolean}
58578     *          Returns false if segment time mapping could not be calculated
58579     */
58580
58581    calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
58582      // TODO: remove side effects
58583      const segment = segmentInfo.segment;
58584      const part = segmentInfo.part;
58585      let mappingObj = this.timelines[segmentInfo.timeline];
58586      let start;
58587      let end;
58588      if (typeof segmentInfo.timestampOffset === 'number') {
58589        mappingObj = {
58590          time: segmentInfo.startOfSegment,
58591          mapping: segmentInfo.startOfSegment - timingInfo.start
58592        };
58593        if (shouldSaveTimelineMapping) {
58594          this.timelines[segmentInfo.timeline] = mappingObj;
58595          this.trigger('timestampoffset');
58596          this.logger_(`time mapping for timeline ${segmentInfo.timeline}: ` + `[time: ${mappingObj.time}] [mapping: ${mappingObj.mapping}]`);
58597        }
58598        start = segmentInfo.startOfSegment;
58599        end = timingInfo.end + mappingObj.mapping;
58600      } else if (mappingObj) {
58601        start = timingInfo.start + mappingObj.mapping;
58602        end = timingInfo.end + mappingObj.mapping;
58603      } else {
58604        return false;
58605      }
58606      if (part) {
58607        part.start = start;
58608        part.end = end;
58609      } // If we don't have a segment start yet or the start value we got
58610      // is less than our current segment.start value, save a new start value.
58611      // We have to do this because parts will have segment timing info saved
58612      // multiple times and we want segment start to be the earliest part start
58613      // value for that segment.
58614
58615      if (!segment.start || start < segment.start) {
58616        segment.start = start;
58617      }
58618      segment.end = end;
58619      return true;
58620    }
58621    /**
58622     * Each time we have discontinuity in the playlist, attempt to calculate the location
58623     * in display of the start of the discontinuity and save that. We also save an accuracy
58624     * value so that we save values with the most accuracy (closest to 0.)
58625     *
58626     * @private
58627     * @param {SegmentInfo} segmentInfo - The current active request information
58628     */
58629
58630    saveDiscontinuitySyncInfo_(segmentInfo) {
58631      const playlist = segmentInfo.playlist;
58632      const segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
58633      // the start of the range and it's accuracy is 0 (greater accuracy values
58634      // mean more approximation)
58635
58636      if (segment.discontinuity) {
58637        this.discontinuities[segment.timeline] = {
58638          time: segment.start,
58639          accuracy: 0
58640        };
58641      } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
58642        // Search for future discontinuities that we can provide better timing
58643        // information for and save that information for sync purposes
58644        for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
58645          const segmentIndex = playlist.discontinuityStarts[i];
58646          const discontinuity = playlist.discontinuitySequence + i + 1;
58647          const mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
58648          const accuracy = Math.abs(mediaIndexDiff);
58649          if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
58650            let time;
58651            if (mediaIndexDiff < 0) {
58652              time = segment.start - sumDurations({
58653                defaultDuration: playlist.targetDuration,
58654                durationList: playlist.segments,
58655                startIndex: segmentInfo.mediaIndex,
58656                endIndex: segmentIndex
58657              });
58658            } else {
58659              time = segment.end + sumDurations({
58660                defaultDuration: playlist.targetDuration,
58661                durationList: playlist.segments,
58662                startIndex: segmentInfo.mediaIndex + 1,
58663                endIndex: segmentIndex
58664              });
58665            }
58666            this.discontinuities[discontinuity] = {
58667              time,
58668              accuracy
58669            };
58670          }
58671        }
58672      }
58673    }
58674    dispose() {
58675      this.trigger('dispose');
58676      this.off();
58677    }
58678  }
58679
58680  /**
58681   * The TimelineChangeController acts as a source for segment loaders to listen for and
58682   * keep track of latest and pending timeline changes. This is useful to ensure proper
58683   * sync, as each loader may need to make a consideration for what timeline the other
58684   * loader is on before making changes which could impact the other loader's media.
58685   *
58686   * @class TimelineChangeController
58687   * @extends videojs.EventTarget
58688   */
58689
58690  class TimelineChangeController extends videojs.EventTarget {
58691    constructor() {
58692      super();
58693      this.pendingTimelineChanges_ = {};
58694      this.lastTimelineChanges_ = {};
58695    }
58696    clearPendingTimelineChange(type) {
58697      this.pendingTimelineChanges_[type] = null;
58698      this.trigger('pendingtimelinechange');
58699    }
58700    pendingTimelineChange({
58701      type,
58702      from,
58703      to
58704    }) {
58705      if (typeof from === 'number' && typeof to === 'number') {
58706        this.pendingTimelineChanges_[type] = {
58707          type,
58708          from,
58709          to
58710        };
58711        this.trigger('pendingtimelinechange');
58712      }
58713      return this.pendingTimelineChanges_[type];
58714    }
58715    lastTimelineChange({
58716      type,
58717      from,
58718      to
58719    }) {
58720      if (typeof from === 'number' && typeof to === 'number') {
58721        this.lastTimelineChanges_[type] = {
58722          type,
58723          from,
58724          to
58725        };
58726        delete this.pendingTimelineChanges_[type];
58727        this.trigger('timelinechange');
58728      }
58729      return this.lastTimelineChanges_[type];
58730    }
58731    dispose() {
58732      this.trigger('dispose');
58733      this.pendingTimelineChanges_ = {};
58734      this.lastTimelineChanges_ = {};
58735      this.off();
58736    }
58737  }
58738
58739  /* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/decrypter-worker.js */
58740  const workerCode = transform(getWorkerString(function () {
58741    /**
58742     * @file stream.js
58743     */
58744
58745    /**
58746     * A lightweight readable stream implemention that handles event dispatching.
58747     *
58748     * @class Stream
58749     */
58750
58751    var Stream = /*#__PURE__*/function () {
58752      function Stream() {
58753        this.listeners = {};
58754      }
58755      /**
58756       * Add a listener for a specified event type.
58757       *
58758       * @param {string} type the event name
58759       * @param {Function} listener the callback to be invoked when an event of
58760       * the specified type occurs
58761       */
58762
58763      var _proto = Stream.prototype;
58764      _proto.on = function on(type, listener) {
58765        if (!this.listeners[type]) {
58766          this.listeners[type] = [];
58767        }
58768        this.listeners[type].push(listener);
58769      }
58770      /**
58771       * Remove a listener for a specified event type.
58772       *
58773       * @param {string} type the event name
58774       * @param {Function} listener  a function previously registered for this
58775       * type of event through `on`
58776       * @return {boolean} if we could turn it off or not
58777       */;
58778
58779      _proto.off = function off(type, listener) {
58780        if (!this.listeners[type]) {
58781          return false;
58782        }
58783        var index = this.listeners[type].indexOf(listener); // TODO: which is better?
58784        // In Video.js we slice listener functions
58785        // on trigger so that it does not mess up the order
58786        // while we loop through.
58787        //
58788        // Here we slice on off so that the loop in trigger
58789        // can continue using it's old reference to loop without
58790        // messing up the order.
58791
58792        this.listeners[type] = this.listeners[type].slice(0);
58793        this.listeners[type].splice(index, 1);
58794        return index > -1;
58795      }
58796      /**
58797       * Trigger an event of the specified type on this stream. Any additional
58798       * arguments to this function are passed as parameters to event listeners.
58799       *
58800       * @param {string} type the event name
58801       */;
58802
58803      _proto.trigger = function trigger(type) {
58804        var callbacks = this.listeners[type];
58805        if (!callbacks) {
58806          return;
58807        } // Slicing the arguments on every invocation of this method
58808        // can add a significant amount of overhead. Avoid the
58809        // intermediate object creation for the common case of a
58810        // single callback argument
58811
58812        if (arguments.length === 2) {
58813          var length = callbacks.length;
58814          for (var i = 0; i < length; ++i) {
58815            callbacks[i].call(this, arguments[1]);
58816          }
58817        } else {
58818          var args = Array.prototype.slice.call(arguments, 1);
58819          var _length = callbacks.length;
58820          for (var _i = 0; _i < _length; ++_i) {
58821            callbacks[_i].apply(this, args);
58822          }
58823        }
58824      }
58825      /**
58826       * Destroys the stream and cleans up.
58827       */;
58828
58829      _proto.dispose = function dispose() {
58830        this.listeners = {};
58831      }
58832      /**
58833       * Forwards all `data` events on this stream to the destination stream. The
58834       * destination stream should provide a method `push` to receive the data
58835       * events as they arrive.
58836       *
58837       * @param {Stream} destination the stream that will receive all `data` events
58838       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
58839       */;
58840
58841      _proto.pipe = function pipe(destination) {
58842        this.on('data', function (data) {
58843          destination.push(data);
58844        });
58845      };
58846      return Stream;
58847    }();
58848    /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
58849
58850    /**
58851     * Returns the subarray of a Uint8Array without PKCS#7 padding.
58852     *
58853     * @param padded {Uint8Array} unencrypted bytes that have been padded
58854     * @return {Uint8Array} the unpadded bytes
58855     * @see http://tools.ietf.org/html/rfc5652
58856     */
58857
58858    function unpad(padded) {
58859      return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
58860    }
58861    /*! @name aes-decrypter @version 4.0.1
vendor: 17,382 bytes, lines 58861-59333
58861 @license Apache-2.0 */
58862
58863    /**
58864     * @file aes.js
58865     *
58866     * This file contains an adaptation of the AES decryption algorithm
58867     * from the Standford Javascript Cryptography Library. That work is
58868     * covered by the following copyright and permissions notice:
58869     *
58870     * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
58871     * All rights reserved.
58872     *
58873     * Redistribution and use in source and binary forms, with or without
58874     * modification, are permitted provided that the following conditions are
58875     * met:
58876     *
58877     * 1. Redistributions of source code must retain the above copyright
58878     *    notice, this list of conditions and the following disclaimer.
58879     *
58880     * 2. Redistributions in binary form must reproduce the above
58881     *    copyright notice, this list of conditions and the following
58882     *    disclaimer in the documentation and/or other materials provided
58883     *    with the distribution.
58884     *
58885     * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
58886     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
58887     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
58888     * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
58889     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
58890     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
58891     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
58892     * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
58893     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
58894     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
58895     * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58896     *
58897     * The views and conclusions contained in the software and documentation
58898     * are those of the authors and should not be interpreted as representing
58899     * official policies, either expressed or implied, of the authors.
58900     */
58901
58902    /**
58903     * Expand the S-box tables.
58904     *
58905     * @private
58906     */
58907
58908    const precompute = function () {
58909      const tables = [[[], [], [], [], []], [[], [], [], [], []]];
58910      const encTable = tables[0];
58911      const decTable = tables[1];
58912      const sbox = encTable[4];
58913      const sboxInv = decTable[4];
58914      let i;
58915      let x;
58916      let xInv;
58917      const d = [];
58918      const th = [];
58919      let x2;
58920      let x4;
58921      let x8;
58922      let s;
58923      let tEnc;
58924      let tDec; // Compute double and third tables
58925
58926      for (i = 0; i < 256; i++) {
58927        th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
58928      }
58929      for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
58930        // Compute sbox
58931        s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
58932        s = s >> 8 ^ s & 255 ^ 99;
58933        sbox[x] = s;
58934        sboxInv[s] = x; // Compute MixColumns
58935
58936        x8 = d[x4 = d[x2 = d[x]]];
58937        tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
58938        tEnc = d[s] * 0x101 ^ s * 0x1010100;
58939        for (i = 0; i < 4; i++) {
58940          encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
58941          decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
58942        }
58943      } // Compactify. Considerable speedup on Firefox.
58944
58945      for (i = 0; i < 5; i++) {
58946        encTable[i] = encTable[i].slice(0);
58947        decTable[i] = decTable[i].slice(0);
58948      }
58949      return tables;
58950    };
58951    let aesTables = null;
58952    /**
58953     * Schedule out an AES key for both encryption and decryption. This
58954     * is a low-level class. Use a cipher mode to do bulk encryption.
58955     *
58956     * @class AES
58957     * @param key {Array} The key as an array of 4, 6 or 8 words.
58958     */
58959
58960    class AES {
58961      constructor(key) {
58962        /**
58963        * The expanded S-box and inverse S-box tables. These will be computed
58964        * on the client so that we don't have to send them down the wire.
58965        *
58966        * There are two tables, _tables[0] is for encryption and
58967        * _tables[1] is for decryption.
58968        *
58969        * The first 4 sub-tables are the expanded S-box with MixColumns. The
58970        * last (_tables[01][4]) is the S-box itself.
58971        *
58972        * @private
58973        */
58974        // if we have yet to precompute the S-box tables
58975        // do so now
58976        if (!aesTables) {
58977          aesTables = precompute();
58978        } // then make a copy of that object for use
58979
58980        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()]];
58981        let i;
58982        let j;
58983        let tmp;
58984        const sbox = this._tables[0][4];
58985        const decTable = this._tables[1];
58986        const keyLen = key.length;
58987        let rcon = 1;
58988        if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
58989          throw new Error('Invalid aes key size');
58990        }
58991        const encKey = key.slice(0);
58992        const decKey = [];
58993        this._key = [encKey, decKey]; // schedule encryption keys
58994
58995        for (i = keyLen; i < 4 * keyLen + 28; i++) {
58996          tmp = encKey[i - 1]; // apply sbox
58997
58998          if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
58999            tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
59000
59001            if (i % keyLen === 0) {
59002              tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
59003              rcon = rcon << 1 ^ (rcon >> 7) * 283;
59004            }
59005          }
59006          encKey[i] = encKey[i - keyLen] ^ tmp;
59007        } // schedule decryption keys
59008
59009        for (j = 0; i; j++, i--) {
59010          tmp = encKey[j & 3 ? i : i - 4];
59011          if (i <= 4 || j < 4) {
59012            decKey[j] = tmp;
59013          } else {
59014            decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
59015          }
59016        }
59017      }
59018      /**
59019       * Decrypt 16 bytes, specified as four 32-bit words.
59020       *
59021       * @param {number} encrypted0 the first word to decrypt
59022       * @param {number} encrypted1 the second word to decrypt
59023       * @param {number} encrypted2 the third word to decrypt
59024       * @param {number} encrypted3 the fourth word to decrypt
59025       * @param {Int32Array} out the array to write the decrypted words
59026       * into
59027       * @param {number} offset the offset into the output array to start
59028       * writing results
59029       * @return {Array} The plaintext.
59030       */
59031
59032      decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
59033        const key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
59034
59035        let a = encrypted0 ^ key[0];
59036        let b = encrypted3 ^ key[1];
59037        let c = encrypted2 ^ key[2];
59038        let d = encrypted1 ^ key[3];
59039        let a2;
59040        let b2;
59041        let c2; // key.length === 2 ?
59042
59043        const nInnerRounds = key.length / 4 - 2;
59044        let i;
59045        let kIndex = 4;
59046        const table = this._tables[1]; // load up the tables
59047
59048        const table0 = table[0];
59049        const table1 = table[1];
59050        const table2 = table[2];
59051        const table3 = table[3];
59052        const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
59053
59054        for (i = 0; i < nInnerRounds; i++) {
59055          a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
59056          b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
59057          c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
59058          d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
59059          kIndex += 4;
59060          a = a2;
59061          b = b2;
59062          c = c2;
59063        } // Last round.
59064
59065        for (i = 0; i < 4; i++) {
59066          out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
59067          a2 = a;
59068          a = b;
59069          b = c;
59070          c = d;
59071          d = a2;
59072        }
59073      }
59074    }
59075    /**
59076     * @file async-stream.js
59077     */
59078
59079    /**
59080     * A wrapper around the Stream class to use setTimeout
59081     * and run stream "jobs" Asynchronously
59082     *
59083     * @class AsyncStream
59084     * @extends Stream
59085     */
59086
59087    class AsyncStream extends Stream {
59088      constructor() {
59089        super(Stream);
59090        this.jobs = [];
59091        this.delay = 1;
59092        this.timeout_ = null;
59093      }
59094      /**
59095       * process an async job
59096       *
59097       * @private
59098       */
59099
59100      processJob_() {
59101        this.jobs.shift()();
59102        if (this.jobs.length) {
59103          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
59104        } else {
59105          this.timeout_ = null;
59106        }
59107      }
59108      /**
59109       * push a job into the stream
59110       *
59111       * @param {Function} job the job to push into the stream
59112       */
59113
59114      push(job) {
59115        this.jobs.push(job);
59116        if (!this.timeout_) {
59117          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
59118        }
59119      }
59120    }
59121    /**
59122     * @file decrypter.js
59123     *
59124     * An asynchronous implementation of AES-128 CBC decryption with
59125     * PKCS#7 padding.
59126     */
59127
59128    /**
59129     * Convert network-order (big-endian) bytes into their little-endian
59130     * representation.
59131     */
59132
59133    const ntoh = function (word) {
59134      return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
59135    };
59136    /**
59137     * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
59138     *
59139     * @param {Uint8Array} encrypted the encrypted bytes
59140     * @param {Uint32Array} key the bytes of the decryption key
59141     * @param {Uint32Array} initVector the initialization vector (IV) to
59142     * use for the first round of CBC.
59143     * @return {Uint8Array} the decrypted bytes
59144     *
59145     * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
59146     * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
59147     * @see https://tools.ietf.org/html/rfc2315
59148     */
59149
59150    const decrypt = function (encrypted, key, initVector) {
59151      // word-level access to the encrypted bytes
59152      const encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
59153      const decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
59154
59155      const decrypted = new Uint8Array(encrypted.byteLength);
59156      const decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
59157      // decrypted data
59158
59159      let init0;
59160      let init1;
59161      let init2;
59162      let init3;
59163      let encrypted0;
59164      let encrypted1;
59165      let encrypted2;
59166      let encrypted3; // iteration variable
59167
59168      let wordIx; // pull out the words of the IV to ensure we don't modify the
59169      // passed-in reference and easier access
59170
59171      init0 = initVector[0];
59172      init1 = initVector[1];
59173      init2 = initVector[2];
59174      init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
59175      // to each decrypted block
59176
59177      for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
59178        // convert big-endian (network order) words into little-endian
59179        // (javascript order)
59180        encrypted0 = ntoh(encrypted32[wordIx]);
59181        encrypted1 = ntoh(encrypted32[wordIx + 1]);
59182        encrypted2 = ntoh(encrypted32[wordIx + 2]);
59183        encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
59184
59185        decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
59186        // plaintext
59187
59188        decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
59189        decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
59190        decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
59191        decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
59192
59193        init0 = encrypted0;
59194        init1 = encrypted1;
59195        init2 = encrypted2;
59196        init3 = encrypted3;
59197      }
59198      return decrypted;
59199    };
59200    /**
59201     * The `Decrypter` class that manages decryption of AES
59202     * data through `AsyncStream` objects and the `decrypt`
59203     * function
59204     *
59205     * @param {Uint8Array} encrypted the encrypted bytes
59206     * @param {Uint32Array} key the bytes of the decryption key
59207     * @param {Uint32Array} initVector the initialization vector (IV) to
59208     * @param {Function} done the function to run when done
59209     * @class Decrypter
59210     */
59211
59212    class Decrypter {
59213      constructor(encrypted, key, initVector, done) {
59214        const step = Decrypter.STEP;
59215        const encrypted32 = new Int32Array(encrypted.buffer);
59216        const decrypted = new Uint8Array(encrypted.byteLength);
59217        let i = 0;
59218        this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
59219
59220        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
59221        for (i = step; i < encrypted32.length; i += step) {
59222          initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
59223          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
59224        } // invoke the done() callback when everything is finished
59225
59226        this.asyncStream_.push(function () {
59227          // remove pkcs#7 padding from the decrypted bytes
59228          done(null, unpad(decrypted));
59229        });
59230      }
59231      /**
59232       * a getter for step the maximum number of bytes to process at one time
59233       *
59234       * @return {number} the value of step 32000
59235       */
59236
59237      static get STEP() {
59238        // 4 * 8000;
59239        return 32000;
59240      }
59241      /**
59242       * @private
59243       */
59244
59245      decryptChunk_(encrypted, key, initVector, decrypted) {
59246        return function () {
59247          const bytes = decrypt(encrypted, key, initVector);
59248          decrypted.set(bytes, encrypted.byteOffset);
59249        };
59250      }
59251    }
59252    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
59253    var win;
59254    if (typeof window !== "undefined") {
59255      win = window;
59256    } else if (typeof commonjsGlobal !== "undefined") {
59257      win = commonjsGlobal;
59258    } else if (typeof self !== "undefined") {
59259      win = self;
59260    } else {
59261      win = {};
59262    }
59263    var window_1 = win;
59264    var isArrayBufferView = function isArrayBufferView(obj) {
59265      if (ArrayBuffer.isView === 'function') {
59266        return ArrayBuffer.isView(obj);
59267      }
59268      return obj && obj.buffer instanceof ArrayBuffer;
59269    };
59270    var BigInt = window_1.BigInt || Number;
59271    [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
59272    (function () {
59273      var a = new Uint16Array([0xFFCC]);
59274      var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
59275      if (b[0] === 0xFF) {
59276        return 'big';
59277      }
59278      if (b[0] === 0xCC) {
59279        return 'little';
59280      }
59281      return 'unknown';
59282    })();
59283    /**
59284     * Creates an object for sending to a web worker modifying properties that are TypedArrays
59285     * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
59286     *
59287     * @param {Object} message
59288     *        Object of properties and values to send to the web worker
59289     * @return {Object}
59290     *         Modified message with TypedArray values expanded
59291     * @function createTransferableMessage
59292     */
59293
59294    const createTransferableMessage = function (message) {
59295      const transferable = {};
59296      Object.keys(message).forEach(key => {
59297        const value = message[key];
59298        if (isArrayBufferView(value)) {
59299          transferable[key] = {
59300            bytes: value.buffer,
59301            byteOffset: value.byteOffset,
59302            byteLength: value.byteLength
59303          };
59304        } else {
59305          transferable[key] = value;
59306        }
59307      });
59308      return transferable;
59309    };
59310    /* global self */
59311
59312    /**
59313     * Our web worker interface so that things can talk to aes-decrypter
59314     * that will be running in a web worker. the scope is passed to this by
59315     * webworkify.
59316     */
59317
59318    self.onmessage = function (event) {
59319      const data = event.data;
59320      const encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
59321      const key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
59322      const iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
59323      /* eslint-disable no-new, handle-callback-err */
59324
59325      new Decrypter(encrypted, key, iv, function (err, bytes) {
59326        self.postMessage(createTransferableMessage({
59327          source: data.source,
59328          decrypted: bytes
59329        }), [bytes.buffer]);
59330      });
59331      /* eslint-enable */
59332    };
59333  }));
59334
59335  var Decrypter = factory(workerCode);
59336  /* rollup-plugin-worker-factory end for worker!/home/runner/work/http-streaming
vendor: 21,837 bytes, lines 59336-59940
59336/http-streaming/src/decrypter-worker.js */
59337
59338  /**
59339   * Convert the properties of an HLS track into an audioTrackKind.
59340   *
59341   * @private
59342   */
59343
59344  const audioTrackKind_ = properties => {
59345    let kind = properties.default ? 'main' : 'alternative';
59346    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
59347      kind = 'main-desc';
59348    }
59349    return kind;
59350  };
59351  /**
59352   * Pause provided segment loader and playlist loader if active
59353   *
59354   * @param {SegmentLoader} segmentLoader
59355   *        SegmentLoader to pause
59356   * @param {Object} mediaType
59357   *        Active media type
59358   * @function stopLoaders
59359   */
59360
59361  const stopLoaders = (segmentLoader, mediaType) => {
59362    segmentLoader.abort();
59363    segmentLoader.pause();
59364    if (mediaType && mediaType.activePlaylistLoader) {
59365      mediaType.activePlaylistLoader.pause();
59366      mediaType.activePlaylistLoader = null;
59367    }
59368  };
59369  /**
59370   * Start loading provided segment loader and playlist loader
59371   *
59372   * @param {PlaylistLoader} playlistLoader
59373   *        PlaylistLoader to start loading
59374   * @param {Object} mediaType
59375   *        Active media type
59376   * @function startLoaders
59377   */
59378
59379  const startLoaders = (playlistLoader, mediaType) => {
59380    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
59381    // playlist loader
59382    mediaType.activePlaylistLoader = playlistLoader;
59383    playlistLoader.load();
59384  };
59385  /**
59386   * Returns a function to be called when the media group changes. It performs a
59387   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
59388   * change of group is merely a rendition switch of the same content at another encoding,
59389   * rather than a change of content, such as switching audio from English to Spanish.
59390   *
59391   * @param {string} type
59392   *        MediaGroup type
59393   * @param {Object} settings
59394   *        Object containing required information for media groups
59395   * @return {Function}
59396   *         Handler for a non-destructive resync of SegmentLoader when the active media
59397   *         group changes.
59398   * @function onGroupChanged
59399   */
59400
59401  const onGroupChanged = (type, settings) => () => {
59402    const {
59403      segmentLoaders: {
59404        [type]: segmentLoader,
59405        main: mainSegmentLoader
59406      },
59407      mediaTypes: {
59408        [type]: mediaType
59409      }
59410    } = settings;
59411    const activeTrack = mediaType.activeTrack();
59412    const activeGroup = mediaType.getActiveGroup();
59413    const previousActiveLoader = mediaType.activePlaylistLoader;
59414    const lastGroup = mediaType.lastGroup_; // the group did not change do nothing
59415
59416    if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
59417      return;
59418    }
59419    mediaType.lastGroup_ = activeGroup;
59420    mediaType.lastTrack_ = activeTrack;
59421    stopLoaders(segmentLoader, mediaType);
59422    if (!activeGroup || activeGroup.isMainPlaylist) {
59423      // there is no group active or active group is a main playlist and won't change
59424      return;
59425    }
59426    if (!activeGroup.playlistLoader) {
59427      if (previousActiveLoader) {
59428        // The previous group had a playlist loader but the new active group does not
59429        // this means we are switching from demuxed to muxed audio. In this case we want to
59430        // do a destructive reset of the main segment loader and not restart the audio
59431        // loaders.
59432        mainSegmentLoader.resetEverything();
59433      }
59434      return;
59435    } // Non-destructive resync
59436
59437    segmentLoader.resyncLoader();
59438    startLoaders(activeGroup.playlistLoader, mediaType);
59439  };
59440  const onGroupChanging = (type, settings) => () => {
59441    const {
59442      segmentLoaders: {
59443        [type]: segmentLoader
59444      },
59445      mediaTypes: {
59446        [type]: mediaType
59447      }
59448    } = settings;
59449    mediaType.lastGroup_ = null;
59450    segmentLoader.abort();
59451    segmentLoader.pause();
59452  };
59453  /**
59454   * Returns a function to be called when the media track changes. It performs a
59455   * destructive reset of the SegmentLoader to ensure we start loading as close to
59456   * currentTime as possible.
59457   *
59458   * @param {string} type
59459   *        MediaGroup type
59460   * @param {Object} settings
59461   *        Object containing required information for media groups
59462   * @return {Function}
59463   *         Handler for a destructive reset of SegmentLoader when the active media
59464   *         track changes.
59465   * @function onTrackChanged
59466   */
59467
59468  const onTrackChanged = (type, settings) => () => {
59469    const {
59470      mainPlaylistLoader,
59471      segmentLoaders: {
59472        [type]: segmentLoader,
59473        main: mainSegmentLoader
59474      },
59475      mediaTypes: {
59476        [type]: mediaType
59477      }
59478    } = settings;
59479    const activeTrack = mediaType.activeTrack();
59480    const activeGroup = mediaType.getActiveGroup();
59481    const previousActiveLoader = mediaType.activePlaylistLoader;
59482    const lastTrack = mediaType.lastTrack_; // track did not change, do nothing
59483
59484    if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
59485      return;
59486    }
59487    mediaType.lastGroup_ = activeGroup;
59488    mediaType.lastTrack_ = activeTrack;
59489    stopLoaders(segmentLoader, mediaType);
59490    if (!activeGroup) {
59491      // there is no group active so we do not want to restart loaders
59492      return;
59493    }
59494    if (activeGroup.isMainPlaylist) {
59495      // track did not change, do nothing
59496      if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
59497        return;
59498      }
59499      const pc = settings.vhs.playlistController_;
59500      const newPlaylist = pc.selectPlaylist(); // media will not change do nothing
59501
59502      if (pc.media() === newPlaylist) {
59503        return;
59504      }
59505      mediaType.logger_(`track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`);
59506      mainPlaylistLoader.pause();
59507      mainSegmentLoader.resetEverything();
59508      pc.fastQualityChange_(newPlaylist);
59509      return;
59510    }
59511    if (type === 'AUDIO') {
59512      if (!activeGroup.playlistLoader) {
59513        // when switching from demuxed audio/video to muxed audio/video (noted by no
59514        // playlist loader for the audio group), we want to do a destructive reset of the
59515        // main segment loader and not restart the audio loaders
59516        mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
59517        // it should be stopped
59518
59519        mainSegmentLoader.resetEverything();
59520        return;
59521      } // although the segment loader is an audio segment loader, call the setAudio
59522      // function to ensure it is prepared to re-append the init segment (or handle other
59523      // config changes)
59524
59525      segmentLoader.setAudio(true);
59526      mainSegmentLoader.setAudio(false);
59527    }
59528    if (previousActiveLoader === activeGroup.playlistLoader) {
59529      // Nothing has actually changed. This can happen because track change events can fire
59530      // multiple times for a "single" change. One for enabling the new active track, and
59531      // one for disabling the track that was active
59532      startLoaders(activeGroup.playlistLoader, mediaType);
59533      return;
59534    }
59535    if (segmentLoader.track) {
59536      // For WebVTT, set the new text track in the segmentloader
59537      segmentLoader.track(activeTrack);
59538    } // destructive reset
59539
59540    segmentLoader.resetEverything();
59541    startLoaders(activeGroup.playlistLoader, mediaType);
59542  };
59543  const onError = {
59544    /**
59545     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
59546     * an error.
59547     *
59548     * @param {string} type
59549     *        MediaGroup type
59550     * @param {Object} settings
59551     *        Object containing required information for media groups
59552     * @return {Function}
59553     *         Error handler. Logs warning (or error if the playlist is excluded) to
59554     *         console and switches back to default audio track.
59555     * @function onError.AUDIO
59556     */
59557    AUDIO: (type, settings) => () => {
59558      const {
59559        mediaTypes: {
59560          [type]: mediaType
59561        },
59562        excludePlaylist
59563      } = settings; // switch back to default audio track
59564
59565      const activeTrack = mediaType.activeTrack();
59566      const activeGroup = mediaType.activeGroup();
59567      const id = (activeGroup.filter(group => group.default)[0] || activeGroup[0]).id;
59568      const defaultTrack = mediaType.tracks[id];
59569      if (activeTrack === defaultTrack) {
59570        // Default track encountered an error. All we can do now is exclude the current
59571        // rendition and hope another will switch audio groups
59572        excludePlaylist({
59573          error: {
59574            message: 'Problem encountered loading the default audio track.'
59575          }
59576        });
59577        return;
59578      }
59579      videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
59580      for (const trackId in mediaType.tracks) {
59581        mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
59582      }
59583      mediaType.onTrackChanged();
59584    },
59585    /**
59586     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
59587     * an error.
59588     *
59589     * @param {string} type
59590     *        MediaGroup type
59591     * @param {Object} settings
59592     *        Object containing required information for media groups
59593     * @return {Function}
59594     *         Error handler. Logs warning to console and disables the active subtitle track
59595     * @function onError.SUBTITLES
59596     */
59597    SUBTITLES: (type, settings) => () => {
59598      const {
59599        mediaTypes: {
59600          [type]: mediaType
59601        }
59602      } = settings;
59603      videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
59604      const track = mediaType.activeTrack();
59605      if (track) {
59606        track.mode = 'disabled';
59607      }
59608      mediaType.onTrackChanged();
59609    }
59610  };
59611  const setupListeners = {
59612    /**
59613     * Setup event listeners for audio playlist loader
59614     *
59615     * @param {string} type
59616     *        MediaGroup type
59617     * @param {PlaylistLoader|null} playlistLoader
59618     *        PlaylistLoader to register listeners on
59619     * @param {Object} settings
59620     *        Object containing required information for media groups
59621     * @function setupListeners.AUDIO
59622     */
59623    AUDIO: (type, playlistLoader, settings) => {
59624      if (!playlistLoader) {
59625        // no playlist loader means audio will be muxed with the video
59626        return;
59627      }
59628      const {
59629        tech,
59630        requestOptions,
59631        segmentLoaders: {
59632          [type]: segmentLoader
59633        }
59634      } = settings;
59635      playlistLoader.on('loadedmetadata', () => {
59636        const media = playlistLoader.media();
59637        segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
59638        // permits, start downloading segments
59639
59640        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
59641          segmentLoader.load();
59642        }
59643      });
59644      playlistLoader.on('loadedplaylist', () => {
59645        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
59646
59647        if (!tech.paused()) {
59648          segmentLoader.load();
59649        }
59650      });
59651      playlistLoader.on('error', onError[type](type, settings));
59652    },
59653    /**
59654     * Setup event listeners for subtitle playlist loader
59655     *
59656     * @param {string} type
59657     *        MediaGroup type
59658     * @param {PlaylistLoader|null} playlistLoader
59659     *        PlaylistLoader to register listeners on
59660     * @param {Object} settings
59661     *        Object containing required information for media groups
59662     * @function setupListeners.SUBTITLES
59663     */
59664    SUBTITLES: (type, playlistLoader, settings) => {
59665      const {
59666        tech,
59667        requestOptions,
59668        segmentLoaders: {
59669          [type]: segmentLoader
59670        },
59671        mediaTypes: {
59672          [type]: mediaType
59673        }
59674      } = settings;
59675      playlistLoader.on('loadedmetadata', () => {
59676        const media = playlistLoader.media();
59677        segmentLoader.playlist(media, requestOptions);
59678        segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
59679        // permits, start downloading segments
59680
59681        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
59682          segmentLoader.load();
59683        }
59684      });
59685      playlistLoader.on('loadedplaylist', () => {
59686        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
59687
59688        if (!tech.paused()) {
59689          segmentLoader.load();
59690        }
59691      });
59692      playlistLoader.on('error', onError[type](type, settings));
59693    }
59694  };
59695  const initialize = {
59696    /**
59697     * Setup PlaylistLoaders and AudioTracks for the audio groups
59698     *
59699     * @param {string} type
59700     *        MediaGroup type
59701     * @param {Object} settings
59702     *        Object containing required information for media groups
59703     * @function initialize.AUDIO
59704     */
59705    'AUDIO': (type, settings) => {
59706      const {
59707        vhs,
59708        sourceType,
59709        segmentLoaders: {
59710          [type]: segmentLoader
59711        },
59712        requestOptions,
59713        main: {
59714          mediaGroups
59715        },
59716        mediaTypes: {
59717          [type]: {
59718            groups,
59719            tracks,
59720            logger_
59721          }
59722        },
59723        mainPlaylistLoader
59724      } = settings;
59725      const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main); // force a default if we have none
59726
59727      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
59728        mediaGroups[type] = {
59729          main: {
59730            default: {
59731              default: true
59732            }
59733          }
59734        };
59735        if (audioOnlyMain) {
59736          mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
59737        }
59738      }
59739      for (const groupId in mediaGroups[type]) {
59740        if (!groups[groupId]) {
59741          groups[groupId] = [];
59742        }
59743        for (const variantLabel in mediaGroups[type][groupId]) {
59744          let properties = mediaGroups[type][groupId][variantLabel];
59745          let playlistLoader;
59746          if (audioOnlyMain) {
59747            logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
59748            properties.isMainPlaylist = true;
59749            playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
59750            // use the resolved media playlist object
59751          } else if (sourceType === 'vhs-json' && properties.playlists) {
59752            playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
59753          } else if (properties.resolvedUri) {
59754            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
59755            // should we even have properties.playlists in this check.
59756          } else if (properties.playlists && sourceType === 'dash') {
59757            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
59758          } else {
59759            // no resolvedUri means the audio is muxed with the video when using this
59760            // audio track
59761            playlistLoader = null;
59762          }
59763          properties = merge({
59764            id: variantLabel,
59765            playlistLoader
59766          }, properties);
59767          setupListeners[type](type, properties.playlistLoader, settings);
59768          groups[groupId].push(properties);
59769          if (typeof tracks[variantLabel] === 'undefined') {
59770            const track = new videojs.AudioTrack({
59771              id: variantLabel,
59772              kind: audioTrackKind_(properties),
59773              enabled: false,
59774              language: properties.language,
59775              default: properties.default,
59776              label: variantLabel
59777            });
59778            tracks[variantLabel] = track;
59779          }
59780        }
59781      } // setup single error event handler for the segment loader
59782
59783      segmentLoader.on('error', onError[type](type, settings));
59784    },
59785    /**
59786     * Setup PlaylistLoaders and TextTracks for the subtitle groups
59787     *
59788     * @param {string} type
59789     *        MediaGroup type
59790     * @param {Object} settings
59791     *        Object containing required information for media groups
59792     * @function initialize.SUBTITLES
59793     */
59794    'SUBTITLES': (type, settings) => {
59795      const {
59796        tech,
59797        vhs,
59798        sourceType,
59799        segmentLoaders: {
59800          [type]: segmentLoader
59801        },
59802        requestOptions,
59803        main: {
59804          mediaGroups
59805        },
59806        mediaTypes: {
59807          [type]: {
59808            groups,
59809            tracks
59810          }
59811        },
59812        mainPlaylistLoader
59813      } = settings;
59814      for (const groupId in mediaGroups[type]) {
59815        if (!groups[groupId]) {
59816          groups[groupId] = [];
59817        }
59818        for (const variantLabel in mediaGroups[type][groupId]) {
59819          if (!vhs.options_.useForcedSubtitles && mediaGroups[type][groupId][variantLabel].forced) {
59820            // Subtitle playlists with the forced attribute are not selectable in Safari.
59821            // According to Apple's HLS Authoring Specification:
59822            //   If content has forced subtitles and regular subtitles in a given language,
59823            //   the regular subtitles track in that language MUST contain both the forced
59824            //   subtitles and the regular subtitles for that language.
59825            // Because of this requirement and that Safari does not add forced subtitles,
59826            // forced subtitles are skipped here to maintain consistent experience across
59827            // all platforms
59828            continue;
59829          }
59830          let properties = mediaGroups[type][groupId][variantLabel];
59831          let playlistLoader;
59832          if (sourceType === 'hls') {
59833            playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
59834          } else if (sourceType === 'dash') {
59835            const playlists = properties.playlists.filter(p => p.excludeUntil !== Infinity);
59836            if (!playlists.length) {
59837              return;
59838            }
59839            playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
59840          } else if (sourceType === 'vhs-json') {
59841            playlistLoader = new PlaylistLoader(
59842            // if the vhs-json object included the media playlist, use the media playlist
59843            // as provided, otherwise use the resolved URI to load the playlist
59844            properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
59845          }
59846          properties = merge({
59847            id: variantLabel,
59848            playlistLoader
59849          }, properties);
59850          setupListeners[type](type, properties.playlistLoader, settings);
59851          groups[groupId].push(properties);
59852          if (typeof tracks[variantLabel] === 'undefined') {
59853            const track = tech.addRemoteTextTrack({
59854              id: variantLabel,
59855              kind: 'subtitles',
59856              default: properties.default && properties.autoselect,
59857              language: properties.language,
59858              label: variantLabel
59859            }, false).track;
59860            tracks[variantLabel] = track;
59861          }
59862        }
59863      } // setup single error event handler for the segment loader
59864
59865      segmentLoader.on('error', onError[type](type, settings));
59866    },
59867    /**
59868     * Setup TextTracks for the closed-caption groups
59869     *
59870     * @param {String} type
59871     *        MediaGroup type
59872     * @param {Object} settings
59873     *        Object containing required information for media groups
59874     * @function initialize['CLOSED-CAPTIONS']
59875     */
59876    'CLOSED-CAPTIONS': (type, settings) => {
59877      const {
59878        tech,
59879        main: {
59880          mediaGroups
59881        },
59882        mediaTypes: {
59883          [type]: {
59884            groups,
59885            tracks
59886          }
59887        }
59888      } = settings;
59889      for (const groupId in mediaGroups[type]) {
59890        if (!groups[groupId]) {
59891          groups[groupId] = [];
59892        }
59893        for (const variantLabel in mediaGroups[type][groupId]) {
59894          const properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
59895
59896          if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
59897            continue;
59898          }
59899          const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
59900          let newProps = {
59901            label: variantLabel,
59902            language: properties.language,
59903            instreamId: properties.instreamId,
59904            default: properties.default && properties.autoselect
59905          };
59906          if (captionServices[newProps.instreamId]) {
59907            newProps = merge(newProps, captionServices[newProps.instreamId]);
59908          }
59909          if (newProps.default === undefined) {
59910            delete newProps.default;
59911          } // No PlaylistLoader is required for Closed-Captions because the captions are
59912          // embedded within the video stream
59913
59914          groups[groupId].push(merge({
59915            id: variantLabel
59916          }, properties));
59917          if (typeof tracks[variantLabel] === 'undefined') {
59918            const track = tech.addRemoteTextTrack({
59919              id: newProps.instreamId,
59920              kind: 'captions',
59921              default: newProps.default,
59922              language: newProps.language,
59923              label: newProps.label
59924            }, false).track;
59925            tracks[variantLabel] = track;
59926          }
59927        }
59928      }
59929    }
59930  };
59931  const groupMatch = (list, media) => {
59932    for (let i = 0; i < list.length; i++) {
59933      if (playlistMatch(media, list[i])) {
59934        return true;
59935      }
59936      if (list[i].playlists && groupMatch(list[i].playlists, media)) {
59937        return true;
59938      }
59939    }
59940    return false;
vendor: 14,796 bytes, lines 59941-60339
59941  };
59942  /**
59943   * Returns a function used to get the active group of the provided type
59944   *
59945   * @param {string} type
59946   *        MediaGroup type
59947   * @param {Object} settings
59948   *        Object containing required information for media groups
59949   * @return {Function}
59950   *         Function that returns the active media group for the provided type. Takes an
59951   *         optional parameter {TextTrack} track. If no track is provided, a list of all
59952   *         variants in the group, otherwise the variant corresponding to the provided
59953   *         track is returned.
59954   * @function activeGroup
59955   */
59956
59957  const activeGroup = (type, settings) => track => {
59958    const {
59959      mainPlaylistLoader,
59960      mediaTypes: {
59961        [type]: {
59962          groups
59963        }
59964      }
59965    } = settings;
59966    const media = mainPlaylistLoader.media();
59967    if (!media) {
59968      return null;
59969    }
59970    let variants = null; // set to variants to main media active group
59971
59972    if (media.attributes[type]) {
59973      variants = groups[media.attributes[type]];
59974    }
59975    const groupKeys = Object.keys(groups);
59976    if (!variants) {
59977      // find the mainPlaylistLoader media
59978      // that is in a media group if we are dealing
59979      // with audio only
59980      if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
59981        for (let i = 0; i < groupKeys.length; i++) {
59982          const groupPropertyList = groups[groupKeys[i]];
59983          if (groupMatch(groupPropertyList, media)) {
59984            variants = groupPropertyList;
59985            break;
59986          }
59987        } // use the main group if it exists
59988      } else if (groups.main) {
59989        variants = groups.main; // only one group, use that one
59990      } else if (groupKeys.length === 1) {
59991        variants = groups[groupKeys[0]];
59992      }
59993    }
59994    if (typeof track === 'undefined') {
59995      return variants;
59996    }
59997    if (track === null || !variants) {
59998      // An active track was specified so a corresponding group is expected. track === null
59999      // means no track is currently active so there is no corresponding group
60000      return null;
60001    }
60002    return variants.filter(props => props.id === track.id)[0] || null;
60003  };
60004  const activeTrack = {
60005    /**
60006     * Returns a function used to get the active track of type provided
60007     *
60008     * @param {string} type
60009     *        MediaGroup type
60010     * @param {Object} settings
60011     *        Object containing required information for media groups
60012     * @return {Function}
60013     *         Function that returns the active media track for the provided type. Returns
60014     *         null if no track is active
60015     * @function activeTrack.AUDIO
60016     */
60017    AUDIO: (type, settings) => () => {
60018      const {
60019        mediaTypes: {
60020          [type]: {
60021            tracks
60022          }
60023        }
60024      } = settings;
60025      for (const id in tracks) {
60026        if (tracks[id].enabled) {
60027          return tracks[id];
60028        }
60029      }
60030      return null;
60031    },
60032    /**
60033     * Returns a function used to get the active track of type provided
60034     *
60035     * @param {string} type
60036     *        MediaGroup type
60037     * @param {Object} settings
60038     *        Object containing required information for media groups
60039     * @return {Function}
60040     *         Function that returns the active media track for the provided type. Returns
60041     *         null if no track is active
60042     * @function activeTrack.SUBTITLES
60043     */
60044    SUBTITLES: (type, settings) => () => {
60045      const {
60046        mediaTypes: {
60047          [type]: {
60048            tracks
60049          }
60050        }
60051      } = settings;
60052      for (const id in tracks) {
60053        if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
60054          return tracks[id];
60055        }
60056      }
60057      return null;
60058    }
60059  };
60060  const getActiveGroup = (type, {
60061    mediaTypes
60062  }) => () => {
60063    const activeTrack_ = mediaTypes[type].activeTrack();
60064    if (!activeTrack_) {
60065      return null;
60066    }
60067    return mediaTypes[type].activeGroup(activeTrack_);
60068  };
60069  /**
60070   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
60071   * Closed-Captions) specified in the main manifest.
60072   *
60073   * @param {Object} settings
60074   *        Object containing required information for setting up the media groups
60075   * @param {Tech} settings.tech
60076   *        The tech of the player
60077   * @param {Object} settings.requestOptions
60078   *        XHR request options used by the segment loaders
60079   * @param {PlaylistLoader} settings.mainPlaylistLoader
60080   *        PlaylistLoader for the main source
60081   * @param {VhsHandler} settings.vhs
60082   *        VHS SourceHandler
60083   * @param {Object} settings.main
60084   *        The parsed main manifest
60085   * @param {Object} settings.mediaTypes
60086   *        Object to store the loaders, tracks, and utility methods for each media type
60087   * @param {Function} settings.excludePlaylist
60088   *        Excludes the current rendition and forces a rendition switch.
60089   * @function setupMediaGroups
60090   */
60091
60092  const setupMediaGroups = settings => {
60093    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
60094      initialize[type](type, settings);
60095    });
60096    const {
60097      mediaTypes,
60098      mainPlaylistLoader,
60099      tech,
60100      vhs,
60101      segmentLoaders: {
60102        ['AUDIO']: audioSegmentLoader,
60103        main: mainSegmentLoader
60104      }
60105    } = settings; // setup active group and track getters and change event handlers
60106
60107    ['AUDIO', 'SUBTITLES'].forEach(type => {
60108      mediaTypes[type].activeGroup = activeGroup(type, settings);
60109      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
60110      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
60111      mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
60112      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
60113      mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
60114    }); // DO NOT enable the default subtitle or caption track.
60115    // DO enable the default audio track
60116
60117    const audioGroup = mediaTypes.AUDIO.activeGroup();
60118    if (audioGroup) {
60119      const groupId = (audioGroup.filter(group => group.default)[0] || audioGroup[0]).id;
60120      mediaTypes.AUDIO.tracks[groupId].enabled = true;
60121      mediaTypes.AUDIO.onGroupChanged();
60122      mediaTypes.AUDIO.onTrackChanged();
60123      const activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
60124      // track is changed, but needs to be handled here since the track may not be considered
60125      // changed on the first call to onTrackChanged
60126
60127      if (!activeAudioGroup.playlistLoader) {
60128        // either audio is muxed with video or the stream is audio only
60129        mainSegmentLoader.setAudio(true);
60130      } else {
60131        // audio is demuxed
60132        mainSegmentLoader.setAudio(false);
60133        audioSegmentLoader.setAudio(true);
60134      }
60135    }
60136    mainPlaylistLoader.on('mediachange', () => {
60137      ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged());
60138    });
60139    mainPlaylistLoader.on('mediachanging', () => {
60140      ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging());
60141    }); // custom audio track change event handler for usage event
60142
60143    const onAudioTrackChanged = () => {
60144      mediaTypes.AUDIO.onTrackChanged();
60145      tech.trigger({
60146        type: 'usage',
60147        name: 'vhs-audio-change'
60148      });
60149    };
60150    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
60151    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
60152    vhs.on('dispose', () => {
60153      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
60154      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
60155    }); // clear existing audio tracks and add the ones we just created
60156
60157    tech.clearTracks('audio');
60158    for (const id in mediaTypes.AUDIO.tracks) {
60159      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
60160    }
60161  };
60162  /**
60163   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
60164   * media type
60165   *
60166   * @return {Object}
60167   *         Object to store the loaders, tracks, and utility methods for each media type
60168   * @function createMediaTypes
60169   */
60170
60171  const createMediaTypes = () => {
60172    const mediaTypes = {};
60173    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
60174      mediaTypes[type] = {
60175        groups: {},
60176        tracks: {},
60177        activePlaylistLoader: null,
60178        activeGroup: noop,
60179        activeTrack: noop,
60180        getActiveGroup: noop,
60181        onGroupChanged: noop,
60182        onTrackChanged: noop,
60183        lastTrack_: null,
60184        logger_: logger(`MediaGroups[${type}]`)
60185      };
60186    });
60187    return mediaTypes;
60188  };
60189
60190  /**
60191   * A utility class for setting properties and maintaining the state of the content steering manifest.
60192   *
60193   * Content Steering manifest format:
60194   * VERSION: number (required) currently only version 1 is supported.
60195   * TTL: number in seconds (optional) until the next content steering manifest reload.
60196   * RELOAD-URI: string (optional) uri to fetch the next content steering manifest.
60197   * SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY a non empty array of unique string values.
60198   * PATHWAY-CLONES: array (optional) (HLS only) pathway clone objects to copy from other playlists.
60199   */
60200
60201  class SteeringManifest {
60202    constructor() {
60203      this.priority_ = [];
60204      this.pathwayClones_ = new Map();
60205    }
60206    set version(number) {
60207      // Only version 1 is currently supported for both DASH and HLS.
60208      if (number === 1) {
60209        this.version_ = number;
60210      }
60211    }
60212    set ttl(seconds) {
60213      // TTL = time-to-live, default = 300 seconds.
60214      this.ttl_ = seconds || 300;
60215    }
60216    set reloadUri(uri) {
60217      if (uri) {
60218        // reload URI can be relative to the previous reloadUri.
60219        this.reloadUri_ = resolveUrl(this.reloadUri_, uri);
60220      }
60221    }
60222    set priority(array) {
60223      // priority must be non-empty and unique values.
60224      if (array && array.length) {
60225        this.priority_ = array;
60226      }
60227    }
60228    set pathwayClones(array) {
60229      // pathwayClones must be non-empty.
60230      if (array && array.length) {
60231        this.pathwayClones_ = new Map(array.map(clone => [clone.ID, clone]));
60232      }
60233    }
60234    get version() {
60235      return this.version_;
60236    }
60237    get ttl() {
60238      return this.ttl_;
60239    }
60240    get reloadUri() {
60241      return this.reloadUri_;
60242    }
60243    get priority() {
60244      return this.priority_;
60245    }
60246    get pathwayClones() {
60247      return this.pathwayClones_;
60248    }
60249  }
60250  /**
60251   * This class represents a content steering manifest and associated state. See both HLS and DASH specifications.
60252   * HLS: https://developer.apple.com/streaming/HLSContentSteeringSpecification.pdf and
60253   * https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/ section 4.4.6.6.
60254   * DASH: https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
60255   *
60256   * @param {function} xhr for making a network request from the browser.
60257   * @param {function} bandwidth for fetching the current bandwidth from the main segment loader.
60258   */
60259
60260  class ContentSteeringController extends videojs.EventTarget {
60261    constructor(xhr, bandwidth) {
60262      super();
60263      this.currentPathway = null;
60264      this.defaultPathway = null;
60265      this.queryBeforeStart = false;
60266      this.availablePathways_ = new Set();
60267      this.steeringManifest = new SteeringManifest();
60268      this.proxyServerUrl_ = null;
60269      this.manifestType_ = null;
60270      this.ttlTimeout_ = null;
60271      this.request_ = null;
60272      this.currentPathwayClones = new Map();
60273      this.nextPathwayClones = new Map();
60274      this.excludedSteeringManifestURLs = new Set();
60275      this.logger_ = logger('Content Steering');
60276      this.xhr_ = xhr;
60277      this.getBandwidth_ = bandwidth;
60278    }
60279    /**
60280     * Assigns the content steering tag properties to the steering controller
60281     *
60282     * @param {string} baseUrl the baseURL from the main manifest for resolving the steering manifest url
60283     * @param {Object} steeringTag the content steering tag from the main manifest
60284     */
60285
60286    assignTagProperties(baseUrl, steeringTag) {
60287      this.manifestType_ = steeringTag.serverUri ? 'HLS' : 'DASH'; // serverUri is HLS serverURL is DASH
60288
60289      const steeringUri = steeringTag.serverUri || steeringTag.serverURL;
60290      if (!steeringUri) {
60291        this.logger_(`steering manifest URL is ${steeringUri}, cannot request steering manifest.`);
60292        this.trigger('error');
60293        return;
60294      } // Content steering manifests can be encoded as a data URI. We can decode, parse and return early if that's the case.
60295
60296      if (steeringUri.startsWith('data:')) {
60297        this.decodeDataUriManifest_(steeringUri.substring(steeringUri.indexOf(',') + 1));
60298        return;
60299      } // reloadUri is the resolution of the main manifest URL and steering URL.
60300
60301      this.steeringManifest.reloadUri = resolveUrl(baseUrl, steeringUri); // pathwayId is HLS defaultServiceLocation is DASH
60302
60303      this.defaultPathway = steeringTag.pathwayId || steeringTag.defaultServiceLocation; // currently only DASH supports the following properties on <ContentSteering> tags.
60304
60305      this.queryBeforeStart = steeringTag.queryBeforeStart;
60306      this.proxyServerUrl_ = steeringTag.proxyServerURL; // trigger a steering event if we have a pathway from the content steering tag.
60307      // this tells VHS which segment pathway to start with.
60308      // If queryBeforeStart is true we need to wait for the steering manifest response.
60309
60310      if (this.defaultPathway && !this.queryBeforeStart) {
60311        this.trigger('content-steering');
60312      }
60313    }
60314    /**
60315     * Requests the content steering manifest and parse the response. This should only be called after
60316     * assignTagProperties was called with a content steering tag.
60317     *
60318     * @param {string} initialUri The optional uri to make the request with.
60319     *    If set, the request should be made with exactly what is passed in this variable.
60320     *    This scenario should only happen once on initalization.
60321     */
60322
60323    requestSteeringManifest(initial) {
60324      const reloadUri = this.steeringManifest.reloadUri;
60325      if (!reloadUri) {
60326        return;
60327      } // We currently don't support passing MPD query parameters directly to the content steering URL as this requires
60328      // ExtUrlQueryInfo tag support. See the DASH content steering spec section 8.1.
60329      // This request URI accounts for manifest URIs that have been excluded.
60330
60331      const uri = initial ? reloadUri : this.getRequestURI(reloadUri); // If there are no valid manifest URIs, we should stop content steering.
60332
60333      if (!uri) {
60334        this.logger_('No valid content steering manifest URIs. Stopping content steering.');
60335        this.trigger('error');
60336        this.dispose();
60337        return;
60338      }
60339      
60339this.request_ = this.xhr_({
60340        uri
60341      }, (error, errorInfo) => {
60342        if (error) {
60343          // If the client receives HTTP 410 Gone in response to a manifest request,
60344          // it MUST NOT issue another request for that URI for the remainder of the
60345          // playback session. It MAY continue to use the most-recently obtained set
60346          // of Pathways.
60347          if (errorInfo.status === 410) {
60348            this.logger_(`manifest request 410 ${error}.`);
60349            this.logger_(`There will be no more content steering requests to ${uri} this session.`);
60350            this.excludedSteeringManifestURLs.add(uri);
60351            return;
60352          } // If the client receives HTTP 429 Too Many Requests with a Retry-After
60353          // header in response to a manifest request, it SHOULD wait until the time
60354          // specified by the Retry-After header to reissue the request.
60355
60356          if (errorInfo.status === 429) {
60357            const retrySeconds = errorInfo.responseHeaders['retry-after'];
60358            this.logger_(`manifest request 429 ${error}.`);
60359            this.logger_(`content steering will retry in ${retrySeconds} seconds.`);
60360            this.startTTLTimeout_(parseInt(retrySeconds, 10));
60361            return;
60362          } // If the Steering Manifest cannot be loaded and parsed correctly, the
60363          // client SHOULD continue to use the previous values and attempt to reload
60364          // it after waiting for the previously-specified TTL (or 5 minutes if
60365          // none).
60366
60367          this.logger_(`manifest failed to load ${error}.`);
60368          this.startTTLTimeout_();
60369          return;
60370        }
60371        const steeringManifestJson = JSON.parse(this.request_.responseText);
60372        this.assignSteeringProperties_(steeringManifestJson
vendor: 8,265 bytes, lines 60372-60598
60372);
60373        this.startTTLTimeout_();
60374      });
60375    }
60376    /**
60377     * Set the proxy server URL and add the steering manifest url as a URI encoded parameter.
60378     *
60379     * @param {string} steeringUrl the steering manifest url
60380     * @return the steering manifest url to a proxy server with all parameters set
60381     */
60382
60383    setProxyServerUrl_(steeringUrl) {
60384      const steeringUrlObject = new window.URL(steeringUrl);
60385      const proxyServerUrlObject = new window.URL(this.proxyServerUrl_);
60386      proxyServerUrlObject.searchParams.set('url', encodeURI(steeringUrlObject.toString()));
60387      return this.setSteeringParams_(proxyServerUrlObject.toString());
60388    }
60389    /**
60390     * Decodes and parses the data uri encoded steering manifest
60391     *
60392     * @param {string} dataUri the data uri to be decoded and parsed.
60393     */
60394
60395    decodeDataUriManifest_(dataUri) {
60396      const steeringManifestJson = JSON.parse(window.atob(dataUri));
60397      this.assignSteeringProperties_(steeringManifestJson);
60398    }
60399    /**
60400     * Set the HLS or DASH content steering manifest request query parameters. For example:
60401     * _HLS_pathway="<CURRENT-PATHWAY-ID>" and _HLS_throughput=<THROUGHPUT>
60402     * _DASH_pathway and _DASH_throughput
60403     *
60404     * @param {string} uri to add content steering server parameters to.
60405     * @return a new uri as a string with the added steering query parameters.
60406     */
60407
60408    setSteeringParams_(url) {
60409      const urlObject = new window.URL(url);
60410      const path = this.getPathway();
60411      const networkThroughput = this.getBandwidth_();
60412      if (path) {
60413        const pathwayKey = `_${this.manifestType_}_pathway`;
60414        urlObject.searchParams.set(pathwayKey, path);
60415      }
60416      if (networkThroughput) {
60417        const throughputKey = `_${this.manifestType_}_throughput`;
60418        urlObject.searchParams.set(throughputKey, networkThroughput);
60419      }
60420      return urlObject.toString();
60421    }
60422    /**
60423     * Assigns the current steering manifest properties and to the SteeringManifest object
60424     *
60425     * @param {Object} steeringJson the raw JSON steering manifest
60426     */
60427
60428    assignSteeringProperties_(steeringJson) {
60429      this.steeringManifest.version = steeringJson.VERSION;
60430      if (!this.steeringManifest.version) {
60431        this.logger_(`manifest version is ${steeringJson.VERSION}, which is not supported.`);
60432        this.trigger('error');
60433        return;
60434      }
60435      this.steeringManifest.ttl = steeringJson.TTL;
60436      this.steeringManifest.reloadUri = steeringJson['RELOAD-URI']; // HLS = PATHWAY-PRIORITY required. DASH = SERVICE-LOCATION-PRIORITY optional
60437
60438      this.steeringManifest.priority = steeringJson['PATHWAY-PRIORITY'] || steeringJson['SERVICE-LOCATION-PRIORITY']; // Pathway clones to be created/updated in HLS.
60439      // See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
60440
60441      this.steeringManifest.pathwayClones = steeringJson['PATHWAY-CLONES'];
60442      this.nextPathwayClones = this.steeringManifest.pathwayClones; // 1. apply first pathway from the array.
60443      // 2. if first pathway doesn't exist in manifest, try next pathway.
60444      //    a. if all pathways are exhausted, ignore the steering manifest priority.
60445      // 3. if segments fail from an established pathway, try all variants/renditions, then exclude the failed pathway.
60446      //    a. exclude a pathway for a minimum of the last TTL duration. Meaning, from the next steering response,
60447      //       the excluded pathway will be ignored.
60448      //       See excludePathway usage in excludePlaylist().
60449      // If there are no available pathways, we need to stop content steering.
60450
60451      if (!this.availablePathways_.size) {
60452        this.logger_('There are no available pathways for content steering. Ending content steering.');
60453        this.trigger('error');
60454        this.dispose();
60455      }
60456      const chooseNextPathway = pathwaysByPriority => {
60457        for (const path of pathwaysByPriority) {
60458          if (this.availablePathways_.has(path)) {
60459            return path;
60460          }
60461        } // If no pathway matches, ignore the manifest and choose the first available.
60462
60463        return [...this.availablePathways_][0];
60464      };
60465      const nextPathway = chooseNextPathway(this.steeringManifest.priority);
60466      if (this.currentPathway !== nextPathway) {
60467        this.currentPathway = nextPathway;
60468        this.trigger('content-steering');
60469      }
60470    }
60471    /**
60472     * Returns the pathway to use for steering decisions
60473     *
60474     * @return {string} returns the current pathway or the default
60475     */
60476
60477    getPathway() {
60478      return this.currentPathway || this.defaultPathway;
60479    }
60480    /**
60481     * Chooses the manifest request URI based on proxy URIs and server URLs.
60482     * Also accounts for exclusion on certain manifest URIs.
60483     *
60484     * @param {string} reloadUri the base uri before parameters
60485     *
60486     * @return {string} the final URI for the request to the manifest server.
60487     */
60488
60489    getRequestURI(reloadUri) {
60490      if (!reloadUri) {
60491        return null;
60492      }
60493      const isExcluded = uri => this.excludedSteeringManifestURLs.has(uri);
60494      if (this.proxyServerUrl_) {
60495        const proxyURI = this.setProxyServerUrl_(reloadUri);
60496        if (!isExcluded(proxyURI)) {
60497          return proxyURI;
60498        }
60499      }
60500      const steeringURI = this.setSteeringParams_(reloadUri);
60501      if (!isExcluded(steeringURI)) {
60502        return steeringURI;
60503      } // Return nothing if all valid manifest URIs are excluded.
60504
60505      return null;
60506    }
60507    /**
60508     * Start the timeout for re-requesting the steering manifest at the TTL interval.
60509     *
60510     * @param {number} ttl time in seconds of the timeout. Defaults to the
60511     *        ttl interval in the steering manifest
60512     */
60513
60514    startTTLTimeout_(ttl = this.steeringManifest.ttl) {
60515      // 300 (5 minutes) is the default value.
60516      const ttlMS = ttl * 1000;
60517      this.ttlTimeout_ = window.setTimeout(() => {
60518        this.requestSteeringManifest();
60519      }, ttlMS);
60520    }
60521    /**
60522     * Clear the TTL timeout if necessary.
60523     */
60524
60525    clearTTLTimeout_() {
60526      window.clearTimeout(this.ttlTimeout_);
60527      this.ttlTimeout_ = null;
60528    }
60529    /**
60530     * aborts any current steering xhr and sets the current request object to null
60531     */
60532
60533    abort() {
60534      if (this.request_) {
60535        this.request_.abort();
60536      }
60537      this.request_ = null;
60538    }
60539    /**
60540     * aborts steering requests clears the ttl timeout and resets all properties.
60541     */
60542
60543    dispose() {
60544      this.off('content-steering');
60545      this.off('error');
60546      this.abort();
60547      this.clearTTLTimeout_();
60548      this.currentPathway = null;
60549      this.defaultPathway = null;
60550      this.queryBeforeStart = null;
60551      this.proxyServerUrl_ = null;
60552      this.manifestType_ = null;
60553      this.ttlTimeout_ = null;
60554      this.request_ = null;
60555      this.excludedSteeringManifestURLs = new Set();
60556      this.availablePathways_ = new Set();
60557      this.steeringManifest = new SteeringManifest();
60558    }
60559    /**
60560     * adds a pathway to the available pathways set
60561     *
60562     * @param {string} pathway the pathway string to add
60563     */
60564
60565    addAvailablePathway(pathway) {
60566      if (pathway) {
60567        this.availablePathways_.add(pathway);
60568      }
60569    }
60570    /**
60571     * Clears all pathways from the available pathways set
60572     */
60573
60574    clearAvailablePathways() {
60575      this.availablePathways_.clear();
60576    }
60577    /**
60578     * Removes a pathway from the available pathways set.
60579     */
60580
60581    excludePathway(pathway) {
60582      return this.availablePathways_.delete(pathway);
60583    }
60584    /**
60585     * Checks the refreshed DASH manifest content steering tag for changes.
60586     *
60587     * @param {string} baseURL new steering tag on DASH manifest refresh
60588     * @param {Object} newTag the new tag to check for changes
60589     * @return a true or false whether the new tag has different values
60590     */
60591
60592    didDASHTagChange(baseURL, newTag) {
60593      return !newTag && this.steeringManifest.reloadUri || newTag && (resolveUrl(baseURL, newTag.serverURL) !== this.steeringManifest.reloadUri || newTag.defaultServiceLocation !== this.defaultPathway || newTag.queryBeforeStart !== this.queryBeforeStart || newTag.proxyServerURL !== this.proxyServerUrl_);
60594    }
60595    getAvailablePathways() {
60596      return this.availablePathways_;
60597    }
60598  }
vendor: 6,830 bytes, lines 60599-60760
60599
60600  /**
60601   * @file playlist-controller.js
60602   */
60603  const ABORT_EARLY_EXCLUSION_SECONDS = 10;
60604  let Vhs$1; // SegmentLoader stats that need to have each loader's
60605  // values summed to calculate the final value
60606
60607  const loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
60608  const sumLoaderStat = function (stat) {
60609    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
60610  };
60611  const shouldSwitchToMedia = function ({
60612    currentPlaylist,
60613    buffered,
60614    currentTime,
60615    nextPlaylist,
60616    bufferLowWaterLine,
60617    bufferHighWaterLine,
60618    duration,
60619    bufferBasedABR,
60620    log
60621  }) {
60622    // we have no other playlist to switch to
60623    if (!nextPlaylist) {
60624      videojs.log.warn('We received no playlist to switch to. Please check your stream.');
60625      return false;
60626    }
60627    const sharedLogLine = `allowing switch ${currentPlaylist && currentPlaylist.id || 'null'} -> ${nextPlaylist.id}`;
60628    if (!currentPlaylist) {
60629      log(`${sharedLogLine} as current playlist is not set`);
60630      return true;
60631    } // no need to switch if playlist is the same
60632
60633    if (nextPlaylist.id === currentPlaylist.id) {
60634      return false;
60635    } // determine if current time is in a buffered range.
60636
60637    const isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
60638    // This is because in LIVE, the player plays 3 segments from the end of the
60639    // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
60640    // in those segments, a viewer will never experience a rendition upswitch.
60641
60642    if (!currentPlaylist.endList) {
60643      // For LLHLS live streams, don't switch renditions before playback has started, as it almost
60644      // doubles the time to first playback.
60645      if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
60646        log(`not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`);
60647        return false;
60648      }
60649      log(`${sharedLogLine} as current playlist is live`);
60650      return true;
60651    }
60652    const forwardBuffer = timeAheadOf(buffered, currentTime);
60653    const maxBufferLowWaterLine = bufferBasedABR ? 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
60654    // duration is below the max potential low water line
60655
60656    if (duration < maxBufferLowWaterLine) {
60657      log(`${sharedLogLine} as duration < max low water line (${duration} < ${maxBufferLowWaterLine})`);
60658      return true;
60659    }
60660    const nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
60661    const currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
60662    // we can switch down
60663
60664    if (nextBandwidth < currBandwidth && (!bufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
60665      let logLine = `${sharedLogLine} as next bandwidth < current bandwidth (${nextBandwidth} < ${currBandwidth})`;
60666      if (bufferBasedABR) {
60667        logLine += ` and forwardBuffer < bufferHighWaterLine (${forwardBuffer} < ${bufferHighWaterLine})`;
60668      }
60669      log(logLine);
60670      return true;
60671    } // and if our buffer is higher than the low water line,
60672    // we can switch up
60673
60674    if ((!bufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
60675      let logLine = `${sharedLogLine} as forwardBuffer >= bufferLowWaterLine (${forwardBuffer} >= ${bufferLowWaterLine})`;
60676      if (bufferBasedABR) {
60677        logLine += ` and next bandwidth > current bandwidth (${nextBandwidth} > ${currBandwidth})`;
60678      }
60679      log(logLine);
60680      return true;
60681    }
60682    log(`not ${sharedLogLine} as no switching criteria met`);
60683    return false;
60684  };
60685  /**
60686   * the main playlist controller controller all interactons
60687   * between playlists and segmentloaders. At this time this mainly
60688   * involves a main playlist and a series of audio playlists
60689   * if they are available
60690   *
60691   * @class PlaylistController
60692   * @extends videojs.EventTarget
60693   */
60694
60695  class PlaylistController extends videojs.EventTarget {
60696    constructor(options) {
60697      super();
60698      const {
60699        src,
60700        withCredentials,
60701        tech,
60702        bandwidth,
60703        externVhs,
60704        useCueTags,
60705        playlistExclusionDuration,
60706        enableLowInitialPlaylist,
60707        sourceType,
60708        cacheEncryptionKeys,
60709        bufferBasedABR,
60710        leastPixelDiffSelector,
60711        captionServices
60712      } = options;
60713      if (!src) {
60714        throw new Error('A non-empty playlist URL or JSON manifest string is required');
60715      }
60716      let {
60717        maxPlaylistRetries
60718      } = options;
60719      if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
60720        maxPlaylistRetries = Infinity;
60721      }
60722      Vhs$1 = externVhs;
60723      this.bufferBasedABR = Boolean(bufferBasedABR);
60724      this.leastPixelDiffSelector = Boolean(leastPixelDiffSelector);
60725      this.withCredentials = withCredentials;
60726      this.tech_ = tech;
60727      this.vhs_ = tech.vhs;
60728      this.sourceType_ = sourceType;
60729      this.useCueTags_ = useCueTags;
60730      this.playlistExclusionDuration = playlistExclusionDuration;
60731      this.maxPlaylistRetries = maxPlaylistRetries;
60732      this.enableLowInitialPlaylist = enableLowInitialPlaylist;
60733      if (this.useCueTags_) {
60734        this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
60735        this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
60736      }
60737      this.requestOptions_ = {
60738        withCredentials,
60739        maxPlaylistRetries,
60740        timeout: null
60741      };
60742      this.on('error', this.pauseLoading);
60743      this.mediaTypes_ = createMediaTypes();
60744      this.mediaSource = new window.MediaSource();
60745      this.handleDurationChange_ = this.handleDurationChange_.bind(this);
60746      this.handleSourceOpen_ = this.handleSourceOpen_.bind(this);
60747      this.handleSourceEnded_ = this.handleSourceEnded_.bind(this);
60748      this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
60749
60750      this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_);
60751      this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
60752      // everything, and the MediaSource should not be detached without a proper disposal
60753
60754      this.seekable_ = createTimeRanges();
60755      this.hasPlayed_ = false;
60756      this.syncController_ = new SyncController(options);
60757      this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
60758        kind: 'metadata',
60759        label: 'segment-metadata'
60760      }, false).track
vendor: 6,152 bytes, lines 60760-60909
60760;
60761      this.decrypter_ = new Decrypter();
60762      this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
60763      this.inbandTextTracks_ = {};
60764      this.timelineChangeController_ = new TimelineChangeController();
60765      this.keyStatusMap_ = new Map();
60766      const segmentLoaderSettings = {
60767        vhs: this.vhs_,
60768        parse708captions: options.parse708captions,
60769        useDtsForTimestampOffset: options.useDtsForTimestampOffset,
60770        captionServices,
60771        mediaSource: this.mediaSource,
60772        currentTime: this.tech_.currentTime.bind(this.tech_),
60773        seekable: () => this.seekable(),
60774        seeking: () => this.tech_.seeking(),
60775        duration: () => this.duration(),
60776        hasPlayed: () => this.hasPlayed_,
60777        goalBufferLength: () => this.goalBufferLength(),
60778        bandwidth,
60779        syncController: this.syncController_,
60780        decrypter: this.decrypter_,
60781        sourceType: this.sourceType_,
60782        inbandTextTracks: this.inbandTextTracks_,
60783        cacheEncryptionKeys,
60784        sourceUpdater: this.sourceUpdater_,
60785        timelineChangeController: this.timelineChangeController_,
60786        exactManifestTimings: options.exactManifestTimings,
60787        addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
60788      }; // The source type check not only determines whether a special DASH playlist loader
60789      // should be used, but also covers the case where the provided src is a vhs-json
60790      // manifest object (instead of a URL). In the case of vhs-json, the default
60791      // PlaylistLoader should be used.
60792
60793      this.mainPlaylistLoader_ = this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
60794        addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
60795      })) : new PlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
60796        addDateRangesToTextTrack: this.addDateRangesToTextTrack_.bind(this)
60797      }));
60798      this.setupMainPlaylistLoaderListeners_(); // setup segment loaders
60799      // combined audio/video or just video when alternate audio track is selected
60800
60801      this.mainSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
60802        segmentMetadataTrack: this.segmentMetadataTrack_,
60803        loaderType: 'main'
60804      }), options); // alternate audio track
60805
60806      this.audioSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
60807        loaderType: 'audio'
60808      }), options);
60809      this.subtitleSegmentLoader_ = new VTTSegmentLoader(merge(segmentLoaderSettings, {
60810        loaderType: 'vtt',
60811        featuresNativeTextTracks: this.tech_.featuresNativeTextTracks,
60812        loadVttJs: () => new Promise((resolve, reject) => {
60813          function onLoad() {
60814            tech.off('vttjserror', onError);
60815            resolve();
60816          }
60817          function onError() {
60818            tech.off('vttjsloaded', onLoad);
60819            reject();
60820          }
60821          tech.one('vttjsloaded', onLoad);
60822          tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
60823
60824          tech.addWebVttScript_();
60825        })
60826      }), options);
60827      const getBandwidth = () => {
60828        return this.mainSegmentLoader_.bandwidth;
60829      };
60830      this.contentSteeringController_ = new ContentSteeringController(this.vhs_.xhr, getBandwidth);
60831      this.setupSegmentLoaderListeners_();
60832      if (this.bufferBasedABR) {
60833        this.mainPlaylistLoader_.one('loadedplaylist', () => this.startABRTimer_());
60834        this.tech_.on('pause', () => this.stopABRTimer_());
60835        this.tech_.on('play', () => this.startABRTimer_());
60836      } // Create SegmentLoader stat-getters
60837      // mediaRequests_
60838      // mediaRequestsAborted_
60839      // mediaRequestsTimedout_
60840      // mediaRequestsErrored_
60841      // mediaTransferDuration_
60842      // mediaBytesTransferred_
60843      // mediaAppends_
60844
60845      loaderStats.forEach(stat => {
60846        this[stat + '_'] = sumLoaderStat.bind(this, stat);
60847      });
60848      this.logger_ = logger('pc');
60849      this.triggeredFmp4Usage = false;
60850      if (this.tech_.preload() === 'none') {
60851        this.loadOnPlay_ = () => {
60852          this.loadOnPlay_ = null;
60853          this.mainPlaylistLoader_.load();
60854        };
60855        this.tech_.one('play', this.loadOnPlay_);
60856      } else {
60857        this.mainPlaylistLoader_.load();
60858      }
60859      this.timeToLoadedData__ = -1;
60860      this.mainAppendsToLoadedData__ = -1;
60861      this.audioAppendsToLoadedData__ = -1;
60862      const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
60863
60864      this.tech_.one(event, () => {
60865        const timeToLoadedDataStart = Date.now();
60866        this.tech_.one('loadeddata', () => {
60867          this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
60868          this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
60869          this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
60870        });
60871      });
60872    }
60873    mainAppendsToLoadedData_() {
60874      return this.mainAppendsToLoadedData__;
60875    }
60876    audioAppendsToLoadedData_() {
60877      return this.audioAppendsToLoadedData__;
60878    }
60879    appendsToLoadedData_() {
60880      const main = this.mainAppendsToLoadedData_();
60881      const audio = this.audioAppendsToLoadedData_();
60882      if (main === -1 || audio === -1) {
60883        return -1;
60884      }
60885      return main + audio;
60886    }
60887    timeToLoadedData_() {
60888      return this.timeToLoadedData__;
60889    }
60890    /**
60891     * Run selectPlaylist and switch to the new playlist if we should
60892     *
60893     * @param {string} [reason=abr] a reason for why the ABR check is made
60894     * @private
60895     */
60896
60897    checkABR_(reason = 'abr') {
60898      const nextPlaylist = this.selectPlaylist();
60899      if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
60900        this.switchMedia_(nextPlaylist, reason);
60901      }
60902    }
60903    switchMedia_(playlist, cause, delay) {
60904      const oldMedia = this.media();
60905      const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
60906      const newId = playlist && (playlist.id || playlist.uri);
60907      if (oldId && oldId !== newId) {
60908        this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
60909        
vendor: 2,096 bytes, lines 60909-60964
60909this.tech_.trigger({
60910          type: 'usage',
60911          name: `vhs-rendition-change-${cause}`
60912        });
60913      }
60914      this.mainPlaylistLoader_.media(playlist, delay);
60915    }
60916    /**
60917     * A function that ensures we switch our playlists inside of `mediaTypes`
60918     * to match the current `serviceLocation` provided by the contentSteering controller.
60919     * We want to check media types of `AUDIO`, `SUBTITLES`, and `CLOSED-CAPTIONS`.
60920     *
60921     * This should only be called on a DASH playback scenario while using content steering.
60922     * This is necessary due to differences in how media in HLS manifests are generally tied to
60923     * a video playlist, where in DASH that is not always the case.
60924     */
60925
60926    switchMediaForDASHContentSteering_() {
60927      ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
60928        const mediaType = this.mediaTypes_[type];
60929        const activeGroup = mediaType ? mediaType.activeGroup() : null;
60930        const pathway = this.contentSteeringController_.getPathway();
60931        if (activeGroup && pathway) {
60932          // activeGroup can be an array or a single group
60933          const mediaPlaylists = activeGroup.length ? activeGroup[0].playlists : activeGroup.playlists;
60934          const dashMediaPlaylists = mediaPlaylists.filter(p => p.attributes.serviceLocation === pathway); // Switch the current active playlist to the correct CDN
60935
60936          if (dashMediaPlaylists.length) {
60937            this.mediaTypes_[type].activePlaylistLoader.media(dashMediaPlaylists[0]);
60938          }
60939        }
60940      });
60941    }
60942    /**
60943     * Start a timer that periodically calls checkABR_
60944     *
60945     * @private
60946     */
60947
60948    startABRTimer_() {
60949      this.stopABRTimer_();
60950      this.abrTimer_ = window.setInterval(() => this.checkABR_(), 250);
60951    }
60952    /**
60953     * Stop the timer that periodically calls checkABR_
60954     *
60955     * @private
60956     */
60957
60958    stopABRTimer_() {
60959      // if we're scrubbing, we don't need to pause.
60960      // This getter will be added to Video.js in version 7.11.
60961      if (this.tech_.scrubbing && this.tech_.scrubbing()) {
60962        return;
60963      }
60964      window
vendor: 7,520 bytes, lines 60964-61145
60964.clearInterval(this.abrTimer_);
60965      this.abrTimer_ = null;
60966    }
60967    /**
60968     * Get a list of playlists for the currently selected audio playlist
60969     *
60970     * @return {Array} the array of audio playlists
60971     */
60972
60973    getAudioTrackPlaylists_() {
60974      const main = this.main();
60975      const defaultPlaylists = main && main.playlists || []; // if we don't have any audio groups then we can only
60976      // assume that the audio tracks are contained in main
60977      // playlist array, use that or an empty array.
60978
60979      if (!main || !main.mediaGroups || !main.mediaGroups.AUDIO) {
60980        return defaultPlaylists;
60981      }
60982      const AUDIO = main.mediaGroups.AUDIO;
60983      const groupKeys = Object.keys(AUDIO);
60984      let track; // get the current active track
60985
60986      if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
60987        track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from main if mediaTypes_ isn't setup yet
60988      } else {
60989        // default group is `main` or just the first group.
60990        const defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
60991        for (const label in defaultGroup) {
60992          if (defaultGroup[label].default) {
60993            track = {
60994              label
60995            };
60996            break;
60997          }
60998        }
60999      } // no active track no playlists.
61000
61001      if (!track) {
61002        return defaultPlaylists;
61003      }
61004      const playlists = []; // get all of the playlists that are possible for the
61005      // active track.
61006
61007      for (const group in AUDIO) {
61008        if (AUDIO[group][track.label]) {
61009          const properties = AUDIO[group][track.label];
61010          if (properties.playlists && properties.playlists.length) {
61011            playlists.push.apply(playlists, properties.playlists);
61012          } else if (properties.uri) {
61013            playlists.push(properties);
61014          } else if (main.playlists.length) {
61015            // if an audio group does not have a uri
61016            // see if we have main playlists that use it as a group.
61017            // if we do then add those to the playlists list.
61018            for (let i = 0; i < main.playlists.length; i++) {
61019              const playlist = main.playlists[i];
61020              if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
61021                playlists.push(playlist);
61022              }
61023            }
61024          }
61025        }
61026      }
61027      if (!playlists.length) {
61028        return defaultPlaylists;
61029      }
61030      return playlists;
61031    }
61032    /**
61033     * Register event handlers on the main playlist loader. A helper
61034     * function for construction time.
61035     *
61036     * @private
61037     */
61038
61039    setupMainPlaylistLoaderListeners_() {
61040      this.mainPlaylistLoader_.on('loadedmetadata', () => {
61041        const media = this.mainPlaylistLoader_.media();
61042        const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61043        // timeout the request.
61044
61045        if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
61046          this.requestOptions_.timeout = 0;
61047        } else {
61048          this.requestOptions_.timeout = requestTimeout;
61049        } // if this isn't a live video and preload permits, start
61050        // downloading segments
61051
61052        if (media.endList && this.tech_.preload() !== 'none') {
61053          this.mainSegmentLoader_.playlist(media, this.requestOptions_);
61054          this.mainSegmentLoader_.load();
61055        }
61056        setupMediaGroups({
61057          sourceType: this.sourceType_,
61058          segmentLoaders: {
61059            AUDIO: this.audioSegmentLoader_,
61060            SUBTITLES: this.subtitleSegmentLoader_,
61061            main: this.mainSegmentLoader_
61062          },
61063          tech: this.tech_,
61064          requestOptions: this.requestOptions_,
61065          mainPlaylistLoader: this.mainPlaylistLoader_,
61066          vhs: this.vhs_,
61067          main: this.main(),
61068          mediaTypes: this.mediaTypes_,
61069          excludePlaylist: this.excludePlaylist.bind(this)
61070        });
61071        this.triggerPresenceUsage_(this.main(), media);
61072        this.setupFirstPlay();
61073        if (!this.mediaTypes_.AUDIO.activePlaylistLoader || this.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
61074          this.trigger('selectedinitialmedia');
61075        } else {
61076          // We must wait for the active audio playlist loader to
61077          // finish setting up before triggering this event so the
61078          // representations API and EME setup is correct
61079          this.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', () => {
61080            this.trigger('selectedinitialmedia');
61081          });
61082        }
61083      });
61084      this.mainPlaylistLoader_.on('loadedplaylist', () => {
61085        if (this.loadOnPlay_) {
61086          this.tech_.off('play', this.loadOnPlay_);
61087        }
61088        let updatedPlaylist = this.mainPlaylistLoader_.media();
61089        if (!updatedPlaylist) {
61090          // Add content steering listeners on first load and init.
61091          this.attachContentSteeringListeners_();
61092          this.initContentSteeringController_(); // exclude any variants that are not supported by the browser before selecting
61093          // an initial media as the playlist selectors do not consider browser support
61094
61095          this.excludeUnsupportedVariants_();
61096          let selectedMedia;
61097          if (this.enableLowInitialPlaylist) {
61098            selectedMedia = this.selectInitialPlaylist();
61099          }
61100          if (!selectedMedia) {
61101            selectedMedia = this.selectPlaylist();
61102          }
61103          if (!selectedMedia || !this.shouldSwitchToMedia_(selectedMedia)) {
61104            return;
61105          }
61106          this.initialMedia_ = selectedMedia;
61107          this.switchMedia_(this.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
61108          // fire again since the playlist will be requested. In the case of vhs-json
61109          // (where the manifest object is provided as the source), when the media
61110          // playlist's `segments` list is already available, a media playlist won't be
61111          // requested, and loadedplaylist won't fire again, so the playlist handler must be
61112          // called on its own here.
61113
61114          const haveJsonSource = this.sourceType_ === 'vhs-json' && this.initialMedia_.segments;
61115          if (!haveJsonSource) {
61116            return;
61117          }
61118          updatedPlaylist = this.initialMedia_;
61119        }
61120        this.handleUpdatedMediaPlaylist(updatedPlaylist);
61121      });
61122      this.mainPlaylistLoader_.on('error', () => {
61123        const error = this.mainPlaylistLoader_.error;
61124        this.excludePlaylist({
61125          playlistToExclude: error.playlist,
61126          error
61127        });
61128      });
61129      this.mainPlaylistLoader_.on('mediachanging', () => {
61130        this.mainSegmentLoader_.abort();
61131        this.mainSegmentLoader_.pause();
61132      });
61133      this.mainPlaylistLoader_.on('mediachange', () => {
61134        const media = this.mainPlaylistLoader_.media();
61135        const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61136        // timeout the request.
61137
61138        if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
61139          this.requestOptions_.timeout = 0;
61140        } else {
61141          this.requestOptions_.timeout = requestTimeout;
61142        }
61143        if (this.sourceType_ === 'dash') {
61144          // we don't want to re-request the same hls playlist right after it was changed
61145          
61145this.mainPlaylistLoader_.load();
61146        } // TODO: Create a new event on the PlaylistLoader that signals
61147        // that the segments have changed in some way and use that to
61148        // update the SegmentLoader instead of doing it twice here and
61149        // on `loadedplaylist`
61150
61151        this.mainSegmentLoader_.pause();
61152        this.mainSegmentLoader_.playlist(media, this.requestOptions_);
61153        if (this.waitingForFastQualityPlaylistReceived_) {
61154          this.runFastQualitySwitch_();
61155        } else {
61156          this.mainSegmentLoader_.load();
61157        }
61158        this.tech_.trigger({
61159          type: 'mediachange',
61160          bubbles: true
61161        });
61162      });
61163      this.mainPlaylistLoader_.on('playlistunchanged', () => {
61164        const updatedPlaylist = this.mainPlaylistLoader_.media(); // ignore unchanged playlists that have already been
61165        // excluded for not-changing. We likely just have a really slowly updating
61166        // playlist.
61167
61168        if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
61169          return;
61170        }
61171        const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);
61172        if (playlistOutdated) {
61173          // Playlist has stopped updating and we're stuck at its end. Try to
61174          // exclude it and switch to another playlist in the hope that that
61175          // one is updating (and give the player a chance to re-adjust to the
61176          // safe live point).
61177          this.excludePlaylist({
61178            error: {
61179              message: 'Playlist no longer updating.',
61180              reason: 'playlist-unchanged'
61181            }
61182          }); // useful for monitoring QoS
61183
61184          this.tech_.trigger('playliststuck');
61185        }
61186      });
61187      this.mainPlaylistLoader_.on('renditiondisabled', () => {
61188        this.tech_.trigger({
61189          type: 'usage',
61190          name: 'vhs-rendition-disabled'
61191        });
61192      });
61193      this.mainPlaylistLoader_.on('renditionenabled', () => {
61194        this.tech_.trigger({
61195          type: 'usage',
61196          name: 'vhs-rendition-enabled'
vendor: 5,808 bytes, lines 61196-61361
61196
61197        });
61198      });
61199    }
61200    /**
61201     * Given an updated media playlist (whether it was loaded for the first time, or
61202     * refreshed for live playlists), update any relevant properties and state to reflect
61203     * changes in the media that should be accounted for (e.g., cues and duration).
61204     *
61205     * @param {Object} updatedPlaylist the updated media playlist object
61206     *
61207     * @private
61208     */
61209
61210    handleUpdatedMediaPlaylist(updatedPlaylist) {
61211      if (this.useCueTags_) {
61212        this.updateAdCues_(updatedPlaylist);
61213      } // TODO: Create a new event on the PlaylistLoader that signals
61214      // that the segments have changed in some way and use that to
61215      // update the SegmentLoader instead of doing it twice here and
61216      // on `mediachange`
61217
61218      this.mainSegmentLoader_.pause();
61219      this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
61220      if (this.waitingForFastQualityPlaylistReceived_) {
61221        this.runFastQualitySwitch_();
61222      }
61223      this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
61224      // as it is possible that it was temporarily stopped while waiting for
61225      // a playlist (e.g., in case the playlist errored and we re-requested it).
61226
61227      if (!this.tech_.paused()) {
61228        this.mainSegmentLoader_.load();
61229        if (this.audioSegmentLoader_) {
61230          this.audioSegmentLoader_.load();
61231        }
61232      }
61233    }
61234    /**
61235     * A helper function for triggerring presence usage events once per source
61236     *
61237     * @private
61238     */
61239
61240    triggerPresenceUsage_(main, media) {
61241      const mediaGroups = main.mediaGroups || {};
61242      let defaultDemuxed = true;
61243      const audioGroupKeys = Object.keys(mediaGroups.AUDIO);
61244      for (const mediaGroup in mediaGroups.AUDIO) {
61245        for (const label in mediaGroups.AUDIO[mediaGroup]) {
61246          const properties = mediaGroups.AUDIO[mediaGroup][label];
61247          if (!properties.uri) {
61248            defaultDemuxed = false;
61249          }
61250        }
61251      }
61252      if (defaultDemuxed) {
61253        this.tech_.trigger({
61254          type: 'usage',
61255          name: 'vhs-demuxed'
61256        });
61257      }
61258      if (Object.keys(mediaGroups.SUBTITLES).length) {
61259        this.tech_.trigger({
61260          type: 'usage',
61261          name: 'vhs-webvtt'
61262        });
61263      }
61264      if (Vhs$1.Playlist.isAes(media)) {
61265        this.tech_.trigger({
61266          type: 'usage',
61267          name: 'vhs-aes'
61268        });
61269      }
61270      if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
61271        this.tech_.trigger({
61272          type: 'usage',
61273          name: 'vhs-alternate-audio'
61274        });
61275      }
61276      if (this.useCueTags_) {
61277        this.tech_.trigger({
61278          type: 'usage',
61279          name: 'vhs-playlist-cue-tags'
61280        });
61281      }
61282    }
61283    shouldSwitchToMedia_(nextPlaylist) {
61284      const currentPlaylist = this.mainPlaylistLoader_.media() || this.mainPlaylistLoader_.pendingMedia_;
61285      const currentTime = this.tech_.currentTime();
61286      const bufferLowWaterLine = this.bufferLowWaterLine();
61287      const bufferHighWaterLine = this.bufferHighWaterLine();
61288      const buffered = this.tech_.buffered();
61289      return shouldSwitchToMedia({
61290        buffered,
61291        currentTime,
61292        currentPlaylist,
61293        nextPlaylist,
61294        bufferLowWaterLine,
61295        bufferHighWaterLine,
61296        duration: this.duration(),
61297        bufferBasedABR: this.bufferBasedABR,
61298        log: this.logger_
61299      });
61300    }
61301    /**
61302     * Register event handlers on the segment loaders. A helper function
61303     * for construction time.
61304     *
61305     * @private
61306     */
61307
61308    setupSegmentLoaderListeners_() {
61309      this.mainSegmentLoader_.on('bandwidthupdate', () => {
61310        // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
61311        // useful to check to see if a rendition switch should be made.
61312        this.checkABR_('bandwidthupdate');
61313        this.tech_.trigger('bandwidthupdate');
61314      });
61315      this.mainSegmentLoader_.on('timeout', () => {
61316        if (this.bufferBasedABR) {
61317          // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
61318          // Here the only consideration is that for buffer based ABR there's no guarantee
61319          // of an immediate switch (since the bandwidth is averaged with a timeout
61320          // bandwidth value of 1), so force a load on the segment loader to keep it going.
61321          this.mainSegmentLoader_.load();
61322        }
61323      }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
61324      // based ABR.
61325
61326      if (!this.bufferBasedABR) {
61327        this.mainSegmentLoader_.on('progress', () => {
61328          this.trigger('progress');
61329        });
61330      }
61331      this.mainSegmentLoader_.on('error', () => {
61332        const error = this.mainSegmentLoader_.error();
61333        this.excludePlaylist({
61334          playlistToExclude: error.playlist,
61335          error
61336        });
61337      });
61338      this.mainSegmentLoader_.on('appenderror', () => {
61339        this.error = this.mainSegmentLoader_.error_;
61340        this.trigger('error');
61341      });
61342      this.mainSegmentLoader_.on('syncinfoupdate', () => {
61343        this.onSyncInfoUpdate_();
61344      });
61345      this.mainSegmentLoader_.on('timestampoffset', () => {
61346        this.tech_.trigger({
61347          type: 'usage',
61348          name: 'vhs-timestamp-offset'
61349        });
61350      });
61351      this.audioSegmentLoader_.on('syncinfoupdate', () => {
61352        this.onSyncInfoUpdate_();
61353      });
61354      this.audioSegmentLoader_.on('appenderror', () => {
61355        this.error = this.audioSegmentLoader_.error_;
61356        this.trigger('error');
61357      });
61358      this.mainSegmentLoader_.on('ended', () => {
61359        this.logger_('main segment loader ended');
61360        this.onEndOfStream();
61361      });
vendor: 5,597 bytes, lines 61361-61510
61361
61362      this.mainSegmentLoader_.on('earlyabort', event => {
61363        // never try to early abort with the new ABR algorithm
61364        if (this.bufferBasedABR) {
61365          return;
61366        }
61367        this.delegateLoaders_('all', ['abort']);
61368        this.excludePlaylist({
61369          error: {
61370            message: 'Aborted early because there isn\'t enough bandwidth to complete ' + 'the request without rebuffering.'
61371          },
61372          playlistExclusionDuration: ABORT_EARLY_EXCLUSION_SECONDS
61373        });
61374      });
61375      const updateCodecs = () => {
61376        if (!this.sourceUpdater_.hasCreatedSourceBuffers()) {
61377          return this.tryToCreateSourceBuffers_();
61378        }
61379        const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
61380
61381        if (!codecs) {
61382          return;
61383        }
61384        this.sourceUpdater_.addOrChangeSourceBuffers(codecs);
61385      };
61386      this.mainSegmentLoader_.on('trackinfo', updateCodecs);
61387      this.audioSegmentLoader_.on('trackinfo', updateCodecs);
61388      this.mainSegmentLoader_.on('fmp4', () => {
61389        if (!this.triggeredFmp4Usage) {
61390          this.tech_.trigger({
61391            type: 'usage',
61392            name: 'vhs-fmp4'
61393          });
61394          this.triggeredFmp4Usage = true;
61395        }
61396      });
61397      this.audioSegmentLoader_.on('fmp4', () => {
61398        if (!this.triggeredFmp4Usage) {
61399          this.tech_.trigger({
61400            type: 'usage',
61401            name: 'vhs-fmp4'
61402          });
61403          this.triggeredFmp4Usage = true;
61404        }
61405      });
61406      this.audioSegmentLoader_.on('ended', () => {
61407        this.logger_('audioSegmentLoader ended');
61408        this.onEndOfStream();
61409      });
61410    }
61411    mediaSecondsLoaded_() {
61412      return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
61413    }
61414    /**
61415     * Call load on our SegmentLoaders
61416     */
61417
61418    load() {
61419      this.mainSegmentLoader_.load();
61420      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61421        this.audioSegmentLoader_.load();
61422      }
61423      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
61424        this.subtitleSegmentLoader_.load();
61425      }
61426    }
61427    /**
61428     * Re-tune playback quality level for the current player
61429     * conditions. This method will perform destructive actions like removing
61430     * already buffered content in order to readjust the currently active
61431     * playlist quickly. This is good for manual quality changes
61432     *
61433     * @private
61434     */
61435
61436    fastQualityChange_(media = this.selectPlaylist()) {
61437      if (media && media === this.mainPlaylistLoader_.media()) {
61438        this.logger_('skipping fastQualityChange because new media is same as old');
61439        return;
61440      }
61441      this.switchMedia_(media, 'fast-quality'); // we would like to avoid race condition when we call fastQuality,
61442      // reset everything and start loading segments from prev segments instead of new because new playlist is not received yet
61443
61444      this.waitingForFastQualityPlaylistReceived_ = true;
61445    }
61446    runFastQualitySwitch_() {
61447      this.waitingForFastQualityPlaylistReceived_ = false; // Delete all buffered data to allow an immediate quality switch, then seek to give
61448      // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
61449      // ahead was roughly the minimum that will accomplish this across a variety of content
61450      // in IE and Edge, but seeking in place is sufficient on all other browsers)
61451      // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
61452      // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
61453
61454      this.mainSegmentLoader_.pause();
61455      this.mainSegmentLoader_.resetEverything(() => {
61456        this.tech_.setCurrentTime(this.tech_.currentTime());
61457      }); // don't need to reset audio as it is reset when media changes
61458    }
61459    /**
61460     * Begin playback.
61461     */
61462
61463    play() {
61464      if (this.setupFirstPlay()) {
61465        return;
61466      }
61467      if (this.tech_.ended()) {
61468        this.tech_.setCurrentTime(0);
61469      }
61470      if (this.hasPlayed_) {
61471        this.load();
61472      }
61473      const seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
61474      // seek forward to the live point
61475
61476      if (this.tech_.duration() === Infinity) {
61477        if (this.tech_.currentTime() < seekable.start(0)) {
61478          return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
61479        }
61480      }
61481    }
61482    /**
61483     * Seek to the latest media position if this is a live video and the
61484     * player and video are loaded and initialized.
61485     */
61486
61487    setupFirstPlay() {
61488      const media = this.mainPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
61489      //  If 1) there is no active media
61490      //     2) the player is paused
61491      //     3) the first play has already been setup
61492      // then exit early
61493
61494      if (!media || this.tech_.paused() || this.hasPlayed_) {
61495        return false;
61496      } // when the video is a live stream and/or has a start time
61497
61498      if (!media.endList || media.start) {
61499        const seekable = this.seekable();
61500        if (!seekable.length) {
61501          // without a seekable range, the player cannot seek to begin buffering at the
61502          // live or start point
61503          return false;
61504        }
61505        const seekableEnd = seekable.end(0);
61506        let startPoint = seekableEnd;
61507        if (media.start) {
61508          const offset = media.start.timeOffset;
61509          if (offset < 0) {
61510            startPoint = Math.max(seekableEnd + offset
vendor: 15,902 bytes, lines 61510-61899
61510, seekable.start(0));
61511          } else {
61512            startPoint = Math.min(seekableEnd, offset);
61513          }
61514        } // trigger firstplay to inform the source handler to ignore the next seek event
61515
61516        this.trigger('firstplay'); // seek to the live point
61517
61518        this.tech_.setCurrentTime(startPoint);
61519      }
61520      this.hasPlayed_ = true; // we can begin loading now that everything is ready
61521
61522      this.load();
61523      return true;
61524    }
61525    /**
61526     * handle the sourceopen event on the MediaSource
61527     *
61528     * @private
61529     */
61530
61531    handleSourceOpen_() {
61532      // Only attempt to create the source buffer if none already exist.
61533      // handleSourceOpen is also called when we are "re-opening" a source buffer
61534      // after `endOfStream` has been called (in response to a seek for instance)
61535      this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
61536      // code in video.js but is required because play() must be invoked
61537      // *after* the media source has opened.
61538
61539      if (this.tech_.autoplay()) {
61540        const playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
61541        // on browsers which return a promise
61542
61543        if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
61544          playPromise.then(null, e => {});
61545        }
61546      }
61547      this.trigger('sourceopen');
61548    }
61549    /**
61550     * handle the sourceended event on the MediaSource
61551     *
61552     * @private
61553     */
61554
61555    handleSourceEnded_() {
61556      if (!this.inbandTextTracks_.metadataTrack_) {
61557        return;
61558      }
61559      const cues = this.inbandTextTracks_.metadataTrack_.cues;
61560      if (!cues || !cues.length) {
61561        return;
61562      }
61563      const duration = this.duration();
61564      cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
61565    }
61566    /**
61567     * handle the durationchange event on the MediaSource
61568     *
61569     * @private
61570     */
61571
61572    handleDurationChange_() {
61573      this.tech_.trigger('durationchange');
61574    }
61575    /**
61576     * Calls endOfStream on the media source when all active stream types have called
61577     * endOfStream
61578     *
61579     * @param {string} streamType
61580     *        Stream type of the segment loader that called endOfStream
61581     * @private
61582     */
61583
61584    onEndOfStream() {
61585      let isEndOfStream = this.mainSegmentLoader_.ended_;
61586      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61587        const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
61588
61589        if (!mainMediaInfo || mainMediaInfo.hasVideo) {
61590          // if we do not know if the main segment loader contains video yet or if we
61591          // definitively know the main segment loader contains video, then we need to wait
61592          // for both main and audio segment loaders to call endOfStream
61593          isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
61594        } else {
61595          // otherwise just rely on the audio loader
61596          isEndOfStream = this.audioSegmentLoader_.ended_;
61597        }
61598      }
61599      if (!isEndOfStream) {
61600        return;
61601      }
61602      this.stopABRTimer_();
61603      this.sourceUpdater_.endOfStream();
61604    }
61605    /**
61606     * Check if a playlist has stopped being updated
61607     *
61608     * @param {Object} playlist the media playlist object
61609     * @return {boolean} whether the playlist has stopped being updated or not
61610     */
61611
61612    stuckAtPlaylistEnd_(playlist) {
61613      const seekable = this.seekable();
61614      if (!seekable.length) {
61615        // playlist doesn't have enough information to determine whether we are stuck
61616        return false;
61617      }
61618      const expired = this.syncController_.getExpiredTime(playlist, this.duration());
61619      if (expired === null) {
61620        return false;
61621      } // does not use the safe live end to calculate playlist end, since we
61622      // don't want to say we are stuck while there is still content
61623
61624      const absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
61625      const currentTime = this.tech_.currentTime();
61626      const buffered = this.tech_.buffered();
61627      if (!buffered.length) {
61628        // return true if the playhead reached the absolute end of the playlist
61629        return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
61630      }
61631      const bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
61632      // end of playlist
61633
61634      return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
61635    }
61636    /**
61637     * Exclude a playlist for a set amount of time, making it unavailable for selection by
61638     * the rendition selection algorithm, then force a new playlist (rendition) selection.
61639     *
61640     * @param {Object=} playlistToExclude
61641     *                  the playlist to exclude, defaults to the currently selected playlist
61642     * @param {Object=} error
61643     *                  an optional error
61644     * @param {number=} playlistExclusionDuration
61645     *                  an optional number of seconds to exclude the playlist
61646     */
61647
61648    excludePlaylist({
61649      playlistToExclude = this.mainPlaylistLoader_.media(),
61650      error = {},
61651      playlistExclusionDuration
61652    }) {
61653      // If the `error` was generated by the playlist loader, it will contain
61654      // the playlist we were trying to load (but failed) and that should be
61655      // excluded instead of the currently selected playlist which is likely
61656      // out-of-date in this scenario
61657      playlistToExclude = playlistToExclude || this.mainPlaylistLoader_.media();
61658      playlistExclusionDuration = playlistExclusionDuration || error.playlistExclusionDuration || this.playlistExclusionDuration; // If there is no current playlist, then an error occurred while we were
61659      // trying to load the main OR while we were disposing of the tech
61660
61661      if (!playlistToExclude) {
61662        this.error = error;
61663        if (this.mediaSource.readyState !== 'open') {
61664          this.trigger('error');
61665        } else {
61666          this.sourceUpdater_.endOfStream('network');
61667        }
61668        return;
61669      }
61670      playlistToExclude.playlistErrors_++;
61671      const playlists = this.mainPlaylistLoader_.main.playlists;
61672      const enabledPlaylists = playlists.filter(isEnabled);
61673      const isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === playlistToExclude; // Don't exclude the only playlist unless it was excluded
61674      // forever
61675
61676      if (playlists.length === 1 && playlistExclusionDuration !== Infinity) {
61677        videojs.log.warn(`Problem encountered with playlist ${playlistToExclude.id}. ` + 'Trying again since it is the only playlist.');
61678        this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
61679
61680        return this.mainPlaylistLoader_.load(isFinalRendition);
61681      }
61682      if (isFinalRendition) {
61683        // If we're content steering, try other pathways.
61684        if (this.main().contentSteering) {
61685          const pathway = this.pathwayAttribute_(playlistToExclude); // Ignore at least 1 steering manifest refresh.
61686
61687          const reIncludeDelay = this.contentSteeringController_.steeringManifest.ttl * 1000;
61688          this.contentSteeringController_.excludePathway(pathway);
61689          this.excludeThenChangePathway_();
61690          setTimeout(() => {
61691            this.contentSteeringController_.addAvailablePathway(pathway);
61692          }, reIncludeDelay);
61693          return;
61694        } // Since we're on the final non-excluded playlist, and we're about to exclude
61695        // it, instead of erring the player or retrying this playlist, clear out the current
61696        // exclusion list. This allows other playlists to be attempted in case any have been
61697        // fixed.
61698
61699        let reincluded = false;
61700        playlists.forEach(playlist => {
61701          // skip current playlist which is about to be excluded
61702          if (playlist === playlistToExclude) {
61703            return;
61704          }
61705          const excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
61706
61707          if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
61708            reincluded = true;
61709            delete playlist.excludeUntil;
61710          }
61711        });
61712        if (reincluded) {
61713          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
61714          // playlist. This is needed for users relying on the retryplaylist event to catch a
61715          // case where the player might be stuck and looping through "dead" playlists.
61716
61717          this.tech_.trigger('retryplaylist');
61718        }
61719      } // Exclude this playlist
61720
61721      let excludeUntil;
61722      if (playlistToExclude.playlistErrors_ > this.maxPlaylistRetries) {
61723        excludeUntil = Infinity;
61724      } else {
61725        excludeUntil = Date.now() + playlistExclusionDuration * 1000;
61726      }
61727      playlistToExclude.excludeUntil = excludeUntil;
61728      if (error.reason) {
61729        playlistToExclude.lastExcludeReason_ = error.reason;
61730      }
61731      this.tech_.trigger('excludeplaylist');
61732      this.tech_.trigger({
61733        type: 'usage',
61734        name: 'vhs-rendition-excluded'
61735      }); // TODO: only load a new playlist if we're excluding the current playlist
61736      // If this function was called with a playlist that's not the current active playlist
61737      // (e.g., media().id !== playlistToExclude.id),
61738      // then a new playlist should not be selected and loaded, as there's nothing wrong with the current playlist.
61739
61740      const nextPlaylist = this.selectPlaylist();
61741      if (!nextPlaylist) {
61742        this.error = 'Playback cannot continue. No available working or supported playlists.';
61743        this.trigger('error');
61744        return;
61745      }
61746      const logFn = error.internal ? this.logger_ : videojs.log.warn;
61747      const errorMessage = error.message ? ' ' + error.message : '';
61748      logFn(`${error.internal ? 'Internal problem' : 'Problem'} encountered with playlist ${playlistToExclude.id}.` + `${errorMessage} Switching to playlist ${nextPlaylist.id}.`); // if audio group changed reset audio loaders
61749
61750      if (nextPlaylist.attributes.AUDIO !== playlistToExclude.attributes.AUDIO) {
61751        this.delegateLoaders_('audio', ['abort', 'pause']);
61752      } // if subtitle group changed reset subtitle loaders
61753
61754      if (nextPlaylist.attributes.SUBTITLES !== playlistToExclude.attributes.SUBTITLES) {
61755        this.delegateLoaders_('subtitle', ['abort', 'pause']);
61756      }
61757      this.delegateLoaders_('main', ['abort', 'pause']);
61758      const delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
61759      const 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
61760
61761      return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
61762    }
61763    /**
61764     * Pause all segment/playlist loaders
61765     */
61766
61767    pauseLoading() {
61768      this.delegateLoaders_('all', ['abort', 'pause']);
61769      this.stopABRTimer_();
61770    }
61771    /**
61772     * Call a set of functions in order on playlist loaders, segment loaders,
61773     * or both types of loaders.
61774     *
61775     * @param {string} filter
61776     *        Filter loaders that should call fnNames using a string. Can be:
61777     *        * all - run on all loaders
61778     *        * audio - run on all audio loaders
61779     *        * subtitle - run on all subtitle loaders
61780     *        * main - run on the main loaders
61781     *
61782     * @param {Array|string} fnNames
61783     *        A string or array of function names to call.
61784     */
61785
61786    delegateLoaders_(filter, fnNames) {
61787      const loaders = [];
61788      const dontFilterPlaylist = filter === 'all';
61789      if (dontFilterPlaylist || filter === 'main') {
61790        loaders.push(this.mainPlaylistLoader_);
61791      }
61792      const mediaTypes = [];
61793      if (dontFilterPlaylist || filter === 'audio') {
61794        mediaTypes.push('AUDIO');
61795      }
61796      if (dontFilterPlaylist || filter === 'subtitle') {
61797        mediaTypes.push('CLOSED-CAPTIONS');
61798        mediaTypes.push('SUBTITLES');
61799      }
61800      mediaTypes.forEach(mediaType => {
61801        const loader = this.mediaTypes_[mediaType] && this.mediaTypes_[mediaType].activePlaylistLoader;
61802        if (loader) {
61803          loaders.push(loader);
61804        }
61805      });
61806      ['main', 'audio', 'subtitle'].forEach(name => {
61807        const loader = this[`${name}SegmentLoader_`];
61808        if (loader && (filter === name || filter === 'all')) {
61809          loaders.push(loader);
61810        }
61811      });
61812      loaders.forEach(loader => fnNames.forEach(fnName => {
61813        if (typeof loader[fnName] === 'function') {
61814          loader[fnName]();
61815        }
61816      }));
61817    }
61818    /**
61819     * set the current time on all segment loaders
61820     *
61821     * @param {TimeRange} currentTime the current time to set
61822     * @return {TimeRange} the current time
61823     */
61824
61825    setCurrentTime(currentTime) {
61826      const buffered = findRange(this.tech_.buffered(), currentTime);
61827      if (!(this.mainPlaylistLoader_ && this.mainPlaylistLoader_.media())) {
61828        // return immediately if the metadata is not ready yet
61829        return 0;
61830      } // it's clearly an edge-case but don't thrown an error if asked to
61831      // seek within an empty playlist
61832
61833      if (!this.mainPlaylistLoader_.media().segments) {
61834        return 0;
61835      } // if the seek location is already buffered, continue buffering as usual
61836
61837      if (buffered && buffered.length) {
61838        return currentTime;
61839      } // cancel outstanding requests so we begin buffering at the new
61840      // location
61841
61842      this.mainSegmentLoader_.pause();
61843      this.mainSegmentLoader_.resetEverything();
61844      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61845        this.audioSegmentLoader_.pause();
61846        this.audioSegmentLoader_.resetEverything();
61847      }
61848      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
61849        this.subtitleSegmentLoader_.pause();
61850        this.subtitleSegmentLoader_.resetEverything();
61851      } // start segment loader loading in case they are paused
61852
61853      this.load();
61854    }
61855    /**
61856     * get the current duration
61857     *
61858     * @return {TimeRange} the duration
61859     */
61860
61861    duration() {
61862      if (!this.mainPlaylistLoader_) {
61863        return 0;
61864      }
61865      const media = this.mainPlaylistLoader_.media();
61866      if (!media) {
61867        // no playlists loaded yet, so can't determine a duration
61868        return 0;
61869      } // Don't rely on the media source for duration in the case of a live playlist since
61870      // setting the native MediaSource's duration to infinity ends up with consequences to
61871      // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
61872      //
61873      // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
61874      // however, few browsers have support for setLiveSeekableRange()
61875      // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
61876      //
61877      // Until a time when the duration of the media source can be set to infinity, and a
61878      // seekable range specified across browsers, just return Infinity.
61879
61880      if (!media.endList) {
61881        return Infinity;
61882      } // Since this is a VOD video, it is safe to rely on the media source's duration (if
61883      // available). If it's not available, fall back to a playlist-calculated estimate.
61884
61885      if (this.mediaSource) {
61886        return this.mediaSource.duration;
61887      }
61888      return Vhs$1.Playlist.duration(media);
61889    }
61890    /**
61891     * check the seekable range
61892     *
61893     * @return {TimeRange} the seekable range
61894     */
61895
61896    seekable() {
61897      return this.seekable_;
61898    }
61899    
vendor: 3,583 bytes, lines 61899-61970
61899onSyncInfoUpdate_() {
61900      let audioSeekable; // TODO check for creation of both source buffers before updating seekable
61901      //
61902      // A fix was made to this function where a check for
61903      // this.sourceUpdater_.hasCreatedSourceBuffers
61904      // was added to ensure that both source buffers were created before seekable was
61905      // updated. However, it originally had a bug where it was checking for a true and
61906      // returning early instead of checking for false. Setting it to check for false to
61907      // return early though created other issues. A call to play() would check for seekable
61908      // end without verifying that a seekable range was present. In addition, even checking
61909      // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
61910      // due to a media update calling load on the segment loaders, skipping a seek to live,
61911      // thereby starting live streams at the beginning of the stream rather than at the end.
61912      //
61913      // This conditional should be fixed to wait for the creation of two source buffers at
61914      // the same time as the other sections of code are fixed to properly seek to live and
61915      // not throw an error due to checking for a seekable end when no seekable range exists.
61916      //
61917      // For now, fall back to the older behavior, with the understanding that the seekable
61918      // range may not be completely correct, leading to a suboptimal initial live point.
61919
61920      if (!this.mainPlaylistLoader_) {
61921        return;
61922      }
61923      let media = this.mainPlaylistLoader_.media();
61924      if (!media) {
61925        return;
61926      }
61927      let expired = this.syncController_.getExpiredTime(media, this.duration());
61928      if (expired === null) {
61929        // not enough information to update seekable
61930        return;
61931      }
61932      const main = this.mainPlaylistLoader_.main;
61933      const mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
61934      if (mainSeekable.length === 0) {
61935        return;
61936      }
61937      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61938        media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
61939        expired = this.syncController_.getExpiredTime(media, this.duration());
61940        if (expired === null) {
61941          return;
61942        }
61943        audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
61944        if (audioSeekable.length === 0) {
61945          return;
61946        }
61947      }
61948      let oldEnd;
61949      let oldStart;
61950      if (this.seekable_ && this.seekable_.length) {
61951        oldEnd = this.seekable_.end(0);
61952        oldStart = this.seekable_.start(0);
61953      }
61954      if (!audioSeekable) {
61955        // seekable has been calculated based on buffering video data so it
61956        // can be returned directly
61957        this.seekable_ = mainSeekable;
61958      } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
61959        // seekables are pretty far off, rely on main
61960        this.seekable_ = mainSeekable;
61961      } else {
61962        this.seekable_ = 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)]]);
61963      } // seekable is the same as last time
61964
61965      if (this.seekable_ && this.seekable_.length) {
61966        if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
61967          return;
61968        }
61969      }
61970      this.logger_(`seekable updated [${printableRange(this.seekable_)}]`
vendor: 5,513 bytes, lines 61970-62098
61970);
61971      this.tech_.trigger('seekablechanged');
61972    }
61973    /**
61974     * Update the player duration
61975     */
61976
61977    updateDuration(isLive) {
61978      if (this.updateDuration_) {
61979        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
61980        this.updateDuration_ = null;
61981      }
61982      if (this.mediaSource.readyState !== 'open') {
61983        this.updateDuration_ = this.updateDuration.bind(this, isLive);
61984        this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
61985        return;
61986      }
61987      if (isLive) {
61988        const seekable = this.seekable();
61989        if (!seekable.length) {
61990          return;
61991        } // Even in the case of a live playlist, the native MediaSource's duration should not
61992        // be set to Infinity (even though this would be expected for a live playlist), since
61993        // setting the native MediaSource's duration to infinity ends up with consequences to
61994        // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
61995        //
61996        // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
61997        // however, few browsers have support for setLiveSeekableRange()
61998        // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
61999        //
62000        // Until a time when the duration of the media source can be set to infinity, and a
62001        // seekable range specified across browsers, the duration should be greater than or
62002        // equal to the last possible seekable value.
62003        // MediaSource duration starts as NaN
62004        // It is possible (and probable) that this case will never be reached for many
62005        // sources, since the MediaSource reports duration as the highest value without
62006        // accounting for timestamp offset. For example, if the timestamp offset is -100 and
62007        // we buffered times 0 to 100 with real times of 100 to 200, even though current
62008        // time will be between 0 and 100, the native media source may report the duration
62009        // as 200. However, since we report duration separate from the media source (as
62010        // Infinity), and as long as the native media source duration value is greater than
62011        // our reported seekable range, seeks will work as expected. The large number as
62012        // duration for live is actually a strategy used by some players to work around the
62013        // issue of live seekable ranges cited above.
62014
62015        if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
62016          this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
62017        }
62018        return;
62019      }
62020      const buffered = this.tech_.buffered();
62021      let duration = Vhs$1.Playlist.duration(this.mainPlaylistLoader_.media());
62022      if (buffered.length > 0) {
62023        duration = Math.max(duration, buffered.end(buffered.length - 1));
62024      }
62025      if (this.mediaSource.duration !== duration) {
62026        this.sourceUpdater_.setDuration(duration);
62027      }
62028    }
62029    /**
62030     * dispose of the PlaylistController and everything
62031     * that it controls
62032     */
62033
62034    dispose() {
62035      this.trigger('dispose');
62036      this.decrypter_.terminate();
62037      this.mainPlaylistLoader_.dispose();
62038      this.mainSegmentLoader_.dispose();
62039      this.contentSteeringController_.dispose();
62040      this.keyStatusMap_.clear();
62041      if (this.loadOnPlay_) {
62042        this.tech_.off('play', this.loadOnPlay_);
62043      }
62044      ['AUDIO', 'SUBTITLES'].forEach(type => {
62045        const groups = this.mediaTypes_[type].groups;
62046        for (const id in groups) {
62047          groups[id].forEach(group => {
62048            if (group.playlistLoader) {
62049              group.playlistLoader.dispose();
62050            }
62051          });
62052        }
62053      });
62054      this.audioSegmentLoader_.dispose();
62055      this.subtitleSegmentLoader_.dispose();
62056      this.sourceUpdater_.dispose();
62057      this.timelineChangeController_.dispose();
62058      this.stopABRTimer_();
62059      if (this.updateDuration_) {
62060        this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
62061      }
62062      this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
62063
62064      this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
62065      this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
62066      this.off();
62067    }
62068    /**
62069     * return the main playlist object if we have one
62070     *
62071     * @return {Object} the main playlist object that we parsed
62072     */
62073
62074    main() {
62075      return this.mainPlaylistLoader_.main;
62076    }
62077    /**
62078     * return the currently selected playlist
62079     *
62080     * @return {Object} the currently selected playlist object that we parsed
62081     */
62082
62083    media() {
62084      // playlist loader will not return media if it has not been fully loaded
62085      return this.mainPlaylistLoader_.media() || this.initialMedia_;
62086    }
62087    areMediaTypesKnown_() {
62088      const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62089      const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
62090      // otherwise check on the segment loader.
62091
62092      const hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
62093
62094      if (!hasMainMediaInfo || !hasAudioMediaInfo) {
62095        return false;
62096      }
62097      return true;
62098    }
vendor: 6,207 bytes, lines 62098-62246
62098
62099    getCodecsOrExclude_() {
62100      const media = {
62101        main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
62102        audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
62103      };
62104      const playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
62105
62106      media.video = media.main;
62107      const playlistCodecs = codecsForPlaylist(this.main(), playlist);
62108      const codecs = {};
62109      const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62110      if (media.main.hasVideo) {
62111        codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC;
62112      }
62113      if (media.main.isMuxed) {
62114        codecs.video += `,${playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC}`;
62115      }
62116      if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
62117        codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
62118
62119        media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
62120      } // no codecs, no playback.
62121
62122      if (!codecs.audio && !codecs.video) {
62123        this.excludePlaylist({
62124          playlistToExclude: playlist,
62125          error: {
62126            message: 'Could not determine codecs for playlist.'
62127          },
62128          playlistExclusionDuration: Infinity
62129        });
62130        return;
62131      } // fmp4 relies on browser support, while ts relies on muxer support
62132
62133      const supportFunction = (isFmp4, codec) => isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec);
62134      const unsupportedCodecs = {};
62135      let unsupportedAudio;
62136      ['video', 'audio'].forEach(function (type) {
62137        if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
62138          const supporter = media[type].isFmp4 ? 'browser' : 'muxer';
62139          unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
62140          unsupportedCodecs[supporter].push(codecs[type]);
62141          if (type === 'audio') {
62142            unsupportedAudio = supporter;
62143          }
62144        }
62145      });
62146      if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
62147        const audioGroup = playlist.attributes.AUDIO;
62148        this.main().playlists.forEach(variant => {
62149          const variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
62150          if (variantAudioGroup === audioGroup && variant !== playlist) {
62151            variant.excludeUntil = Infinity;
62152          }
62153        });
62154        this.logger_(`excluding audio group ${audioGroup} as ${unsupportedAudio} does not support codec(s): "${codecs.audio}"`);
62155      } // if we have any unsupported codecs exclude this playlist.
62156
62157      if (Object.keys(unsupportedCodecs).length) {
62158        const message = Object.keys(unsupportedCodecs).reduce((acc, supporter) => {
62159          if (acc) {
62160            acc += ', ';
62161          }
62162          acc += `${supporter} does not support codec(s): "${unsupportedCodecs[supporter].join(',')}"`;
62163          return acc;
62164        }, '') + '.';
62165        this.excludePlaylist({
62166          playlistToExclude: playlist,
62167          error: {
62168            internal: true,
62169            message
62170          },
62171          playlistExclusionDuration: Infinity
62172        });
62173        return;
62174      } // check if codec switching is happening
62175
62176      if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
62177        const switchMessages = [];
62178        ['video', 'audio'].forEach(type => {
62179          const newCodec = (parseCodecs(this.sourceUpdater_.codecs[type] || '')[0] || {}).type;
62180          const oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type;
62181          if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
62182            switchMessages.push(`"${this.sourceUpdater_.codecs[type]}" -> "${codecs[type]}"`);
62183          }
62184        });
62185        if (switchMessages.length) {
62186          this.excludePlaylist({
62187            playlistToExclude: playlist,
62188            error: {
62189              message: `Codec switching not supported: ${switchMessages.join(', ')}.`,
62190              internal: true
62191            },
62192            playlistExclusionDuration: Infinity
62193          });
62194          return;
62195        }
62196      } // TODO: when using the muxer shouldn't we just return
62197      // the codecs that the muxer outputs?
62198
62199      return codecs;
62200    }
62201    /**
62202     * Create source buffers and exlude any incompatible renditions.
62203     *
62204     * @private
62205     */
62206
62207    tryToCreateSourceBuffers_() {
62208      // media source is not ready yet or sourceBuffers are already
62209      // created.
62210      if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
62211        return;
62212      }
62213      if (!this.areMediaTypesKnown_()) {
62214        return;
62215      }
62216      const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
62217
62218      if (!codecs) {
62219        return;
62220      }
62221      this.sourceUpdater_.createSourceBuffers(codecs);
62222      const codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
62223      this.excludeIncompatibleVariants_(codecString);
62224    }
62225    /**
62226     * Excludes playlists with codecs that are unsupported by the muxer and browser.
62227     */
62228
62229    excludeUnsupportedVariants_() {
62230      const playlists = this.main().playlists;
62231      const ids = []; // TODO: why don't we have a property to loop through all
62232      // playlist? Why did we ever mix indexes and keys?
62233
62234      Object.keys(playlists).forEach(key => {
62235        const variant = playlists[key]; // check if we already processed this playlist.
62236
62237        if (ids.indexOf(variant.id) !== -1) {
62238          return;
62239        }
62240        ids.push(variant.id);
62241        const codecs = codecsForPlaylist(this.main, variant);
62242        const unsupported = [];
62243        if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) {
62244          unsupported.push(`audio codec ${codecs.audio}`);
62245        }
62246        if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video
vendor: 20,249 bytes, lines 62246-62717
62246)) {
62247          unsupported.push(`video codec ${codecs.video}`);
62248        }
62249        if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
62250          unsupported.push(`text codec ${codecs.text}`);
62251        }
62252        if (unsupported.length) {
62253          variant.excludeUntil = Infinity;
62254          this.logger_(`excluding ${variant.id} for unsupported: ${unsupported.join(', ')}`);
62255        }
62256      });
62257    }
62258    /**
62259     * Exclude playlists that are known to be codec or
62260     * stream-incompatible with the SourceBuffer configuration. For
62261     * instance, Media Source Extensions would cause the video element to
62262     * stall waiting for video data if you switched from a variant with
62263     * video and audio to an audio-only one.
62264     *
62265     * @param {Object} media a media playlist compatible with the current
62266     * set of SourceBuffers. Variants in the current main playlist that
62267     * do not appear to have compatible codec or stream configurations
62268     * will be excluded from the default playlist selection algorithm
62269     * indefinitely.
62270     * @private
62271     */
62272
62273    excludeIncompatibleVariants_(codecString) {
62274      const ids = [];
62275      const playlists = this.main().playlists;
62276      const codecs = unwrapCodecList(parseCodecs(codecString));
62277      const codecCount_ = codecCount(codecs);
62278      const videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null;
62279      const audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null;
62280      Object.keys(playlists).forEach(key => {
62281        const variant = playlists[key]; // check if we already processed this playlist.
62282        // or it if it is already excluded forever.
62283
62284        if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
62285          return;
62286        }
62287        ids.push(variant.id);
62288        const exclusionReasons = []; // get codecs from the playlist for this variant
62289
62290        const variantCodecs = codecsForPlaylist(this.mainPlaylistLoader_.main, variant);
62291        const variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
62292        // variant is incompatible. Wait for mux.js to probe
62293
62294        if (!variantCodecs.audio && !variantCodecs.video) {
62295          return;
62296        } // TODO: we can support this by removing the
62297        // old media source and creating a new one, but it will take some work.
62298        // The number of streams cannot change
62299
62300        if (variantCodecCount !== codecCount_) {
62301          exclusionReasons.push(`codec count "${variantCodecCount}" !== "${codecCount_}"`);
62302        } // only exclude playlists by codec change, if codecs cannot switch
62303        // during playback.
62304
62305        if (!this.sourceUpdater_.canChangeType()) {
62306          const variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null;
62307          const variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
62308
62309          if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
62310            exclusionReasons.push(`video codec "${variantVideoDetails.type}" !== "${videoDetails.type}"`);
62311          } // the audio codec cannot change
62312
62313          if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
62314            exclusionReasons.push(`audio codec "${variantAudioDetails.type}" !== "${audioDetails.type}"`);
62315          }
62316        }
62317        if (exclusionReasons.length) {
62318          variant.excludeUntil = Infinity;
62319          this.logger_(`excluding ${variant.id}: ${exclusionReasons.join(' && ')}`);
62320        }
62321      });
62322    }
62323    updateAdCues_(media) {
62324      let offset = 0;
62325      const seekable = this.seekable();
62326      if (seekable.length) {
62327        offset = seekable.start(0);
62328      }
62329      updateAdCues(media, this.cueTagsTrack_, offset);
62330    }
62331    /**
62332     * Calculates the desired forward buffer length based on current time
62333     *
62334     * @return {number} Desired forward buffer length in seconds
62335     */
62336
62337    goalBufferLength() {
62338      const currentTime = this.tech_.currentTime();
62339      const initial = Config.GOAL_BUFFER_LENGTH;
62340      const rate = Config.GOAL_BUFFER_LENGTH_RATE;
62341      const max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
62342      return Math.min(initial + currentTime * rate, max);
62343    }
62344    /**
62345     * Calculates the desired buffer low water line based on current time
62346     *
62347     * @return {number} Desired buffer low water line in seconds
62348     */
62349
62350    bufferLowWaterLine() {
62351      const currentTime = this.tech_.currentTime();
62352      const initial = Config.BUFFER_LOW_WATER_LINE;
62353      const rate = Config.BUFFER_LOW_WATER_LINE_RATE;
62354      const max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
62355      const newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
62356      return Math.min(initial + currentTime * rate, this.bufferBasedABR ? newMax : max);
62357    }
62358    bufferHighWaterLine() {
62359      return Config.BUFFER_HIGH_WATER_LINE;
62360    }
62361    addDateRangesToTextTrack_(dateRanges) {
62362      createMetadataTrackIfNotExists(this.inbandTextTracks_, 'com.apple.streaming', this.tech_);
62363      addDateRangeMetadata({
62364        inbandTextTracks: this.inbandTextTracks_,
62365        dateRanges
62366      });
62367    }
62368    addMetadataToTextTrack(dispatchType, metadataArray, videoDuration) {
62369      const timestampOffset = this.sourceUpdater_.videoBuffer ? this.sourceUpdater_.videoTimestampOffset() : this.sourceUpdater_.audioTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
62370      // audio/video source with a metadata track, and an alt audio with a metadata track.
62371      // However, this probably won't happen, and if it does it can be handled then.
62372
62373      createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.tech_);
62374      addMetadata({
62375        inbandTextTracks: this.inbandTextTracks_,
62376        metadataArray,
62377        timestampOffset,
62378        videoDuration
62379      });
62380    }
62381    /**
62382     * Utility for getting the pathway or service location from an HLS or DASH playlist.
62383     *
62384     * @param {Object} playlist for getting pathway from.
62385     * @return the pathway attribute of a playlist
62386     */
62387
62388    pathwayAttribute_(playlist) {
62389      return playlist.attributes['PATHWAY-ID'] || playlist.attributes.serviceLocation;
62390    }
62391    /**
62392     * Initialize available pathways and apply the tag properties.
62393     */
62394
62395    initContentSteeringController_() {
62396      const main = this.main();
62397      if (!main.contentSteering) {
62398        return;
62399      }
62400      for (const playlist of main.playlists) {
62401        this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
62402      }
62403      this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering); // request the steering manifest immediately if queryBeforeStart is set.
62404
62405      if (this.contentSteeringController_.queryBeforeStart) {
62406        // When queryBeforeStart is true, initial request should omit steering parameters.
62407        this.contentSteeringController_.requestSteeringManifest(true);
62408        return;
62409      } // otherwise start content steering after playback starts
62410
62411      this.tech_.one('canplay', () => {
62412        this.contentSteeringController_.requestSteeringManifest();
62413      });
62414    }
62415    /**
62416     * Reset the content steering controller and re-init.
62417     */
62418
62419    resetContentSteeringController_() {
62420      this.contentSteeringController_.clearAvailablePathways();
62421      this.contentSteeringController_.dispose();
62422      this.initContentSteeringController_();
62423    }
62424    /**
62425     * Attaches the listeners for content steering.
62426     */
62427
62428    attachContentSteeringListeners_() {
62429      this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this));
62430      if (this.sourceType_ === 'dash') {
62431        this.mainPlaylistLoader_.on('loadedplaylist', () => {
62432          const main = this.main(); // check if steering tag or pathways changed.
62433
62434          const didDashTagChange = this.contentSteeringController_.didDASHTagChange(main.uri, main.contentSteering);
62435          const didPathwaysChange = () => {
62436            const availablePathways = this.contentSteeringController_.getAvailablePathways();
62437            const newPathways = [];
62438            for (const playlist of main.playlists) {
62439              const serviceLocation = playlist.attributes.serviceLocation;
62440              if (serviceLocation) {
62441                newPathways.push(serviceLocation);
62442                if (!availablePathways.has(serviceLocation)) {
62443                  return true;
62444                }
62445              }
62446            } // If we have no new serviceLocations and previously had availablePathways
62447
62448            if (!newPathways.length && availablePathways.size) {
62449              return true;
62450            }
62451            return false;
62452          };
62453          if (didDashTagChange || didPathwaysChange()) {
62454            this.resetContentSteeringController_();
62455          }
62456        });
62457      }
62458    }
62459    /**
62460     * Simple exclude and change playlist logic for content steering.
62461     */
62462
62463    excludeThenChangePathway_() {
62464      const currentPathway = this.contentSteeringController_.getPathway();
62465      if (!currentPathway) {
62466        return;
62467      }
62468      this.handlePathwayClones_();
62469      const main = this.main();
62470      const playlists = main.playlists;
62471      const ids = new Set();
62472      let didEnablePlaylists = false;
62473      Object.keys(playlists).forEach(key => {
62474        const variant = playlists[key];
62475        const pathwayId = this.pathwayAttribute_(variant);
62476        const differentPathwayId = pathwayId && currentPathway !== pathwayId;
62477        const steeringExclusion = variant.excludeUntil === Infinity && variant.lastExcludeReason_ === 'content-steering';
62478        if (steeringExclusion && !differentPathwayId) {
62479          delete variant.excludeUntil;
62480          delete variant.lastExcludeReason_;
62481          didEnablePlaylists = true;
62482        }
62483        const noExcludeUntil = !variant.excludeUntil && variant.excludeUntil !== Infinity;
62484        const shouldExclude = !ids.has(variant.id) && differentPathwayId && noExcludeUntil;
62485        if (!shouldExclude) {
62486          return;
62487        }
62488        ids.add(variant.id);
62489        variant.excludeUntil = Infinity;
62490        variant.lastExcludeReason_ = 'content-steering'; // TODO: kind of spammy, maybe move this.
62491
62492        this.logger_(`excluding ${variant.id} for ${variant.lastExcludeReason_}`);
62493      });
62494      if (this.contentSteeringController_.manifestType_ === 'DASH') {
62495        Object.keys(this.mediaTypes_).forEach(key => {
62496          const type = this.mediaTypes_[key];
62497          if (type.activePlaylistLoader) {
62498            const currentPlaylist = type.activePlaylistLoader.media_; // Check if the current media playlist matches the current CDN
62499
62500            if (currentPlaylist && currentPlaylist.attributes.serviceLocation !== currentPathway) {
62501              didEnablePlaylists = true;
62502            }
62503          }
62504        });
62505      }
62506      if (didEnablePlaylists) {
62507        this.changeSegmentPathway_();
62508      }
62509    }
62510    /**
62511     * Add, update, or delete playlists and media groups for
62512     * the pathway clones for HLS Content Steering.
62513     *
62514     * See https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
62515     *
62516     * NOTE: Pathway cloning does not currently support the `PER_VARIANT_URIS` and
62517     * `PER_RENDITION_URIS` as we do not handle `STABLE-VARIANT-ID` or
62518     * `STABLE-RENDITION-ID` values.
62519     */
62520
62521    handlePathwayClones_() {
62522      const main = this.main();
62523      const playlists = main.playlists;
62524      const currentPathwayClones = this.contentSteeringController_.currentPathwayClones;
62525      const nextPathwayClones = this.contentSteeringController_.nextPathwayClones;
62526      const hasClones = currentPathwayClones && currentPathwayClones.size || nextPathwayClones && nextPathwayClones.size;
62527      if (!hasClones) {
62528        return;
62529      }
62530      for (const [id, clone] of currentPathwayClones.entries()) {
62531        const newClone = nextPathwayClones.get(id); // Delete the old pathway clone.
62532
62533        if (!newClone) {
62534          this.mainPlaylistLoader_.updateOrDeleteClone(clone);
62535          this.contentSteeringController_.excludePathway(id);
62536        }
62537      }
62538      for (const [id, clone] of nextPathwayClones.entries()) {
62539        const oldClone = currentPathwayClones.get(id); // Create a new pathway if it is a new pathway clone object.
62540
62541        if (!oldClone) {
62542          const playlistsToClone = playlists.filter(p => {
62543            return p.attributes['PATHWAY-ID'] === clone['BASE-ID'];
62544          });
62545          playlistsToClone.forEach(p => {
62546            this.mainPlaylistLoader_.addClonePathway(clone, p);
62547          });
62548          this.contentSteeringController_.addAvailablePathway(id);
62549          continue;
62550        } // There have not been changes to the pathway clone object, so skip.
62551
62552        if (this.equalPathwayClones_(oldClone, clone)) {
62553          continue;
62554        } // Update a preexisting cloned pathway.
62555        // True is set for the update flag.
62556
62557        this.mainPlaylistLoader_.updateOrDeleteClone(clone, true);
62558        this.contentSteeringController_.addAvailablePathway(id);
62559      } // Deep copy contents of next to current pathways.
62560
62561      this.contentSteeringController_.currentPathwayClones = new Map(JSON.parse(JSON.stringify([...nextPathwayClones])));
62562    }
62563    /**
62564     * Determines whether two pathway clone objects are equivalent.
62565     *
62566     * @param {Object} a The first pathway clone object.
62567     * @param {Object} b The second pathway clone object.
62568     * @return {boolean} True if the pathway clone objects are equal, false otherwise.
62569     */
62570
62571    equalPathwayClones_(a, b) {
62572      if (a['BASE-ID'] !== b['BASE-ID'] || a.ID !== b.ID || a['URI-REPLACEMENT'].HOST !== b['URI-REPLACEMENT'].HOST) {
62573        return false;
62574      }
62575      const aParams = a['URI-REPLACEMENT'].PARAMS;
62576      const bParams = b['URI-REPLACEMENT'].PARAMS; // We need to iterate through both lists of params because one could be
62577      // missing a parameter that the other has.
62578
62579      for (const p in aParams) {
62580        if (aParams[p] !== bParams[p]) {
62581          return false;
62582        }
62583      }
62584      for (const p in bParams) {
62585        if (aParams[p] !== bParams[p]) {
62586          return false;
62587        }
62588      }
62589      return true;
62590    }
62591    /**
62592     * Changes the current playlists for audio, video and subtitles after a new pathway
62593     * is chosen from content steering.
62594     */
62595
62596    changeSegmentPathway_() {
62597      const nextPlaylist = this.selectPlaylist();
62598      this.pauseLoading(); // Switch audio and text track playlists if necessary in DASH
62599
62600      if (this.contentSteeringController_.manifestType_ === 'DASH') {
62601        this.switchMediaForDASHContentSteering_();
62602      }
62603      this.switchMedia_(nextPlaylist, 'content-steering');
62604    }
62605    /**
62606     * Iterates through playlists and check their keyId set and compare with the
62607     * keyStatusMap, only enable playlists that have a usable key. If the playlist
62608     * has no keyId leave it enabled by default.
62609     */
62610
62611    excludeNonUsablePlaylistsByKeyId_() {
62612      if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
62613        return;
62614      }
62615      let nonUsableKeyStatusCount = 0;
62616      const NON_USABLE = 'non-usable';
62617      this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
62618        const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist); // If the playlist doesn't have keyIDs lets not exclude it.
62619
62620        if (!keyIdSet || !keyIdSet.size) {
62621          return;
62622        }
62623        keyIdSet.forEach(key => {
62624          const USABLE = 'usable';
62625          const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
62626          const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
62627          if (!hasUsableKeyStatus) {
62628            // Only exclude playlists that haven't already been excluded as non-usable.
62629            if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
62630              playlist.excludeUntil = Infinity;
62631              playlist.lastExcludeReason_ = NON_USABLE;
62632              this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
62633            } // count all nonUsableKeyStatus
62634
62635            nonUsableKeyStatusCount++;
62636          } else if (hasUsableKeyStatus && nonUsableExclusion) {
62637            delete playlist.excludeUntil;
62638            delete playlist.lastExcludeReason_;
62639            this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
62640          }
62641        });
62642      }); // If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
62643
62644      if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
62645        this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
62646          const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720;
62647          const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
62648          if (isNonHD && excludedForNonUsableKey) {
62649            // Only delete the excludeUntil so we don't try and re-exclude these playlists.
62650            delete playlist.excludeUntil;
62651            videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`);
62652          }
62653        });
62654      }
62655    }
62656    /**
62657     * Adds a keystatus to the keystatus map, tries to convert to string if necessary.
62658     *
62659     * @param {any} keyId the keyId to add a status for
62660     * @param {string} status the status of the keyId
62661     */
62662
62663    addKeyStatus_(keyId, status) {
62664      const isString = typeof keyId === 'string';
62665      const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
62666      const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
62667      this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`);
62668      this.keyStatusMap_.set(formattedKeyIdString, status);
62669    }
62670    /**
62671     * Utility function for adding key status to the keyStatusMap and filtering usable encrypted playlists.
62672     *
62673     * @param {any} keyId the keyId from the keystatuschange event
62674     * @param {string} status the key status string
62675     */
62676
62677    updatePlaylistByKeyStatus(keyId, status) {
62678      this.addKeyStatus_(keyId, status);
62679      if (!this.waitingForFastQualityPlaylistReceived_) {
62680        this.excludeNonUsableThenChangePlaylist_();
62681      } // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
62682
62683      this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
62684      this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
62685    }
62686    excludeNonUsableThenChangePlaylist_() {
62687      this.excludeNonUsablePlaylistsByKeyId_();
62688      this.fastQualityChange_();
62689    }
62690  }
62691
62692  /**
62693   * Returns a function that acts as the Enable/disable playlist function.
62694   *
62695   * @param {PlaylistLoader} loader - The main playlist loader
62696   * @param {string} playlistID - id of the playlist
62697   * @param {Function} changePlaylistFn - A function to be called after a
62698   * playlist's enabled-state has been changed. Will NOT be called if a
62699   * playlist's enabled-state is unchanged
62700   * @param {boolean=} enable - Value to set the playlist enabled-state to
62701   * or if undefined returns the current enabled-state for the playlist
62702   * @return {Function} Function for setting/getting enabled
62703   */
62704
62705  const enableFunction = (loader, playlistID, changePlaylistFn) => enable => {
62706    const playlist = loader.main.playlists[playlistID];
62707    const incompatible = isIncompatible(playlist);
62708    const currentlyEnabled = isEnabled(playlist);
62709    if (typeof enable === 'undefined') {
62710      return currentlyEnabled;
62711    }
62712    if (enable) {
62713      delete playlist.disabled;
62714    } else {
62715      playlist.disabled = true;
62716    }
62717    
vendor: 12,266 bytes, lines 62717-63012
62717if (enable !== currentlyEnabled && !incompatible) {
62718      // Ensure the outside world knows about our changes
62719      changePlaylistFn();
62720      if (enable) {
62721        loader.trigger('renditionenabled');
62722      } else {
62723        loader.trigger('renditiondisabled');
62724      }
62725    }
62726    return enable;
62727  };
62728  /**
62729   * The representation object encapsulates the publicly visible information
62730   * in a media playlist along with a setter/getter-type function (enabled)
62731   * for changing the enabled-state of a particular playlist entry
62732   *
62733   * @class Representation
62734   */
62735
62736  class Representation {
62737    constructor(vhsHandler, playlist, id) {
62738      const {
62739        playlistController_: pc
62740      } = vhsHandler;
62741      const qualityChangeFunction = pc.fastQualityChange_.bind(pc); // some playlist attributes are optional
62742
62743      if (playlist.attributes) {
62744        const resolution = playlist.attributes.RESOLUTION;
62745        this.width = resolution && resolution.width;
62746        this.height = resolution && resolution.height;
62747        this.bandwidth = playlist.attributes.BANDWIDTH;
62748        this.frameRate = playlist.attributes['FRAME-RATE'];
62749      }
62750      this.codecs = codecsForPlaylist(pc.main(), playlist);
62751      this.playlist = playlist; // The id is simply the ordinality of the media playlist
62752      // within the main playlist
62753
62754      this.id = id; // Partially-apply the enableFunction to create a playlist-
62755      // specific variant
62756
62757      this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
62758    }
62759  }
62760  /**
62761   * A mixin function that adds the `representations` api to an instance
62762   * of the VhsHandler class
62763   *
62764   * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
62765   * representation API into
62766   */
62767
62768  const renditionSelectionMixin = function (vhsHandler) {
62769    // Add a single API-specific function to the VhsHandler instance
62770    vhsHandler.representations = () => {
62771      const main = vhsHandler.playlistController_.main();
62772      const playlists = isAudioOnly(main) ? vhsHandler.playlistController_.getAudioTrackPlaylists_() : main.playlists;
62773      if (!playlists) {
62774        return [];
62775      }
62776      return playlists.filter(media => !isIncompatible(media)).map((e, i) => new Representation(vhsHandler, e, e.id));
62777    };
62778  };
62779
62780  /**
62781   * @file playback-watcher.js
62782   *
62783   * Playback starts, and now my watch begins. It shall not end until my death. I shall
62784   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
62785   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
62786   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
62787   * my life and honor to the Playback Watch, for this Player and all the Players to come.
62788   */
62789
62790  const timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
62791  /**
62792   * @class PlaybackWatcher
62793   */
62794
62795  class PlaybackWatcher {
62796    /**
62797     * Represents an PlaybackWatcher object.
62798     *
62799     * @class
62800     * @param {Object} options an object that includes the tech and settings
62801     */
62802    constructor(options) {
62803      this.playlistController_ = options.playlistController;
62804      this.tech_ = options.tech;
62805      this.seekable = options.seekable;
62806      this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
62807      this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
62808      this.media = options.media;
62809      this.consecutiveUpdates = 0;
62810      this.lastRecordedTime = null;
62811      this.checkCurrentTimeTimeout_ = null;
62812      this.logger_ = logger('PlaybackWatcher');
62813      this.logger_('initialize');
62814      const playHandler = () => this.monitorCurrentTime_();
62815      const canPlayHandler = () => this.monitorCurrentTime_();
62816      const waitingHandler = () => this.techWaiting_();
62817      const cancelTimerHandler = () => this.resetTimeUpdate_();
62818      const pc = this.playlistController_;
62819      const loaderTypes = ['main', 'subtitle', 'audio'];
62820      const loaderChecks = {};
62821      loaderTypes.forEach(type => {
62822        loaderChecks[type] = {
62823          reset: () => this.resetSegmentDownloads_(type),
62824          updateend: () => this.checkSegmentDownloads_(type)
62825        };
62826        pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
62827        // isn't changing we want to reset. We cannot assume that the new rendition
62828        // will also be stalled, until after new appends.
62829
62830        pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
62831        // This prevents one segment playlists (single vtt or single segment content)
62832        // from being detected as stalling. As the buffer will not change in those cases, since
62833        // the buffer is the entire video duration.
62834
62835        this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
62836      });
62837      /**
62838       * We check if a seek was into a gap through the following steps:
62839       * 1. We get a seeking event and we do not get a seeked event. This means that
62840       *    a seek was attempted but not completed.
62841       * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
62842       *    removed everything from our buffer and appended a segment, and should be ready
62843       *    to check for gaps.
62844       */
62845
62846      const setSeekingHandlers = fn => {
62847        ['main', 'audio'].forEach(type => {
62848          pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
62849        });
62850      };
62851      this.seekingAppendCheck_ = () => {
62852        if (this.fixesBadSeeks_()) {
62853          this.consecutiveUpdates = 0;
62854          this.lastRecordedTime = this.tech_.currentTime();
62855          setSeekingHandlers('off');
62856        }
62857      };
62858      this.clearSeekingAppendCheck_ = () => setSeekingHandlers('off');
62859      this.watchForBadSeeking_ = () => {
62860        this.clearSeekingAppendCheck_();
62861        setSeekingHandlers('on');
62862      };
62863      this.tech_.on('seeked', this.clearSeekingAppendCheck_);
62864      this.tech_.on('seeking', this.watchForBadSeeking_);
62865      this.tech_.on('waiting', waitingHandler);
62866      this.tech_.on(timerCancelEvents, cancelTimerHandler);
62867      this.tech_.on('canplay', canPlayHandler);
62868      /*
62869        An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
62870        is surfaced in one of two ways:
62871         1)  The `waiting` event is fired before the player has buffered content, making it impossible
62872            to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
62873            we can check if playback is stalled due to a gap, and skip the gap if necessary.
62874        2)  A source with a gap at the beginning of the stream is loaded programatically while the player
62875            is in a playing state. To catch this case, it's important that our one-time play listener is setup
62876            even if the player is in a playing state
62877      */
62878
62879      this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
62880
62881      this.dispose = () => {
62882        this.clearSeekingAppendCheck_();
62883        this.logger_('dispose');
62884        this.tech_.off('waiting', waitingHandler);
62885        this.tech_.off(timerCancelEvents, cancelTimerHandler);
62886        this.tech_.off('canplay', canPlayHandler);
62887        this.tech_.off('play', playHandler);
62888        this.tech_.off('seeking', this.watchForBadSeeking_);
62889        this.tech_.off('seeked', this.clearSeekingAppendCheck_);
62890        loaderTypes.forEach(type => {
62891          pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
62892          pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
62893          this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
62894        });
62895        if (this.checkCurrentTimeTimeout_) {
62896          window.clearTimeout(this.checkCurrentTimeTimeout_);
62897        }
62898        this.resetTimeUpdate_();
62899      };
62900    }
62901    /**
62902     * Periodically check current time to see if playback stopped
62903     *
62904     * @private
62905     */
62906
62907    monitorCurrentTime_() {
62908      this.checkCurrentTime_();
62909      if (this.checkCurrentTimeTimeout_) {
62910        window.clearTimeout(this.checkCurrentTimeTimeout_);
62911      } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
62912
62913      this.checkCurrentTimeTimeout_ = window.setTimeout(this.monitorCurrentTime_.bind(this), 250);
62914    }
62915    /**
62916     * Reset stalled download stats for a specific type of loader
62917     *
62918     * @param {string} type
62919     *        The segment loader type to check.
62920     *
62921     * @listens SegmentLoader#playlistupdate
62922     * @listens Tech#seeking
62923     * @listens Tech#seeked
62924     */
62925
62926    resetSegmentDownloads_(type) {
62927      const loader = this.playlistController_[`${type}SegmentLoader_`];
62928      if (this[`${type}StalledDownloads_`] > 0) {
62929        this.logger_(`resetting possible stalled download count for ${type} loader`);
62930      }
62931      this[`${type}StalledDownloads_`] = 0;
62932      this[`${type}Buffered_`] = loader.buffered_();
62933    }
62934    /**
62935     * Checks on every segment `appendsdone` to see
62936     * if segment appends are making progress. If they are not
62937     * and we are still downloading bytes. We exclude the playlist.
62938     *
62939     * @param {string} type
62940     *        The segment loader type to check.
62941     *
62942     * @listens SegmentLoader#appendsdone
62943     */
62944
62945    checkSegmentDownloads_(type) {
62946      const pc = this.playlistController_;
62947      const loader = pc[`${type}SegmentLoader_`];
62948      const buffered = loader.buffered_();
62949      const isBufferedDifferent = isRangeDifferent(this[`${type}Buffered_`], buffered);
62950      this[`${type}Buffered_`] = buffered; // if another watcher is going to fix the issue or
62951      // the buffered value for this loader changed
62952      // appends are working
62953
62954      if (isBufferedDifferent) {
62955        this.resetSegmentDownloads_(type);
62956        return;
62957      }
62958      this[`${type}StalledDownloads_`]++;
62959      this.logger_(`found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`, {
62960        playlistId: loader.playlist_ && loader.playlist_.id,
62961        buffered: timeRangesToArray(buffered)
62962      }); // after 10 possibly stalled appends with no reset, exclude
62963
62964      if (this[`${type}StalledDownloads_`] < 10) {
62965        return;
62966      }
62967      this.logger_(`${type} loader stalled download exclusion`);
62968      this.resetSegmentDownloads_(type);
62969      this.tech_.trigger({
62970        type: 'usage',
62971        name: `vhs-${type}-download-exclusion`
62972      });
62973      if (type === 'subtitle') {
62974        return;
62975      } // TODO: should we exclude audio tracks rather than main tracks
62976      // when type is audio?
62977
62978      pc.excludePlaylist({
62979        error: {
62980          message: `Excessive ${type} segment downloading detected.`
62981        },
62982        playlistExclusionDuration: Infinity
62983      });
62984    }
62985    /**
62986     * The purpose of this function is to emulate the "waiting" event on
62987     * browsers that do not emit it when they are waiting for more
62988     * data to continue playback
62989     *
62990     * @private
62991     */
62992
62993    checkCurrentTime_() {
62994      if (this.tech_.paused() || this.tech_.seeking()) {
62995        return;
62996      }
62997      const currentTime = this.tech_.currentTime();
62998      const buffered = this.tech_.buffered();
62999      if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
63000        // If current time is at the end of the final buffered region, then any playback
63001        // stall is most likely caused by buffering in a low bandwidth environment. The tech
63002        // should fire a `waiting` event in this scenario, but due to browser and tech
63003        // inconsistencies. Calling `techWaiting_` here allows us to simulate
63004        // responding to a native `waiting` event when the tech fails to emit one.
63005        return this.techWaiting_();
63006      }
63007      if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
63008        this.consecutiveUpdates++;
63009        this.waiting_();
63010      } else if (currentTime === this.lastRecordedTime) {
63011        this.consecutiveUpdates++;
63012      } else {
vendor: 11,124 bytes, lines 63012-63273
63012
63013        this.consecutiveUpdates = 0;
63014        this.lastRecordedTime = currentTime;
63015      }
63016    }
63017    /**
63018     * Resets the 'timeupdate' mechanism designed to detect that we are stalled
63019     *
63020     * @private
63021     */
63022
63023    resetTimeUpdate_() {
63024      this.consecutiveUpdates = 0;
63025    }
63026    /**
63027     * Fixes situations where there's a bad seek
63028     *
63029     * @return {boolean} whether an action was taken to fix the seek
63030     * @private
63031     */
63032
63033    fixesBadSeeks_() {
63034      const seeking = this.tech_.seeking();
63035      if (!seeking) {
63036        return false;
63037      } // TODO: It's possible that these seekable checks should be moved out of this function
63038      // and into a function that runs on seekablechange. It's also possible that we only need
63039      // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
63040      // seekable range.
63041
63042      const seekable = this.seekable();
63043      const currentTime = this.tech_.currentTime();
63044      const isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
63045      let seekTo;
63046      if (isAfterSeekableRange) {
63047        const seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
63048
63049        seekTo = seekableEnd;
63050      }
63051      if (this.beforeSeekableWindow_(seekable, currentTime)) {
63052        const seekableStart = seekable.start(0); // sync to the beginning of the live window
63053        // provide a buffer of .1 seconds to handle rounding/imprecise numbers
63054
63055        seekTo = seekableStart + (
63056        // if the playlist is too short and the seekable range is an exact time (can
63057        // happen in live with a 3 segment playlist), then don't use a time delta
63058        seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
63059      }
63060      if (typeof seekTo !== 'undefined') {
63061        this.logger_(`Trying to seek outside of seekable at time ${currentTime} with ` + `seekable range ${printableRange(seekable)}. Seeking to ` + `${seekTo}.`);
63062        this.tech_.setCurrentTime(seekTo);
63063        return true;
63064      }
63065      const sourceUpdater = this.playlistController_.sourceUpdater_;
63066      const buffered = this.tech_.buffered();
63067      const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
63068      const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
63069      const media = this.media(); // verify that at least two segment durations or one part duration have been
63070      // appended before checking for a gap.
63071
63072      const minAppendedDuration = media.partTargetDuration ? media.partTargetDuration : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
63073      // appended before checking for a gap.
63074
63075      const bufferedToCheck = [audioBuffered, videoBuffered];
63076      for (let i = 0; i < bufferedToCheck.length; i++) {
63077        // skip null buffered
63078        if (!bufferedToCheck[i]) {
63079          continue;
63080        }
63081        const timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
63082        // duration behind we haven't appended enough to call this a bad seek.
63083
63084        if (timeAhead < minAppendedDuration) {
63085          return false;
63086        }
63087      }
63088      const nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
63089      // to seek over the gap
63090
63091      if (nextRange.length === 0) {
63092        return false;
63093      }
63094      seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
63095      this.logger_(`Buffered region starts (${nextRange.start(0)}) ` + ` just beyond seek point (${currentTime}). Seeking to ${seekTo}.`);
63096      this.tech_.setCurrentTime(seekTo);
63097      return true;
63098    }
63099    /**
63100     * Handler for situations when we determine the player is waiting.
63101     *
63102     * @private
63103     */
63104
63105    waiting_() {
63106      if (this.techWaiting_()) {
63107        return;
63108      } // All tech waiting checks failed. Use last resort correction
63109
63110      const currentTime = this.tech_.currentTime();
63111      const buffered = this.tech_.buffered();
63112      const currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
63113      // region with no indication that anything is amiss (seen in Firefox). Seeking to
63114      // currentTime is usually enough to kickstart the player. This checks that the player
63115      // is currently within a buffered region before attempting a corrective seek.
63116      // Chrome does not appear to continue `timeupdate` events after a `waiting` event
63117      // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
63118      // make sure there is ~3 seconds of forward buffer before taking any corrective action
63119      // to avoid triggering an `unknownwaiting` event when the network is slow.
63120
63121      if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
63122        this.resetTimeUpdate_();
63123        this.tech_.setCurrentTime(currentTime);
63124        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
63125
63126        this.tech_.trigger({
63127          type: 'usage',
63128          name: 'vhs-unknown-waiting'
63129        });
63130        return;
63131      }
63132    }
63133    /**
63134     * Handler for situations when the tech fires a `waiting` event
63135     *
63136     * @return {boolean}
63137     *         True if an action (or none) was needed to correct the waiting. False if no
63138     *         checks passed
63139     * @private
63140     */
63141
63142    techWaiting_() {
63143      const seekable = this.seekable();
63144      const currentTime = this.tech_.currentTime();
63145      if (this.tech_.seeking()) {
63146        // Tech is seeking or already waiting on another action, no action needed
63147        return true;
63148      }
63149      if (this.beforeSeekableWindow_(seekable, currentTime)) {
63150        const livePoint = seekable.end(seekable.length - 1);
63151        this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` + `live point (seekable end) ${livePoint}`);
63152        this.resetTimeUpdate_();
63153        this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
63154
63155        this.tech_.trigger({
63156          type: 'usage',
63157          name: 'vhs-live-resync'
63158        });
63159        return true;
63160      }
63161      const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
63162      const buffered = this.tech_.buffered();
63163      const videoUnderflow = this.videoUnderflow_({
63164        audioBuffered: sourceUpdater.audioBuffered(),
63165        videoBuffered: sourceUpdater.videoBuffered(),
63166        currentTime
63167      });
63168      if (videoUnderflow) {
63169        // Even though the video underflowed and was stuck in a gap, the audio overplayed
63170        // the gap, leading currentTime into a buffered range. Seeking to currentTime
63171        // allows the video to catch up to the audio position without losing any audio
63172        // (only suffering ~3 seconds of frozen video and a pause in audio playback).
63173        this.resetTimeUpdate_();
63174        this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
63175
63176        this.tech_.trigger({
63177          type: 'usage',
63178          name: 'vhs-video-underflow'
63179        });
63180        return true;
63181      }
63182      const nextRange = findNextRange(buffered, currentTime); // check for gap
63183
63184      if (nextRange.length > 0) {
63185        this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
63186        this.resetTimeUpdate_();
63187        this.skipTheGap_(currentTime);
63188        return true;
63189      } // All checks failed. Returning false to indicate failure to correct waiting
63190
63191      return false;
63192    }
63193    afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow = false) {
63194      if (!seekable.length) {
63195        // we can't make a solid case if there's no seekable, default to false
63196        return false;
63197      }
63198      let allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
63199      const isLive = !playlist.endList;
63200      const isLLHLS = typeof playlist.partTargetDuration === 'number';
63201      if (isLive && (isLLHLS || allowSeeksWithinUnsafeLiveWindow)) {
63202        allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
63203      }
63204      if (currentTime > allowedEnd) {
63205        return true;
63206      }
63207      return false;
63208    }
63209    beforeSeekableWindow_(seekable, currentTime) {
63210      if (seekable.length &&
63211      // can't fall before 0 and 0 seekable start identifies VOD stream
63212      seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
63213        return true;
63214      }
63215      return false;
63216    }
63217    videoUnderflow_({
63218      videoBuffered,
63219      audioBuffered,
63220      currentTime
63221    }) {
63222      // audio only content will not have video underflow :)
63223      if (!videoBuffered) {
63224        return;
63225      }
63226      let gap; // find a gap in demuxed content.
63227
63228      if (videoBuffered.length && audioBuffered.length) {
63229        // in Chrome audio will continue to play for ~3s when we run out of video
63230        // so we have to check that the video buffer did have some buffer in the
63231        // past.
63232        const lastVideoRange = findRange(videoBuffered, currentTime - 3);
63233        const videoRange = findRange(videoBuffered, currentTime);
63234        const audioRange = findRange(audioBuffered, currentTime);
63235        if (audioRange.length && !videoRange.length && lastVideoRange.length) {
63236          gap = {
63237            start: lastVideoRange.end(0),
63238            end: audioRange.end(0)
63239          };
63240        } // find a gap in muxed content.
63241      } else {
63242        const nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
63243        // stuck in a gap due to video underflow.
63244
63245        if (!nextRange.length) {
63246          gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
63247        }
63248      }
63249      if (gap) {
63250        this.logger_(`Encountered a gap in video from ${gap.start} to ${gap.end}. ` + `Seeking to current time ${currentTime}`);
63251        return true;
63252      }
63253      return false;
63254    }
63255    /**
63256     * Timer callback. If playback still has not proceeded, then we seek
63257     * to the start of the next buffered region.
63258     *
63259     * @private
63260     */
63261
63262    skipTheGap_(scheduledCurrentTime) {
63263      const buffered = this.tech_.buffered();
63264      const currentTime = this.tech_.currentTime();
63265      const nextRange = findNextRange(buffered, currentTime);
63266      this.resetTimeUpdate_();
63267      if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
63268        return;
63269      }
63270      this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
63271
63272      this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
63273      
vendor: 5,726 bytes, lines 63273-63449
63273this.tech_.trigger({
63274        type: 'usage',
63275        name: 'vhs-gap-skip'
63276      });
63277    }
63278    gapFromVideoUnderflow_(buffered, currentTime) {
63279      // At least in Chrome, if there is a gap in the video buffer, the audio will continue
63280      // playing for ~3 seconds after the video gap starts. This is done to account for
63281      // video buffer underflow/underrun (note that this is not done when there is audio
63282      // buffer underflow/underrun -- in that case the video will stop as soon as it
63283      // encounters the gap, as audio stalls are more noticeable/jarring to a user than
63284      // video stalls). The player's time will reflect the playthrough of audio, so the
63285      // time will appear as if we are in a buffered region, even if we are stuck in a
63286      // "gap."
63287      //
63288      // Example:
63289      // video buffer:   0 => 10.1, 10.2 => 20
63290      // audio buffer:   0 => 20
63291      // overall buffer: 0 => 10.1, 10.2 => 20
63292      // current time: 13
63293      //
63294      // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
63295      // however, the audio continued playing until it reached ~3 seconds past the gap
63296      // (13 seconds), at which point it stops as well. Since current time is past the
63297      // gap, findNextRange will return no ranges.
63298      //
63299      // To check for this issue, we see if there is a gap that starts somewhere within
63300      // a 3 second range (3 seconds +/- 1 second) back from our current time.
63301      const gaps = findGaps(buffered);
63302      for (let i = 0; i < gaps.length; i++) {
63303        const start = gaps.start(i);
63304        const end = gaps.end(i); // gap is starts no more than 4 seconds back
63305
63306        if (currentTime - start < 4 && currentTime - start > 2) {
63307          return {
63308            start,
63309            end
63310          };
63311        }
63312      }
63313      return null;
63314    }
63315  }
63316  const defaultOptions = {
63317    errorInterval: 30,
63318    getSource(next) {
63319      const tech = this.tech({
63320        IWillNotUseThisInPlugins: true
63321      });
63322      const sourceObj = tech.currentSource_ || this.currentSource();
63323      return next(sourceObj);
63324    }
63325  };
63326  /**
63327   * Main entry point for the plugin
63328   *
63329   * @param {Player} player a reference to a videojs Player instance
63330   * @param {Object} [options] an object with plugin options
63331   * @private
63332   */
63333
63334  const initPlugin = function (player, options) {
63335    let lastCalled = 0;
63336    let seekTo = 0;
63337    const localOptions = merge(defaultOptions, options);
63338    player.ready(() => {
63339      player.trigger({
63340        type: 'usage',
63341        name: 'vhs-error-reload-initialized'
63342      });
63343    });
63344    /**
63345     * Player modifications to perform that must wait until `loadedmetadata`
63346     * has been triggered
63347     *
63348     * @private
63349     */
63350
63351    const loadedMetadataHandler = function () {
63352      if (seekTo) {
63353        player.currentTime(seekTo);
63354      }
63355    };
63356    /**
63357     * Set the source on the player element, play, and seek if necessary
63358     *
63359     * @param {Object} sourceObj An object specifying the source url and mime-type to play
63360     * @private
63361     */
63362
63363    const setSource = function (sourceObj) {
63364      if (sourceObj === null || sourceObj === undefined) {
63365        return;
63366      }
63367      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
63368      player.one('loadedmetadata', loadedMetadataHandler);
63369      player.src(sourceObj);
63370      player.trigger({
63371        type: 'usage',
63372        name: 'vhs-error-reload'
63373      });
63374      player.play();
63375    };
63376    /**
63377     * Attempt to get a source from either the built-in getSource function
63378     * or a custom function provided via the options
63379     *
63380     * @private
63381     */
63382
63383    const errorHandler = function () {
63384      // Do not attempt to reload the source if a source-reload occurred before
63385      // 'errorInterval' time has elapsed since the last source-reload
63386      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
63387        player.trigger({
63388          type: 'usage',
63389          name: 'vhs-error-reload-canceled'
63390        });
63391        return;
63392      }
63393      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
63394        videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
63395        return;
63396      }
63397      lastCalled = Date.now();
63398      return localOptions.getSource.call(player, setSource);
63399    };
63400    /**
63401     * Unbind any event handlers that were bound by the plugin
63402     *
63403     * @private
63404     */
63405
63406    const cleanupEvents = function () {
63407      player.off('loadedmetadata', loadedMetadataHandler);
63408      player.off('error', errorHandler);
63409      player.off('dispose', cleanupEvents);
63410    };
63411    /**
63412     * Cleanup before re-initializing the plugin
63413     *
63414     * @param {Object} [newOptions] an object with plugin options
63415     * @private
63416     */
63417
63418    const reinitPlugin = function (newOptions) {
63419      cleanupEvents();
63420      initPlugin(player, newOptions);
63421    };
63422    player.on('error', errorHandler);
63423    player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
63424    // initializing the plugin
63425
63426    player.reloadSourceOnError = reinitPlugin;
63427  };
63428  /**
63429   * Reload the source when an error is detected as long as there
63430   * wasn't an error previously within the last 30 seconds
63431   *
63432   * @param {Object} [options] an object with plugin options
63433   */
63434
63435  const reloadSourceOnError = function (options) {
63436    initPlugin(this, options);
63437  };
63438  var version$4 = "3.9.1";
63439  var version$3 = "7.0.2";
63440  var version$2 = "1.3.0";
63441  var version$1 = "7.1.0";
63442  var version = "4.0.1";
63443
63444  /**
63445   * @file videojs-http-streaming.js
63446   *
63447   * The main file for the VHS project.
63448   * License: https://github.com/videojs/videojs-http-streaming/blob/main/LICENSE
63449   */
vendor: 20,454 bytes, lines 63449-64004
63449
63450  const Vhs = {
63451    PlaylistLoader,
63452    Playlist,
63453    utils,
63454    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
63455    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
63456    lastBandwidthSelector,
63457    movingAverageBandwidthSelector,
63458    comparePlaylistBandwidth,
63459    comparePlaylistResolution,
63460    xhr: xhrFactory()
63461  }; // Define getter/setters for config properties
63462
63463  Object.keys(Config).forEach(prop => {
63464    Object.defineProperty(Vhs, prop, {
63465      get() {
63466        videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
63467        return Config[prop];
63468      },
63469      set(value) {
63470        videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
63471        if (typeof value !== 'number' || value < 0) {
63472          videojs.log.warn(`value of Vhs.${prop} must be greater than or equal to 0`);
63473          return;
63474        }
63475        Config[prop] = value;
63476      }
63477    });
63478  });
63479  const LOCAL_STORAGE_KEY = 'videojs-vhs';
63480  /**
63481   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
63482   *
63483   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
63484   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
63485   * @function handleVhsMediaChange
63486   */
63487
63488  const handleVhsMediaChange = function (qualityLevels, playlistLoader) {
63489    const newPlaylist = playlistLoader.media();
63490    let selectedIndex = -1;
63491    for (let i = 0; i < qualityLevels.length; i++) {
63492      if (qualityLevels[i].id === newPlaylist.id) {
63493        selectedIndex = i;
63494        break;
63495      }
63496    }
63497    qualityLevels.selectedIndex_ = selectedIndex;
63498    qualityLevels.trigger({
63499      selectedIndex,
63500      type: 'change'
63501    });
63502  };
63503  /**
63504   * Adds quality levels to list once playlist metadata is available
63505   *
63506   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
63507   * @param {Object} vhs Vhs object to listen to for media events.
63508   * @function handleVhsLoadedMetadata
63509   */
63510
63511  const handleVhsLoadedMetadata = function (qualityLevels, vhs) {
63512    vhs.representations().forEach(rep => {
63513      qualityLevels.addQualityLevel(rep);
63514    });
63515    handleVhsMediaChange(qualityLevels, vhs.playlists);
63516  }; // VHS is a source handler, not a tech. Make sure attempts to use it
63517  // as one do not cause exceptions.
63518
63519  Vhs.canPlaySource = function () {
63520    return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
63521  };
63522  const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
63523    if (!keySystemOptions) {
63524      return keySystemOptions;
63525    }
63526    let codecs = {};
63527    if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
63528      codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS));
63529    }
63530    if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
63531      codecs.audio = audioPlaylist.attributes.CODECS;
63532    }
63533    const videoContentType = getMimeForCodec(codecs.video);
63534    const audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
63535
63536    const keySystemContentTypes = {};
63537    for (const keySystem in keySystemOptions) {
63538      keySystemContentTypes[keySystem] = {};
63539      if (audioContentType) {
63540        keySystemContentTypes[keySystem].audioContentType = audioContentType;
63541      }
63542      if (videoContentType) {
63543        keySystemContentTypes[keySystem].videoContentType = videoContentType;
63544      } // Default to using the video playlist's PSSH even though they may be different, as
63545      // videojs-contrib-eme will only accept one in the options.
63546      //
63547      // This shouldn't be an issue for most cases as early intialization will handle all
63548      // unique PSSH values, and if they aren't, then encrypted events should have the
63549      // specific information needed for the unique license.
63550
63551      if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
63552        keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
63553      } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
63554      // so we need to prevent overwriting the URL entirely
63555
63556      if (typeof keySystemOptions[keySystem] === 'string') {
63557        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
63558      }
63559    }
63560    return merge(keySystemOptions, keySystemContentTypes);
63561  };
63562  /**
63563   * @typedef {Object} KeySystems
63564   *
63565   * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
63566   * Note: not all options are listed here.
63567   *
63568   * @property {Uint8Array} [pssh]
63569   *           Protection System Specific Header
63570   */
63571
63572  /**
63573   * Goes through all the playlists and collects an array of KeySystems options objects
63574   * containing each playlist's keySystems and their pssh values, if available.
63575   *
63576   * @param {Object[]} playlists
63577   *        The playlists to look through
63578   * @param {string[]} keySystems
63579   *        The keySystems to collect pssh values for
63580   *
63581   * @return {KeySystems[]}
63582   *         An array of KeySystems objects containing available key systems and their
63583   *         pssh values
63584   */
63585
63586  const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
63587    return playlists.reduce((keySystemsArr, playlist) => {
63588      if (!playlist.contentProtection) {
63589        return keySystemsArr;
63590      }
63591      const keySystemsOptions = keySystems.reduce((keySystemsObj, keySystem) => {
63592        const keySystemOptions = playlist.contentProtection[keySystem];
63593        if (keySystemOptions && keySystemOptions.pssh) {
63594          keySystemsObj[keySystem] = {
63595            pssh: keySystemOptions.pssh
63596          };
63597        }
63598        return keySystemsObj;
63599      }, {});
63600      if (Object.keys(keySystemsOptions).length) {
63601        keySystemsArr.push(keySystemsOptions);
63602      }
63603      return keySystemsArr;
63604    }, []);
63605  };
63606  /**
63607   * Returns a promise that waits for the
63608   * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
63609   *
63610   * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
63611   * browsers.
63612   *
63613   * As per the above ticket, this is particularly important for Chrome, where, if
63614   * unencrypted content is appended before encrypted content and the key session has not
63615   * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
63616   * during playback.
63617   *
63618   * @param {Object} player
63619   *        The player instance
63620   * @param {Object[]} sourceKeySystems
63621   *        The key systems options from the player source
63622   * @param {Object} [audioMedia]
63623   *        The active audio media playlist (optional)
63624   * @param {Object[]} mainPlaylists
63625   *        The playlists found on the main playlist object
63626   *
63627   * @return {Object}
63628   *         Promise that resolves when the key session has been created
63629   */
63630
63631  const waitForKeySessionCreation = ({
63632    player,
63633    sourceKeySystems,
63634    audioMedia,
63635    mainPlaylists
63636  }) => {
63637    if (!player.eme.initializeMediaKeys) {
63638      return Promise.resolve();
63639    } // TODO should all audio PSSH values be initialized for DRM?
63640    //
63641    // All unique video rendition pssh values are initialized for DRM, but here only
63642    // the initial audio playlist license is initialized. In theory, an encrypted
63643    // event should be fired if the user switches to an alternative audio playlist
63644    // where a license is required, but this case hasn't yet been tested. In addition, there
63645    // may be many alternate audio playlists unlikely to be used (e.g., multiple different
63646    // languages).
63647
63648    const playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
63649    const keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
63650    const initializationFinishedPromises = [];
63651    const keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
63652    // only place where it should not be deduped is for ms-prefixed APIs, but
63653    // the existence of modern EME APIs in addition to
63654    // ms-prefixed APIs on Edge should prevent this from being a concern.
63655    // initializeMediaKeys also won't use the webkit-prefixed APIs.
63656
63657    keySystemsOptionsArr.forEach(keySystemsOptions => {
63658      keySessionCreatedPromises.push(new Promise((resolve, reject) => {
63659        player.tech_.one('keysessioncreated', resolve);
63660      }));
63661      initializationFinishedPromises.push(new Promise((resolve, reject) => {
63662        player.eme.initializeMediaKeys({
63663          keySystems: keySystemsOptions
63664        }, err => {
63665          if (err) {
63666            reject(err);
63667            return;
63668          }
63669          resolve();
63670        });
63671      }));
63672    }); // The reasons Promise.race is chosen over Promise.any:
63673    //
63674    // * Promise.any is only available in Safari 14+.
63675    // * None of these promises are expected to reject. If they do reject, it might be
63676    //   better here for the race to surface the rejection, rather than mask it by using
63677    //   Promise.any.
63678
63679    return Promise.race([
63680    // If a session was previously created, these will all finish resolving without
63681    // creating a new session, otherwise it will take until the end of all license
63682    // requests, which is why the key session check is used (to make setup much faster).
63683    Promise.all(initializationFinishedPromises),
63684    // Once a single session is created, the browser knows DRM will be used.
63685    Promise.race(keySessionCreatedPromises)]);
63686  };
63687  /**
63688   * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
63689   * there are keySystems on the source, sets up source options to prepare the source for
63690   * eme.
63691   *
63692   * @param {Object} player
63693   *        The player instance
63694   * @param {Object[]} sourceKeySystems
63695   *        The key systems options from the player source
63696   * @param {Object} media
63697   *        The active media playlist
63698   * @param {Object} [audioMedia]
63699   *        The active audio media playlist (optional)
63700   *
63701   * @return {boolean}
63702   *         Whether or not options were configured and EME is available
63703   */
63704
63705  const setupEmeOptions = ({
63706    player,
63707    sourceKeySystems,
63708    media,
63709    audioMedia
63710  }) => {
63711    const sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
63712    if (!sourceOptions) {
63713      return false;
63714    }
63715    player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
63716    // do nothing.
63717
63718    if (sourceOptions && !player.eme) {
63719      videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
63720      return false;
63721    }
63722    return true;
63723  };
63724  const getVhsLocalStorage = () => {
63725    if (!window.localStorage) {
63726      return null;
63727    }
63728    const storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY);
63729    if (!storedObject) {
63730      return null;
63731    }
63732    try {
63733      return JSON.parse(storedObject);
63734    } catch (e) {
63735      // someone may have tampered with the value
63736      return null;
63737    }
63738  };
63739  const updateVhsLocalStorage = options => {
63740    if (!window.localStorage) {
63741      return false;
63742    }
63743    let objectToStore = getVhsLocalStorage();
63744    objectToStore = objectToStore ? merge(objectToStore, options) : options;
63745    try {
63746      window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
63747    } catch (e) {
63748      // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
63749      // storage is set to 0).
63750      // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
63751      // No need to perform any operation.
63752      return false;
63753    }
63754    return objectToStore;
63755  };
63756  /**
63757   * Parses VHS-supported media types from data URIs. See
63758   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
63759   * for information on data URIs.
63760   *
63761   * @param {string} dataUri
63762   *        The data URI
63763   *
63764   * @return {string|Object}
63765   *         The parsed object/string, or the original string if no supported media type
63766   *         was found
63767   */
63768
63769  const expandDataUri = dataUri => {
63770    if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
63771      return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
63772    } // no known case for this data URI, return the string as-is
63773
63774    return dataUri;
63775  };
63776  /**
63777   * Adds a request hook to an xhr object
63778   *
63779   * @param {Object} xhr object to add the onRequest hook to
63780   * @param {function} callback hook function for an xhr request
63781   */
63782
63783  const addOnRequestHook = (xhr, callback) => {
63784    if (!xhr._requestCallbackSet) {
63785      xhr._requestCallbackSet = new Set();
63786    }
63787    xhr._requestCallbackSet.add(callback);
63788  };
63789  /**
63790   * Adds a response hook to an xhr object
63791   *
63792   * @param {Object} xhr object to add the onResponse hook to
63793   * @param {function} callback hook function for an xhr response
63794   */
63795
63796  const addOnResponseHook = (xhr, callback) => {
63797    if (!xhr._responseCallbackSet) {
63798      xhr._responseCallbackSet = new Set();
63799    }
63800    xhr._responseCallbackSet.add(callback);
63801  };
63802  /**
63803   * Removes a request hook on an xhr object, deletes the onRequest set if empty.
63804   *
63805   * @param {Object} xhr object to remove the onRequest hook from
63806   * @param {function} callback hook function to remove
63807   */
63808
63809  const removeOnRequestHook = (xhr, callback) => {
63810    if (!xhr._requestCallbackSet) {
63811      return;
63812    }
63813    xhr._requestCallbackSet.delete(callback);
63814    if (!xhr._requestCallbackSet.size) {
63815      delete xhr._requestCallbackSet;
63816    }
63817  };
63818  /**
63819   * Removes a response hook on an xhr object, deletes the onResponse set if empty.
63820   *
63821   * @param {Object} xhr object to remove the onResponse hook from
63822   * @param {function} callback hook function to remove
63823   */
63824
63825  const removeOnResponseHook = (xhr, callback) => {
63826    if (!xhr._responseCallbackSet) {
63827      return;
63828    }
63829    xhr._responseCallbackSet.delete(callback);
63830    if (!xhr._responseCallbackSet.size) {
63831      delete xhr._responseCallbackSet;
63832    }
63833  };
63834  /**
63835   * Whether the browser has built-in HLS support.
63836   */
63837
63838  Vhs.supportsNativeHls = function () {
63839    if (!document || !document.createElement) {
63840      return false;
63841    }
63842    const video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
63843
63844    if (!videojs.getTech('Html5').isSupported()) {
63845      return false;
63846    } // HLS manifests can go by many mime-types
63847
63848    const canPlay = [
63849    // Apple santioned
63850    'application/vnd.apple.mpegurl',
63851    // Apple sanctioned for backwards compatibility
63852    'audio/mpegurl',
63853    // Very common
63854    'audio/x-mpegurl',
63855    // Very common
63856    'application/x-mpegurl',
63857    // Included for completeness
63858    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
63859    return canPlay.some(function (canItPlay) {
63860      return /maybe|probably/i.test(video.canPlayType(canItPlay));
63861    });
63862  }();
63863  Vhs.supportsNativeDash = function () {
63864    if (!document || !document.createElement || !videojs.getTech('Html5').isSupported()) {
63865      return false;
63866    }
63867    return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
63868  }();
63869  Vhs.supportsTypeNatively = type => {
63870    if (type === 'hls') {
63871      return Vhs.supportsNativeHls;
63872    }
63873    if (type === 'dash') {
63874      return Vhs.supportsNativeDash;
63875    }
63876    return false;
63877  };
63878  /**
63879   * VHS is a source handler, not a tech. Make sure attempts to use it
63880   * as one do not cause exceptions.
63881   */
63882
63883  Vhs.isSupported = function () {
63884    return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
63885  };
63886  /**
63887   * A global function for setting an onRequest hook
63888   *
63889   * @param {function} callback for request modifiction
63890   */
63891
63892  Vhs.xhr.onRequest = function (callback) {
63893    addOnRequestHook(Vhs.xhr, callback);
63894  };
63895  /**
63896   * A global function for setting an onResponse hook
63897   *
63898   * @param {callback} callback for response data retrieval
63899   */
63900
63901  Vhs.xhr.onResponse = function (callback) {
63902    addOnResponseHook(Vhs.xhr, callback);
63903  };
63904  /**
63905   * Deletes a global onRequest callback if it exists
63906   *
63907   * @param {function} callback to delete from the global set
63908   */
63909
63910  Vhs.xhr.offRequest = function (callback) {
63911    removeOnRequestHook(Vhs.xhr, callback);
63912  };
63913  /**
63914   * Deletes a global onResponse callback if it exists
63915   *
63916   * @param {function} callback to delete from the global set
63917   */
63918
63919  Vhs.xhr.offResponse = function (callback) {
63920    removeOnResponseHook(Vhs.xhr, callback);
63921  };
63922  const Component = videojs.getComponent('Component');
63923  /**
63924   * The Vhs Handler object, where we orchestrate all of the parts
63925   * of VHS to interact with video.js
63926   *
63927   * @class VhsHandler
63928   * @extends videojs.Component
63929   * @param {Object} source the soruce object
63930   * @param {Tech} tech the parent tech object
63931   * @param {Object} options optional and required options
63932   */
63933
63934  class VhsHandler extends Component {
63935    constructor(source, tech, options) {
63936      super(tech, options.vhs); // if a tech level `initialBandwidth` option was passed
63937      // use that over the VHS level `bandwidth` option
63938
63939      if (typeof options.initialBandwidth === 'number') {
63940        this.options_.bandwidth = options.initialBandwidth;
63941      }
63942      this.logger_ = logger('VhsHandler'); // we need access to the player in some cases,
63943      // so, get it from Video.js via the `playerId`
63944
63945      if (tech.options_ && tech.options_.playerId) {
63946        const _player = videojs.getPlayer(tech.options_.playerId);
63947        this.player_ = _player;
63948      }
63949      this.tech_ = tech;
63950      this.source_ = source;
63951      this.stats = {};
63952      this.ignoreNextSeekingEvent_ = false;
63953      this.setOptions_();
63954      if (this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
63955        tech.overrideNativeAudioTracks(true);
63956        tech.overrideNativeVideoTracks(true);
63957      } else if (this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
63958        // overriding native VHS only works if audio tracks have been emulated
63959        // error early if we're misconfigured
63960        throw new Error('Overriding native VHS requires emulated tracks. ' + 'See https://git.io/vMpjB');
63961      } // listen for fullscreenchange events for this player so that we
63962      // can adjust our quality selection quickly
63963
63964      this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], event => {
63965        const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
63966        if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
63967          this.playlistController_.fastQualityChange_();
63968        } else {
63969          // When leaving fullscreen, since the in page pixel dimensions should be smaller
63970          // than full screen, see if there should be a rendition switch down to preserve
63971          // bandwidth.
63972          this.playlistController_.checkABR_();
63973        }
63974      });
63975      this.on(this.tech_, 'seeking', function () {
63976        if (this.ignoreNextSeekingEvent_) {
63977          this.ignoreNextSeekingEvent_ = false;
63978          return;
63979        }
63980        this.setCurrentTime(this.tech_.currentTime());
63981      });
63982      this.on(this.tech_, 'error', function () {
63983        // verify that the error was real and we are loaded
63984        // enough to have pc loaded.
63985        if (this.tech_.error() && this.playlistController_) {
63986          this.playlistController_.pauseLoading();
63987        }
63988      });
63989      this.on(this.tech_, 'play', this.play);
63990    }
63991    /**
63992     * Set VHS options based on options from configuration, as well as partial
63993     * options to be passed at a later time.
63994     *
63995     * @param {Object} options A partial chunk of config options
63996     */
63997
63998    setOptions_(options = {}) {
63999      this.options_ = merge(this.options_, options); // defaults
64000
64001      this.options_.withCredentials = this.options_.withCredentials || false;
64002      this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
64003      this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
64004      this.options_.use
vendor: 4,196 bytes, lines 64004-64085
64004BandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
64005      this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
64006      this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
64007      this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
64008      this.options_.customTagParsers = this.options_.customTagParsers || [];
64009      this.options_.customTagMappers = this.options_.customTagMappers || [];
64010      this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
64011      this.options_.llhls = this.options_.llhls === false ? false : true;
64012      this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
64013      if (typeof this.options_.playlistExclusionDuration !== 'number') {
64014        this.options_.playlistExclusionDuration = 60;
64015      }
64016      if (typeof this.options_.bandwidth !== 'number') {
64017        if (this.options_.useBandwidthFromLocalStorage) {
64018          const storedObject = getVhsLocalStorage();
64019          if (storedObject && storedObject.bandwidth) {
64020            this.options_.bandwidth = storedObject.bandwidth;
64021            this.tech_.trigger({
64022              type: 'usage',
64023              name: 'vhs-bandwidth-from-local-storage'
64024            });
64025          }
64026          if (storedObject && storedObject.throughput) {
64027            this.options_.throughput = storedObject.throughput;
64028            this.tech_.trigger({
64029              type: 'usage',
64030              name: 'vhs-throughput-from-local-storage'
64031            });
64032          }
64033        }
64034      } // if bandwidth was not set by options or pulled from local storage, start playlist
64035      // selection at a reasonable bandwidth
64036
64037      if (typeof this.options_.bandwidth !== 'number') {
64038        this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
64039      } // If the bandwidth number is unchanged from the initial setting
64040      // then this takes precedence over the enableLowInitialPlaylist option
64041
64042      this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
64043
64044      ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'customTagParsers', 'customTagMappers', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'bufferBasedABR', 'liveRangeSafeTimeDelta', 'llhls', 'useForcedSubtitles', 'useNetworkInformationApi', 'useDtsForTimestampOffset', 'exactManifestTimings', 'leastPixelDiffSelector'].forEach(option => {
64045        if (typeof this.source_[option] !== 'undefined') {
64046          this.options_[option] = this.source_[option];
64047        }
64048      });
64049      this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
64050      this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
64051    } // alias for public method to set options
64052
64053    setOptions(options = {}) {
64054      this.setOptions_(options);
64055    }
64056    /**
64057     * called when player.src gets called, handle a new source
64058     *
64059     * @param {Object} src the source object to handle
64060     */
64061
64062    src(src, type) {
64063      // do nothing if the src is falsey
64064      if (!src) {
64065        return;
64066      }
64067      this.setOptions_(); // add main playlist controller options
64068
64069      this.options_.src = expandDataUri(this.source_.src);
64070      this.options_.tech = this.tech_;
64071      this.options_.externVhs = Vhs;
64072      this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
64073
64074      this.options_.seekTo = time => {
64075        this.tech_.setCurrentTime(time);
64076      };
64077      this.playlistController_ = new PlaylistController(this.options_);
64078      const playbackWatcherOptions = merge({
64079        liveRangeSafeTimeDelta: SAFE_TIME_DELTA
64080      }, this.options_, {
64081        seekable: () => this.seekable(),
64082        media: () => this.playlistController_.media(),
64083        playlistController: this.playlistController_
64084      });
64085      this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions
vendor: 9,435 bytes, lines 64085-64319
64085);
64086      this.playlistController_.on('error', () => {
64087        const player = videojs.players[this.tech_.options_.playerId];
64088        let error = this.playlistController_.error;
64089        if (typeof error === 'object' && !error.code) {
64090          error.code = 3;
64091        } else if (typeof error === 'string') {
64092          error = {
64093            message: error,
64094            code: 3
64095          };
64096        }
64097        player.error(error);
64098      });
64099      const defaultSelector = this.options_.bufferBasedABR ? Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
64100      // compatibility with < v2
64101
64102      this.playlistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
64103      this.playlistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
64104
64105      this.playlists = this.playlistController_.mainPlaylistLoader_;
64106      this.mediaSource = this.playlistController_.mediaSource; // Proxy assignment of some properties to the main playlist
64107      // controller. Using a custom property for backwards compatibility
64108      // with < v2
64109
64110      Object.defineProperties(this, {
64111        selectPlaylist: {
64112          get() {
64113            return this.playlistController_.selectPlaylist;
64114          },
64115          set(selectPlaylist) {
64116            this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
64117          }
64118        },
64119        throughput: {
64120          get() {
64121            return this.playlistController_.mainSegmentLoader_.throughput.rate;
64122          },
64123          set(throughput) {
64124            this.playlistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
64125            // for the cumulative average
64126
64127            this.playlistController_.mainSegmentLoader_.throughput.count = 1;
64128          }
64129        },
64130        bandwidth: {
64131          get() {
64132            let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
64133            const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection;
64134            const tenMbpsAsBitsPerSecond = 10e6;
64135            if (this.options_.useNetworkInformationApi && networkInformation) {
64136              // downlink returns Mbps
64137              // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
64138              const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
64139              // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
64140              // high quality streams are not filtered out.
64141
64142              if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
64143                playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
64144              } else {
64145                playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
64146              }
64147            }
64148            return playerBandwidthEst;
64149          },
64150          set(bandwidth) {
64151            this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
64152            // `count` is set to zero that current value of `rate` isn't included
64153            // in the cumulative average
64154
64155            this.playlistController_.mainSegmentLoader_.throughput = {
64156              rate: 0,
64157              count: 0
64158            };
64159          }
64160        },
64161        /**
64162         * `systemBandwidth` is a combination of two serial processes bit-rates. The first
64163         * is the network bitrate provided by `bandwidth` and the second is the bitrate of
64164         * the entire process after that - decryption, transmuxing, and appending - provided
64165         * by `throughput`.
64166         *
64167         * Since the two process are serial, the overall system bandwidth is given by:
64168         *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
64169         */
64170        systemBandwidth: {
64171          get() {
64172            const invBandwidth = 1 / (this.bandwidth || 1);
64173            let invThroughput;
64174            if (this.throughput > 0) {
64175              invThroughput = 1 / this.throughput;
64176            } else {
64177              invThroughput = 0;
64178            }
64179            const systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
64180            return systemBitrate;
64181          },
64182          set() {
64183            videojs.log.error('The "systemBandwidth" property is read-only');
64184          }
64185        }
64186      });
64187      if (this.options_.bandwidth) {
64188        this.bandwidth = this.options_.bandwidth;
64189      }
64190      if (this.options_.throughput) {
64191        this.throughput = this.options_.throughput;
64192      }
64193      Object.defineProperties(this.stats, {
64194        bandwidth: {
64195          get: () => this.bandwidth || 0,
64196          enumerable: true
64197        },
64198        mediaRequests: {
64199          get: () => this.playlistController_.mediaRequests_() || 0,
64200          enumerable: true
64201        },
64202        mediaRequestsAborted: {
64203          get: () => this.playlistController_.mediaRequestsAborted_() || 0,
64204          enumerable: true
64205        },
64206        mediaRequestsTimedout: {
64207          get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
64208          enumerable: true
64209        },
64210        mediaRequestsErrored: {
64211          get: () => this.playlistController_.mediaRequestsErrored_() || 0,
64212          enumerable: true
64213        },
64214        mediaTransferDuration: {
64215          get: () => this.playlistController_.mediaTransferDuration_() || 0,
64216          enumerable: true
64217        },
64218        mediaBytesTransferred: {
64219          get: () => this.playlistController_.mediaBytesTransferred_() || 0,
64220          enumerable: true
64221        },
64222        mediaSecondsLoaded: {
64223          get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
64224          enumerable: true
64225        },
64226        mediaAppends: {
64227          get: () => this.playlistController_.mediaAppends_() || 0,
64228          enumerable: true
64229        },
64230        mainAppendsToLoadedData: {
64231          get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
64232          enumerable: true
64233        },
64234        audioAppendsToLoadedData: {
64235          get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
64236          enumerable: true
64237        },
64238        appendsToLoadedData: {
64239          get: () => this.playlistController_.appendsToLoadedData_() || 0,
64240          enumerable: true
64241        },
64242        timeToLoadedData: {
64243          get: () => this.playlistController_.timeToLoadedData_() || 0,
64244          enumerable: true
64245        },
64246        buffered: {
64247          get: () => timeRangesToArray(this.tech_.buffered()),
64248          enumerable: true
64249        },
64250        currentTime: {
64251          get: () => this.tech_.currentTime(),
64252          enumerable: true
64253        },
64254        currentSource: {
64255          get: () => this.tech_.currentSource_,
64256          enumerable: true
64257        },
64258        currentTech: {
64259          get: () => this.tech_.name_,
64260          enumerable: true
64261        },
64262        duration: {
64263          get: () => this.tech_.duration(),
64264          enumerable: true
64265        },
64266        main: {
64267          get: () => this.playlists.main,
64268          enumerable: true
64269        },
64270        playerDimensions: {
64271          get: () => this.tech_.currentDimensions(),
64272          enumerable: true
64273        },
64274        seekable: {
64275          get: () => timeRangesToArray(this.tech_.seekable()),
64276          enumerable: true
64277        },
64278        timestamp: {
64279          get: () => Date.now(),
64280          enumerable: true
64281        },
64282        videoPlaybackQuality: {
64283          get: () => this.tech_.getVideoPlaybackQuality(),
64284          enumerable: true
64285        }
64286      });
64287      this.tech_.one('canplay', this.playlistController_.setupFirstPlay.bind(this.playlistController_));
64288      this.tech_.on('bandwidthupdate', () => {
64289        if (this.options_.useBandwidthFromLocalStorage) {
64290          updateVhsLocalStorage({
64291            bandwidth: this.bandwidth,
64292            throughput: Math.round(this.throughput)
64293          });
64294        }
64295      });
64296      this.playlistController_.on('selectedinitialmedia', () => {
64297        // Add the manual rendition mix-in to VhsHandler
64298        renditionSelectionMixin(this);
64299      });
64300      this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
64301        this.setupEme_();
64302      }); // the bandwidth of the primary segment loader is our best
64303      // estimate of overall bandwidth
64304
64305      this.on(this.playlistController_, 'progress', function () {
64306        this.tech_.trigger('progress');
64307      }); // In the live case, we need to ignore the very first `seeking` event since
64308      // that will be the result of the seek-to-live behavior
64309
64310      this.on(this.playlistController_, 'firstplay', function () {
64311        this.ignoreNextSeekingEvent_ = true;
64312      });
64313      this.setupQualityLevels_(); // do nothing if the tech has been disposed already
64314      // this can occur if someone sets the src in player.ready(), for instance
64315
64316      if (!this.tech_.el()) {
64317        return;
64318      }
64319      this.mediaSourceUrl_ = window.URL.createObjectURL(this.playlistController_.mediaSource);
64319
64320      this.tech_.src(this.mediaSourceUrl_);
vendor: 7,228 bytes, lines 64320-64545
64320
64321    }
64322    createKeySessions_() {
64323      const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
64324      this.logger_('waiting for EME key session creation');
64325      waitForKeySessionCreation({
64326        player: this.player_,
64327        sourceKeySystems: this.source_.keySystems,
64328        audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
64329        mainPlaylists: this.playlists.main.playlists
64330      }).then(() => {
64331        this.logger_('created EME key session');
64332        this.playlistController_.sourceUpdater_.initializedEme();
64333      }).catch(err => {
64334        this.logger_('error while creating EME key session', err);
64335        this.player_.error({
64336          message: 'Failed to initialize media keys for EME',
64337          code: 3
64338        });
64339      });
64340    }
64341    handleWaitingForKey_() {
64342      // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
64343      // the key is in the manifest. While this should've happened on initial source load, it
64344      // may happen again in live streams where the keys change, and the manifest info
64345      // reflects the update.
64346      //
64347      // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
64348      // already requested keys for, we don't have to worry about this generating extraneous
64349      // requests.
64350      this.logger_('waitingforkey fired, attempting to create any new key sessions');
64351      this.createKeySessions_();
64352    }
64353    /**
64354     * If necessary and EME is available, sets up EME options and waits for key session
64355     * creation.
64356     *
64357     * This function also updates the source updater so taht it can be used, as for some
64358     * browsers, EME must be configured before content is appended (if appending unencrypted
64359     * content before encrypted content).
64360     */
64361
64362    setupEme_() {
64363      const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
64364      const didSetupEmeOptions = setupEmeOptions({
64365        player: this.player_,
64366        sourceKeySystems: this.source_.keySystems,
64367        media: this.playlists.media(),
64368        audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
64369      });
64370      this.player_.tech_.on('keystatuschange', e => {
64371        this.playlistController_.updatePlaylistByKeyStatus(e.keyId, e.status);
64372      });
64373      this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
64374      this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_);
64375      if (!didSetupEmeOptions) {
64376        // If EME options were not set up, we've done all we could to initialize EME.
64377        this.playlistController_.sourceUpdater_.initializedEme();
64378        return;
64379      }
64380      this.createKeySessions_();
64381    }
64382    /**
64383     * Initializes the quality levels and sets listeners to update them.
64384     *
64385     * @method setupQualityLevels_
64386     * @private
64387     */
64388
64389    setupQualityLevels_() {
64390      const player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
64391      // or qualityLevels_ listeners have already been setup, do nothing.
64392
64393      if (!player || !player.qualityLevels || this.qualityLevels_) {
64394        return;
64395      }
64396      this.qualityLevels_ = player.qualityLevels();
64397      this.playlistController_.on('selectedinitialmedia', () => {
64398        handleVhsLoadedMetadata(this.qualityLevels_, this);
64399      });
64400      this.playlists.on('mediachange', () => {
64401        handleVhsMediaChange(this.qualityLevels_, this.playlists);
64402      });
64403    }
64404    /**
64405     * return the version
64406     */
64407
64408    static version() {
64409      return {
64410        '@videojs/http-streaming': version$4,
64411        'mux.js': version$3,
64412        'mpd-parser': version$2,
64413        'm3u8-parser': version$1,
64414        'aes-decrypter': version
64415      };
64416    }
64417    /**
64418     * return the version
64419     */
64420
64421    version() {
64422      return this.constructor.version();
64423    }
64424    canChangeType() {
64425      return SourceUpdater.canChangeType();
64426    }
64427    /**
64428     * Begin playing the video.
64429     */
64430
64431    play() {
64432      this.playlistController_.play();
64433    }
64434    /**
64435     * a wrapper around the function in PlaylistController
64436     */
64437
64438    setCurrentTime(currentTime) {
64439      this.playlistController_.setCurrentTime(currentTime);
64440    }
64441    /**
64442     * a wrapper around the function in PlaylistController
64443     */
64444
64445    duration() {
64446      return this.playlistController_.duration();
64447    }
64448    /**
64449     * a wrapper around the function in PlaylistController
64450     */
64451
64452    seekable() {
64453      return this.playlistController_.seekable();
64454    }
64455    /**
64456     * Abort all outstanding work and cleanup.
64457     */
64458
64459    dispose() {
64460      if (this.playbackWatcher_) {
64461        this.playbackWatcher_.dispose();
64462      }
64463      if (this.playlistController_) {
64464        this.playlistController_.dispose();
64465      }
64466      if (this.qualityLevels_) {
64467        this.qualityLevels_.dispose();
64468      }
64469      if (this.tech_ && this.tech_.vhs) {
64470        delete this.tech_.vhs;
64471      }
64472      if (this.mediaSourceUrl_ && window.URL.revokeObjectURL) {
64473        window.URL.revokeObjectURL(this.mediaSourceUrl_);
64474        this.mediaSourceUrl_ = null;
64475      }
64476      if (this.tech_) {
64477        this.tech_.off('waitingforkey', this.handleWaitingForKey_);
64478      }
64479      super.dispose();
64480    }
64481    convertToProgramTime(time, callback) {
64482      return getProgramTime({
64483        playlist: this.playlistController_.media(),
64484        time,
64485        callback
64486      });
64487    } // the player must be playing before calling this
64488
64489    seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
64490      return seekToProgramTime({
64491        programTime,
64492        playlist: this.playlistController_.media(),
64493        retryCount,
64494        pauseAfterSeek,
64495        seekTo: this.options_.seekTo,
64496        tech: this.options_.tech,
64497        callback
64498      });
64499    }
64500    /**
64501     * Adds the onRequest, onResponse, offRequest and offResponse functions
64502     * to the VhsHandler xhr Object.
64503     */
64504
64505    setupXhrHooks_() {
64506      /**
64507       * A player function for setting an onRequest hook
64508       *
64509       * @param {function} callback for request modifiction
64510       */
64511      this.xhr.onRequest = callback => {
64512        addOnRequestHook(this.xhr, callback);
64513      };
64514      /**
64515       * A player function for setting an onResponse hook
64516       *
64517       * @param {callback} callback for response data retrieval
64518       */
64519
64520      this.xhr.onResponse = callback => {
64521        addOnResponseHook(this.xhr, callback);
64522      };
64523      /**
64524       * Deletes a player onRequest callback if it exists
64525       *
64526       * @param {function} callback to delete from the player set
64527       */
64528
64529      this.xhr.offRequest = callback => {
64530        removeOnRequestHook(this.xhr, callback);
64531      };
64532      /**
64533       * Deletes a player onResponse callback if it exists
64534       *
64535       * @param {function} callback to delete from the player set
64536       */
64537
64538      this.xhr.offResponse = callback => {
64539        removeOnResponseHook(this.xhr, callback);
64540      }; // Trigger an event on the player to notify the user that vhs is ready to set xhr hooks.
64541      // This allows hooks to be set before the source is set to vhs when handleSource is called.
64542
64543      this.player_.trigger('xhr-hooks-ready');
64544    }
64545  
vendor: 2,519 bytes, lines 64545-64617
64545}
64546  /**
64547   * The Source Handler object, which informs video.js what additional
64548   * MIME types are supported and sets up playback. It is registered
64549   * automatically to the appropriate tech based on the capabilities of
64550   * the browser it is running in. It is not necessary to use or modify
64551   * this object in normal usage.
64552   */
64553
64554  const VhsSourceHandler = {
64555    name: 'videojs-http-streaming',
64556    VERSION: version$4,
64557    canHandleSource(srcObj, options = {}) {
64558      const localOptions = merge(videojs.options, options);
64559      return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
64560    },
64561    handleSource(source, tech, options = {}) {
64562      const localOptions = merge(videojs.options, options);
64563      tech.vhs = new VhsHandler(source, tech, localOptions);
64564      tech.vhs.xhr = xhrFactory();
64565      tech.vhs.setupXhrHooks_();
64566      tech.vhs.src(source.src, source.type);
64567      return tech.vhs;
64568    },
64569    canPlayType(type, options) {
64570      const simpleType = simpleTypeFromSourceType(type);
64571      if (!simpleType) {
64572        return '';
64573      }
64574      const overrideNative = VhsSourceHandler.getOverrideNative(options);
64575      const supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
64576      const canUseMsePlayback = !supportsTypeNatively || overrideNative;
64577      return canUseMsePlayback ? 'maybe' : '';
64578    },
64579    getOverrideNative(options = {}) {
64580      const {
64581        vhs = {}
64582      } = options;
64583      const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
64584      const {
64585        overrideNative = defaultOverrideNative
64586      } = vhs;
64587      return overrideNative;
64588    }
64589  };
64590  /**
64591   * Check to see if the native MediaSource object exists and supports
64592   * an MP4 container with both H.264 video and AAC-LC audio.
64593   *
64594   * @return {boolean} if  native media sources are supported
64595   */
64596
64597  const supportsNativeMediaSources = () => {
64598    return browserSupportsCodec('avc1.4d400d,mp4a.40.2');
64599  }; // register source handlers with the appropriate techs
64600
64601  if (supportsNativeMediaSources()) {
64602    videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
64603  }
64604  videojs.VhsHandler = VhsHandler;
64605  videojs.VhsSourceHandler = VhsSourceHandler;
64606  videojs.Vhs = Vhs;
64607  if (!videojs.use) {
64608    videojs.registerComponent('Vhs', Vhs);
64609  }
64610  videojs.options.vhs = videojs.options.vhs || {};
64611  if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
64612    videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
64613  }
64614
64615  return videojs;
64616
64617}));

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.