PageSourceSearch

https://laziar.com/assets/videojs/video.cjs.js

js laziar.com collected 2026-09-28 08:59:59 UTC 1,902,550 bytes, 58,618 lines download raw bytes

vendor: 12,711 bytes, lines 1-444
1/**
2 * @license
3 * Video.js 8.20.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'use strict';
14
15var window$1 = require('global/window');
16var document$1 = require('global/document');
17var XHR = require('@videojs/xhr');
18var vtt = require('videojs-vtt.js');
19var _extends = require('@babel/runtime/helpers/extends');
20var _resolveUrl = require('@videojs/vhs-utils/cjs/resolve-url.js');
21var m3u8Parser = require('m3u8-parser');
22var codecs_js = require('@videojs/vhs-utils/cjs/codecs.js');
23var mediaTypes_js = require('@videojs/vhs-utils/cjs/media-types.js');
24var byteHelpers = require('@videojs/vhs-utils/cjs/byte-helpers');
25var mpdParser = require('mpd-parser');
26var parseSidx = require('mux.js/lib/tools/parse-sidx');
27var id3Helpers = require('@videojs/vhs-utils/cjs/id3-helpers');
28var containers = require('@videojs/vhs-utils/cjs/containers');
29var clock = require('mux.js/lib/utils/clock');
30
31function _interopDefaultLegacy(e) {
32  return e && typeof e === 'object' && 'default' in e ? e : { default: e };
33}
34
35var window__default = /*#__PURE__*/ _interopDefaultLegacy(window$1);
36var document__default = /*#__PURE__*/ _interopDefaultLegacy(document$1);
37var XHR__default = /*#__PURE__*/ _interopDefaultLegacy(XHR);
38var vtt__default = /*#__PURE__*/ _interopDefaultLegacy(vtt);
39var _extends__default = /*#__PURE__*/ _interopDefaultLegacy(_extends);
40var _resolveUrl__default = /*#__PURE__*/ _interopDefaultLegacy(_resolveUrl);
41var parseSidx__default = /*#__PURE__*/ _interopDefaultLegacy(parseSidx);
42
43var version$6 = '8.20.0';
44
45/**
46 * An Object that contains lifecycle hooks as keys which point to an array
47 * of functions that are run when a lifecycle is triggered
48 *
49 * @private
50 */
51const hooks_ = {};
52
53/**
54 * Get a list of hooks for a specific lifecycle
55 *
56 * @param  {string} type
57 *         the lifecycle to get hooks from
58 *
59 * @param  {Function|Function[]} [fn]
60 *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
61 *
62 * @return {Array}
63 *         an array of hooks, or an empty array if there are none.
64 */
65const hooks = function (type, fn) {
66  hooks_[type] = hooks_[type] || [];
67  if (fn) {
68    hooks_[type] = hooks_[type].concat(fn);
69  }
70  return hooks_[type];
71};
72
73/**
74 * Add a function hook to a specific videojs lifecycle.
75 *
76 * @param {string} type
77 *        the lifecycle to hook the function to.
78 *
79 * @param {Function|Function[]}
80 *        The function or array of functions to attach.
81 */
82const hook = function (type, fn) {
83  hooks(type, fn);
84};
85
86/**
87 * Remove a hook from a specific videojs lifecycle.
88 *
89 * @param  {string} type
90 *         the lifecycle that the function hooked to
91 *
92 * @param  {Function} fn
93 *         The hooked function to remove
94 *
95 * @return {boolean}
96 *         The function that was removed or undef
97 */
98const removeHook = function (type, fn) {
99  const index = hooks(type).indexOf(fn);
100  if (index <= -1) {
101    return false;
102  }
103  hooks_[type] = hooks_[type].slice();
104  hooks_[type].splice(index, 1);
105  return true;
106};
107
108/**
109 * Add a function hook that will only run once to a specific videojs lifecycle.
110 *
111 * @param {string} type
112 *        the lifecycle to hook the function to.
113 *
114 * @param {Function|Function[]}
115 *        The function or array of functions to attach.
116 */
117const hookOnce = function (type, fn) {
118  hooks(
119    type,
120    [].concat(fn).map((original) => {
121      const wrapper = (...args) => {
122        removeHook(type, wrapper);
123        return original(...args);
124      };
125      return wrapper;
126    })
127  );
128};
129
130/**
131 * @file fullscreen-api.js
132 * @module fullscreen-api
133 */
134
135/**
136 * Store the browser-specific methods for the fullscreen API.
137 *
138 * @type {Object}
139 * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
140 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
141 */
142const FullscreenApi = {
143  prefixed: true,
144};
145
146// browser API methods
147const apiMap = [
148  [
149    'requestFullscreen',
150    'exitFullscreen',
151    'fullscreenElement',
152    'fullscreenEnabled',
153    'fullscreenchange',
154    'fullscreenerror',
155    'fullscreen',
156  ],
157  // WebKit
158  [
159    'webkitRequestFullscreen',
160    'webkitExitFullscreen',
161    'webkitFullscreenElement',
162    'webkitFullscreenEnabled',
163    'webkitfullscreenchange',
164    'webkitfullscreenerror',
165    '-webkit-full-screen',
166  ],
167];
168const specApi = apiMap[0];
169let browserApi;
170
171// determine the supported set of functions
172for (let i = 0; i < apiMap.length; i++) {
173  // check for exitFullscreen function
174  if (apiMap[i][1] in document__default['default']) {
175    browserApi = apiMap[i];
176    break;
177  }
178}
179
180// map the browser API names to the spec API names
181if (browserApi) {
182  for (let i = 0; i < browserApi.length; i++) {
183    FullscreenApi[specApi[i]] = browserApi[i];
184  }
185  FullscreenApi.prefixed = browserApi[0] !== specApi[0];
186}
187
188/**
189 * @file create-logger.js
190 * @module create-logger
191 */
192
193// This is the private tracking variable for the logging history.
194let history = [];
195
196/**
197 * Log messages to the console and history based on the type of message
198 *
199 * @private
200 * @param  {string} name
201 *         The name of the console method to use.
202 *
203 * @param  {Object} log
204 *         The arguments to be passed to the matching console method.
205 *
206 * @param {string} [styles]
207 *        styles for name
208 */
209const LogByTypeFactory = (name, log, styles) => (type, level, args) => {
210  const lvl = log.levels[level];
211  const lvlRegExp = new RegExp(`^(${lvl})$`);
212  let resultName = name;
213  if (type !== 'log') {
214    // Add the type to the front of the message when it's not "log".
215    args.unshift(type.toUpperCase() + ':');
216  }
217  if (styles) {
218    resultName = `%c${name}`;
219    args.unshift(styles);
220  }
221
222  // Add console prefix after adding to history.
223  args.unshift(resultName + ':');
224
225  // Add a clone of the args at this point to history.
226  if (history) {
227    history.push([].concat(args));
228
229    // only store 1000 history entries
230    const splice = history.length - 1000;
231    history.splice(0, splice > 0 ? splice : 0);
232  }
233
234  // If there's no console then don't try to output messages, but they will
235  // still be stored in history.
236  if (!window__default['default'].console) {
237    return;
238  }
239
240  // Was setting these once outside of this function, but containing them
241  // in the function makes it easier to test cases where console doesn't exist
242  // when the module is executed.
243  let fn = window__default['default'].console[type];
244  if (!fn && type === 'debug') {
245    // Certain browsers don't have support for console.debug. For those, we
246    // should default to the closest comparable log.
247    fn = window__default['default'].console.info || window__default['default'].console.log;
248  }
249
250  // Bail out if there's no console or if this type is not allowed by the
251  // current logging level.
252  if (!fn || !lvl || !lvlRegExp.test(type)) {
253    return;
254  }
255  fn[Array.isArray(args) ? 'apply' : 'call'](window__default['default'].console, args);
256};
257function createLogger$1(name, delimiter = ':', styles = '') {
258  // This is the private tracking variable for logging level.
259  let level = 'info';
260
261  // the curried logByType bound to the specific log and history
262  let logByType;
263
264  /**
265   * Logs plain debug messages. Similar to `console.log`.
266   *
267   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
268   * of our JSDoc template, we cannot properly document this as both a function
269   * and a namespace, so its function signature is documented here.
270   *
271   * #### Arguments
272   * ##### *args
273   * *[]
274   *
275   * Any combination of values that could be passed to `console.log()`.
276   *
277   * #### Return Value
278   *
279   * `undefined`
280   *
281   * @namespace
282   * @param    {...*} args
283   *           One or more messages or objects that should be logged.
284   */
285  function log(...args) {
286    logByType('log', level, args);
287  }
288
289  // This is the logByType helper that the logging methods below use
290  logByType = LogByTypeFactory(name, log, styles);
291
292  /**
293   * Create a new subLogger which chains the old name to the new name.
294   *
295   * For example, doing `mylogger = videojs.log.createLogger('player')` and then using that logger will log the following:
296   * ```js
297   *  mylogger('foo');
298   *  // > VIDEOJS: player: foo
299   * ```
300   *
301   * @param {string} subName
302   *        The name to add call the new logger
303   * @param {string} [subDelimiter]
304   *        Optional delimiter
305   * @param {string} [subStyles]
306   *        Optional styles
307   * @return {Object}
308   */
309  log.createLogger = (subName, subDelimiter, subStyles) => {
310    const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter;
311    const resultStyles = subStyles !== undefined ? subStyles : styles;
312    const resultName = `${name} ${resultDelimiter} ${subName}`;
313    return createLogger$1(resultName, resultDelimiter, resultStyles);
314  };
315
316  /**
317   * Create a new logger.
318   *
319   * @param {string} newName
320   *        The name for the new logger
321   * @param {string} [newDelimiter]
322   *        Optional delimiter
323   * @param {string} [newStyles]
324   *        Optional styles
325   * @return {Object}
326   */
327  log.createNewLogger = (newName, newDelimiter, newStyles) => {
328    return createLogger$1(newName, newDelimiter, newStyles);
329  };
330
331  /**
332   * Enumeration of available logging levels, where the keys are the level names
333   * and the values are `|`-separated strings containing logging methods allowed
334   * in that logging level. These strings are used to create a regular expression
335   * matching the function name being called.
336   *
337   * Levels provided by Video.js are:
338   *
339   * - `off`: Matches no calls. Any value that can be cast to `false` will have
340   *   this effect. The most restrictive.
341   * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
342   *   `log.warn`, and `log.error`).
343   * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
344   * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
345   * - `warn`: Matches `log.warn` and `log.error` calls.
346   * - `error`: Matches only `log.error` calls.
347   *
348   * @type {Object}
349   */
350  log.levels = {
351    all: 'debug|log|warn|error',
352    off: '',
353    debug: 'debug|log|warn|error',
354    info: 'log|warn|error',
355    warn: 'warn|error',
356    error: 'error',
357    DEFAULT: level,
358  };
359
360  /**
361   * Get or set the current logging level.
362   *
363   * If a string matching a key from {@link module:log.levels} is provided, acts
364   * as a setter.
365   *
366   * @param  {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl]
367   *         Pass a valid level to set a new logging level.
368   *
369   * @return {string}
370   *         The current logging level.
371   */
372  log.level = (lvl) => {
373    if (typeof lvl === 'string') {
374      if (!log.levels.hasOwnProperty(lvl)) {
375        throw new Error(`"${lvl}" in not a valid log level`);
376      }
377      level = lvl;
378    }
379    return level;
380  };
381
382  /**
383   * Returns an array containing everything that has been logged to the history.
384   *
385   * This array is a shallow clone of the internal history record. However, its
386   * contents are _not_ cloned; so, mutating objects inside this array will
387   * mutate them in history.
388   *
389   * @return {Array}
390   */
391  log.history = () => (history ? [].concat(history) : []);
392
393  /**
394   * Allows you to filter the history by the given logger name
395   *
396   * @param {string} fname
397   *        The name to filter by
398   *
399   * @return {Array}
400   *         The filtered list to return
401   */
402  log.history.filter = (fname) => {
403    return (history || []).filter((historyItem) => {
404      // if the first item in each historyItem includes `fname`, then it's a match
405      return new RegExp(`.*${fname}.*`).test(historyItem[0]);
406    });
407  };
408
409  /**
410   * Clears the internal history tracking, but does not prevent further history
411   * tracking.
412   */
413  log.history.clear = () => {
414    if (history) {
415      history.length = 0;
416    }
417  };
418
419  /**
420   * Disable history tracking if it is currently enabled.
421   */
422  log.history.disable = () => {
423    if (history !== null) {
424      history.length = 0;
425      history = null;
426    }
427  };
428
429  /**
430   * Enable history tracking if it is currently disabled.
431   */
432  log.history.enable = () => {
433    if (history === null) {
434      history = [];
435    }
436  };
437
438  /**
439   * Logs error messages. Similar to `console.error`.
440   *
441   * @param {...*} args
442   *        One or more messages or objects that should be logged as an error
443   */
444  log.error = (...args) =>
444 logByType('error', level, args);
445
446  /**
447   * Logs warning messages. Similar to `console.warn`.
448   *
449   * @param {...*} args
450   *        One or more messages or objects that should be logged as a warning.
451   */
452  log.warn = (...args) => logByType('warn', level, args);
453
454  /**
455   * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
456   * log if `console.debug` is not available
457   *
458   * @param {...*} args
459   *        One or more messages or objects that should be logged as debug.
460   */
461  log.debug = (...args) => logByType('debug', level, args);
462  return log;
463}
464
465/**
466 * @file log.js
467 * @module log
468 */
469const log$1 = createLogger$1('VIDEOJS');
470const createLogger = log$1.createLogger;
471
472/**
473 * @file obj.js
474 * @module obj
475 */
476
477/**
478 * @callback obj:EachCallback
479 *
480 * @param {*} value
481 *        The current key for the object that is being iterated over.
482 *
483 * @param {string} key
484 *        The current key-value for object that is being iterated over
485 */
486
487/**
488 * @callback obj:ReduceCallback
489 *
490 * @param {*} accum
491 *        The value that is accumulating over the reduce loop.
492 *
493 * @param {*} value
494 *        The current key for the object that is being iterated over.
495 *
496 * @param {string} key
497 *        The current key-value for object that is being iterated over
498 *
499 * @return {*}
500 *         The new accumulated value.
501 */
502const toString = Object.prototype.toString;
503
504/**
505 * Get the keys of an Object
506 *
507 * @param {Object}
508 *        The Object to get the keys from
509 *
510 * @return {string[]}
511 *         An array of the keys from the object. Returns an empty array if the
512 *         object passed in was invalid or had no keys.
513 *
514 * @private
515 */
516const keys = function (object) {
517  return isObject(object) ? Object.keys(object) : [];
518};
519
520/**
521 * Array-like iteration for objects.
522 *
523 * @param {Object} object
524 *        The object to iterate over
525 *
526 * @param {obj:EachCallback} fn
527 *        The callback function which is called for each key in the object.
528 */
529function each(object, fn) {
530  keys(object).forEach((key) => fn(object[key], key));
531}
532
533/**
534 * Array-like reduce for objects.
535 *
536 * @param {Object} object
537 *        The Object that you want to reduce.
538 *
539 * @param {Function} fn
540 *         A callback function which is called for each key in the object. It
541 *         receives the accumulated value and the per-iteration value and key
542 *         as arguments.
543 *
544 * @param {*} [initial = 0]
545 *        Starting value
546 *
547 * @return {*}
548 *         The final accumulated value.
549 */
550function reduce(object, fn, initial = 0) {
551  return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
552}
553
554/**
555 * Returns whether a value is an object of any kind - including DOM nodes,
556 * arrays, regular expressions, etc. Not functions, though.
557 *
558 * This avoids the gotcha where using `typeof` on a `null` value
559 * results in `'object'`.
560 *
561 * @param  {Object} value
562 * @return {boolean}
563 */
564function isObject(value) {
565  return !!value && typeof value === 'object';
566}
567
568/**
569 * Returns whether an object appears to be a "plain" object - that is, a
570 * direct instance of `Object`.
571 *
572 * @param  {Object} value
573 * @return {boolean}
574 */
575function isPlain(value) {
576  return (
577    isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object
578  );
579}
580
581/**
582 * Merge two objects recursively.
583 *
584 * Performs a deep merge like
585 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
586 * plain objects (not arrays, elements, or anything else).
587 *
588 * Non-plain object values will be copied directly from the right-most
589 * argument.
590 *
591 * @param   {Object[]} sources
592 *          One or more objects to merge into a new object.
593 *
594 * @return {Object}
595 *          A new object that is the merged result of all sources.
596 */
597function merge$1(...sources) {
598  const result = {};
599  sources.forEach((source) => {
600    if (!source) {
601      return;
602    }
603    each(source, (value, key) => {
604      if (!isPlain(value)) {
605        result[key] = value;
606        return;
607      }
608      if (!isPlain(result[key])) {
609        result[key] = {};
610      }
611      result[key] = merge$1(result[key], value);
612    });
613  });
614  return result;
615}
616
617/**
618 * Returns an array of values for a given object
619 *
620 * @param  {Object} source - target object
621 * @return {Array<unknown>} - object values
622 */
623function values(source = {}) {
624  const result = [];
625  for (const key in source) {
626    if (source.hasOwnProperty(key)) {
627      const value = source[key];
628      result.push(value);
629    }
630  }
631  return result;
632}
633
634/**
635 * Object.defineProperty but "lazy", which means that the value is only set after
636 * it is retrieved the first time, rather than being set right away.
637 *
638 * @param {Object} obj the object to set the property on
639 * @param {string} key the key for the property to set
640 * @param {Function} getValue the function used to get the value when it is needed.
641 * @param {boolean} setter whether a setter should be allowed or not
642 */
643function defineLazyProperty(obj, key, getValue, setter = true) {
644  const set = (value) =>
645    Object.defineProperty(obj, key, {
646      value,
647      enumerable: true,
648      writable: true,
649    });
650  const options = {
651    configurable: true,
652    enumerable: true,
653    get() {
654      const value = getValue();
655      set(value);
656      return value;
657    },
658  };
659  if (setter) {
660    options.set = set;
661  }
662  return Object.defineProperty(obj, key, options);
663}
664
665var Obj = /*#__PURE__*/ Object.freeze({
666  __proto__: null,
667  each: each,
668  reduce: reduce,
669  isObject: isObject,
670  isPlain: isPlain,
671  merge: merge$1,
672  values: values,
673  defineLazyProperty: defineLazyProperty,
674});
675
676/**
677 * @file browser.js
678 * @module browser
679 */
680
681/**
682 * Whether or not this device is an iPod.
683 *
684 * @static
685 * @type {Boolean}
686 */
687let IS_IPOD = false;
688
689/**
690 * The detected iOS version - or `null`.
691 *
692 * @static
693 * @type {string|null}
694 */
695let IOS_VERSION = null;
696
697/**
698 * Whether or not this is an Android device.
699 *
700 * @static
701 * @type {Boolean}
702 */
703let IS_ANDROID = false;
704
705/**
706 * The detected Android version - or `null` if not Android or indeterminable.
707 *
708 * @static
709 * @type {number|string|null}
710 */
711let ANDROID_VERSION;
712
713/**
714 * Whether or not this is Mozilla Firefox.
715 *
716 * @static
717 * @type {Boolean}
718 */
719let IS_FIREFOX = false;
720
721/**
722 * Whether or not this is Microsoft Edge.
723 *
724 * @static
725 * @type {Boolean}
726 */
727let IS_EDGE = false;
728
729/**
730 * Whether or not this is any Chromium Browser
731 *
732 * @static
733 * @type {Boolean}
734 */
735let IS_CHROMIUM = false;
736
737/**
738 * Whether or not this is any Chromium browser that is not Edge.
739 *
740 * This will also be `true` for Chrome on iOS, which will have different support
741 * as it is actually Safari under the hood.
742 *
743 * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
744 * IS_CHROMIUM should be used instead.
745 * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
746 *
747 * @static
748 * @deprecated
749 * @type {Boolean}
750 */
751let IS_CHROME = false;
752
753/**
754 * The detected Chromium version - or `null`.
755 *
756 * @static
757 * @type {number|null}
758 */
759let CHROMIUM_VERSION = null;
760
761/**
762 * The detected Google Chrome version - or `null`.
763 * This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
764 * Deprecated, use CHROMIUM_VERSION instead.
765 *
766 * @static
767 * @deprecated
768 * @type {number|null}
769 */
770let CHROME_VERSION = null;
771
772/**
773 * Whether or not this is a Chromecast receiver application.
774 *
775 * @static
776 * @type {Boolean}
777 */
778const IS_CHROMECAST_RECEIVER = Boolean(
779  window__default['default'].cast &&
780  window__default['default'].cast.framework &&
781  window__default['default'].cast.framework.CastReceiverContext
782);
783
784/**
785 * The detected Internet Explorer version - or `null`.
786 *
787 * @static
788 * @deprecated
789 * @type {number|null}
790 */
791let IE_VERSION = null;
792
793/**
794 * Whether or not this is desktop Safari.
795 *
796 * @static
797 * @type {Boolean}
798 */
799let IS_SAFARI = false;
800
801/**
802 * Whether or not this is a Windows machine.
803 *
804 * @static
805 * @type {Boolean}
806 */
807let IS_WINDOWS = false;
808
809/**
810 * Whether or not this device is an iPad.
811 *
812 * @static
813 * @type {Boolean}
814 */
815let IS_IPAD = false;
816
817/**
818 * Whether or not this device is an iPhone.
819 *
820 * @static
821 * @type {Boolean}
822 */
823// The Facebook app's UIWebView identifies as both an iPhone and iPad, so
824// to identify iPhones, we need to exclude iPads.
825// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
826let IS_IPHONE = false;
827
828/**
829 * Whether or not this is a Tizen device.
830 *
831 * @static
832 * @type {Boolean}
833 */
834let IS_TIZEN = false;
835
836/**
837 * Whether or not this is a WebOS device.
838 *
839 * @static
840 * @type {Boolean}
841 */
842let IS_WEBOS = false;
843
844/**
845 * Whether or not this is a Smart TV (Tizen or WebOS) device.
846 *
847 * @static
848 * @type {Boolean}
849 */
850let IS_SMART_TV = false;
851
852/**
853 * Whether or not this device is touch-enabled.
854 *
855 * @static
856 * @const
857 * @type {Boolean}
858 */
859const TOUCH_ENABLED = Boolean(
860  isReal() &&
861  ('ontouchstart' in window__default['default'] ||
862    window__default['default'].navigator.maxTouchPoints ||
863    (window__default['default'].DocumentTouch &&
864      window__default['default'].document instanceof window__default['default'].DocumentTouch))
865);
866const UAD =
867  window__default['default'].navigator && window__default['default'].navigator.userAgentData;
868if (UAD && UAD.platform && UAD.brands) {
869  // If userAgentData is present, use it instead of userAgent to avoid warnings
870  // Currently only implemented on Chromium
871  // userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
872
873  IS_ANDROID = UAD.platform === 'Android';
874  IS_EDGE = Boolean(UAD.brands.find((b) => b.brand === 'Microsoft Edge'));
875  IS_CHROMIUM = Boolean(UAD.brands.find((b) => b.brand === 'Chromium'));
876  IS_CHROME = !IS_EDGE && IS_CHROMIUM;
877  CHROMIUM_VERSION = CHROME_VERSION =
878    (UAD.brands.find((b) => b.brand === 'Chromium') || {}).version || null;
879  IS_WINDOWS = UAD.platform === 'Windows';
880}
881
882// If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
883//  or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
884// the checks need to be made agiainst the regular userAgent string.
885if (!IS_CHROMIUM) {
886  const USER_AGENT =
887    (window__default['default'].navigator && window__default['default'].navigator.userAgent) || '';
888  IS_IPOD = /iPod/i.test(USER_AGENT);
889  IOS_VERSION = (function () {
890    const match = USER_AGENT.match(/OS (\d+)_/i);
891    if (match && match[1]) {
892      return match[1];
893    }
894    return null;
895  })();
896  IS_ANDROID = /Android/i.test(USER_AGENT);
897  ANDROID_VERSION = (function () {
898    // This matches Android Major.Minor.Patch versions
vendor: 9,002 bytes, lines 899-1245
899    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
900    const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
901    if (!match) {
902      return null;
903    }
904    const major = match[1] && parseFloat(match[1]);
905    const minor = match[2] && parseFloat(match[2]);
906    if (major && minor) {
907      return parseFloat(match[1] + '.' + match[2]);
908    } else if (major) {
909      return major;
910    }
911    return null;
912  })();
913  IS_FIREFOX = /Firefox/i.test(USER_AGENT);
914  IS_EDGE = /Edg/i.test(USER_AGENT);
915  IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
916  IS_CHROME = !IS_EDGE && IS_CHROMIUM;
917  CHROMIUM_VERSION = CHROME_VERSION = (function () {
918    const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
919    if (match && match[2]) {
920      return parseFloat(match[2]);
921    }
922    return null;
923  })();
924  IE_VERSION = (function () {
925    const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
926    let version = result && parseFloat(result[1]);
927    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
928      // IE 11 has a different user agent string than other IE versions
929      version = 11.0;
930    }
931    return version;
932  })();
933  IS_TIZEN = /Tizen/i.test(USER_AGENT);
934  IS_WEBOS = /Web0S/i.test(USER_AGENT);
935  IS_SMART_TV = IS_TIZEN || IS_WEBOS;
936  IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE && !IS_SMART_TV;
937  IS_WINDOWS = /Windows/i.test(USER_AGENT);
938  IS_IPAD = /iPad/i.test(USER_AGENT) || (IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT));
939  IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
940}
941
942/**
943 * Whether or not this is an iOS device.
944 *
945 * @static
946 * @const
947 * @type {Boolean}
948 */
949const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
950
951/**
952 * Whether or not this is any flavor of Safari - including iOS.
953 *
954 * @static
955 * @const
956 * @type {Boolean}
957 */
958const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
959
960var browser = /*#__PURE__*/ Object.freeze({
961  __proto__: null,
962  get IS_IPOD() {
963    return IS_IPOD;
964  },
965  get IOS_VERSION() {
966    return IOS_VERSION;
967  },
968  get IS_ANDROID() {
969    return IS_ANDROID;
970  },
971  get ANDROID_VERSION() {
972    return ANDROID_VERSION;
973  },
974  get IS_FIREFOX() {
975    return IS_FIREFOX;
976  },
977  get IS_EDGE() {
978    return IS_EDGE;
979  },
980  get IS_CHROMIUM() {
981    return IS_CHROMIUM;
982  },
983  get IS_CHROME() {
984    return IS_CHROME;
985  },
986  get CHROMIUM_VERSION() {
987    return CHROMIUM_VERSION;
988  },
989  get CHROME_VERSION() {
990    return CHROME_VERSION;
991  },
992  IS_CHROMECAST_RECEIVER: IS_CHROMECAST_RECEIVER,
993  get IE_VERSION() {
994    return IE_VERSION;
995  },
996  get IS_SAFARI() {
997    return IS_SAFARI;
998  },
999  get IS_WINDOWS() {
1000    return IS_WINDOWS;
1001  },
1002  get IS_IPAD() {
1003    return IS_IPAD;
1004  },
1005  get IS_IPHONE() {
1006    return IS_IPHONE;
1007  },
1008  get IS_TIZEN() {
1009    return IS_TIZEN;
1010  },
1011  get IS_WEBOS() {
1012    return IS_WEBOS;
1013  },
1014  get IS_SMART_TV() {
1015    return IS_SMART_TV;
1016  },
1017  TOUCH_ENABLED: TOUCH_ENABLED,
1018  IS_IOS: IS_IOS,
1019  IS_ANY_SAFARI: IS_ANY_SAFARI,
1020});
1021
1022/**
1023 * @file dom.js
1024 * @module dom
1025 */
1026
1027/**
1028 * Detect if a value is a string with any non-whitespace characters.
1029 *
1030 * @private
1031 * @param  {string} str
1032 *         The string to check
1033 *
1034 * @return {boolean}
1035 *         Will be `true` if the string is non-blank, `false` otherwise.
1036 *
1037 */
1038function isNonBlankString(str) {
1039  // we use str.trim as it will trim any whitespace characters
1040  // from the front or back of non-whitespace characters. aka
1041  // Any string that contains non-whitespace characters will
1042  // still contain them after `trim` but whitespace only strings
1043  // will have a length of 0, failing this check.
1044  return typeof str === 'string' && Boolean(str.trim());
1045}
1046
1047/**
1048 * Throws an error if the passed string has whitespace. This is used by
1049 * class methods to be relatively consistent with the classList API.
1050 *
1051 * @private
1052 * @param  {string} str
1053 *         The string to check for whitespace.
1054 *
1055 * @throws {Error}
1056 *         Throws an error if there is whitespace in the string.
1057 */
1058function throwIfWhitespace(str) {
1059  // str.indexOf instead of regex because str.indexOf is faster performance wise.
1060  if (str.indexOf(' ') >= 0) {
1061    throw new Error('class has illegal whitespace characters');
1062  }
1063}
1064
1065/**
1066 * Whether the current DOM interface appears to be real (i.e. not simulated).
1067 *
1068 * @return {boolean}
1069 *         Will be `true` if the DOM appears to be real, `false` otherwise.
1070 */
1071function isReal() {
1072  // Both document and window will never be undefined thanks to `global`.
1073  return document__default['default'] === window__default['default'].document;
1074}
1075
1076/**
1077 * Determines, via duck typing, whether or not a value is a DOM element.
1078 *
1079 * @param  {*} value
1080 *         The value to check.
1081 *
1082 * @return {boolean}
1083 *         Will be `true` if the value is a DOM element, `false` otherwise.
1084 */
1085function isEl(value) {
1086  return isObject(value) && value.nodeType === 1;
1087}
1088
1089/**
1090 * Determines if the current DOM is embedded in an iframe.
1091 *
1092 * @return {boolean}
1093 *         Will be `true` if the DOM is embedded in an iframe, `false`
1094 *         otherwise.
1095 */
1096function isInFrame() {
1097  // We need a try/catch here because Safari will throw errors when attempting
1098  // to get either `parent` or `self`
1099  try {
1100    return window__default['default'].parent !== window__default['default'].self;
1101  } catch (x) {
1102    return true;
1103  }
1104}
1105
1106/**
1107 * Creates functions to query the DOM using a given method.
1108 *
1109 * @private
1110 * @param   {string} method
1111 *          The method to create the query with.
1112 *
1113 * @return  {Function}
1114 *          The query method
1115 */
1116function createQuerier(method) {
1117  return function (selector, context) {
1118    if (!isNonBlankString(selector)) {
1119      return document__default['default'][method](null);
1120    }
1121    if (isNonBlankString(context)) {
1122      context = document__default['default'].querySelector(context);
1123    }
1124    const ctx = isEl(context) ? context : document__default['default'];
1125    return ctx[method] && ctx[method](selector);
1126  };
1127}
1128
1129/**
1130 * Creates an element and applies properties, attributes, and inserts content.
1131 *
1132 * @param  {string} [tagName='div']
1133 *         Name of tag to be created.
1134 *
1135 * @param  {Object} [properties={}]
1136 *         Element properties to be applied.
1137 *
1138 * @param  {Object} [attributes={}]
1139 *         Element attributes to be applied.
1140 *
1141 * @param {ContentDescriptor} [content]
1142 *        A content descriptor object.
1143 *
1144 * @return {Element}
1145 *         The element that was created.
1146 */
1147function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
1148  const el = document__default['default'].createElement(tagName);
1149  Object.getOwnPropertyNames(properties).forEach(function (propName) {
1150    const val = properties[propName];
1151
1152    // Handle textContent since it's not supported everywhere and we have a
1153    // method for it.
1154    if (propName === 'textContent') {
1155      textContent(el, val);
1156    } else if (el[propName] !== val || propName === 'tabIndex') {
1157      el[propName] = val;
1158    }
1159  });
1160  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1161    el.setAttribute(attrName, attributes[attrName]);
1162  });
1163  if (content) {
1164    appendContent(el, content);
1165  }
1166  return el;
1167}
1168
1169/**
1170 * Injects text into an element, replacing any existing contents entirely.
1171 *
1172 * @param  {HTMLElement} el
1173 *         The element to add text content into
1174 *
1175 * @param  {string} text
1176 *         The text content to add.
1177 *
1178 * @return {Element}
1179 *         The element with added text content.
1180 */
1181function textContent(el, text) {
1182  if (typeof el.textContent === 'undefined') {
1183    el.innerText = text;
1184  } else {
1185    el.textContent = text;
1186  }
1187  return el;
1188}
1189
1190/**
1191 * Insert an element as the first child node of another
1192 *
1193 * @param {Element} child
1194 *        Element to insert
1195 *
1196 * @param {Element} parent
1197 *        Element to insert child into
1198 */
1199function prependTo(child, parent) {
1200  if (parent.firstChild) {
1201    parent.insertBefore(child, parent.firstChild);
1202  } else {
1203    parent.appendChild(child);
1204  }
1205}
1206
1207/**
1208 * Check if an element has a class name.
1209 *
1210 * @param  {Element} element
1211 *         Element to check
1212 *
1213 * @param  {string} classToCheck
1214 *         Class name to check for
1215 *
1216 * @return {boolean}
1217 *         Will be `true` if the element has a class, `false` otherwise.
1218 *
1219 * @throws {Error}
1220 *         Throws an error if `classToCheck` has white space.
1221 */
1222function hasClass(element, classToCheck) {
1223  throwIfWhitespace(classToCheck);
1224  return element.classList.contains(classToCheck);
1225}
1226
1227/**
1228 * Add a class name to an element.
1229 *
1230 * @param  {Element} element
1231 *         Element to add class name to.
1232 *
1233 * @param  {...string} classesToAdd
1234 *         One or more class name to add.
1235 *
1236 * @return {Element}
1237 *         The DOM element with the added class name.
1238 */
1239function addClass(element, ...classesToAdd) {
1240  element.classList.add(
1241    ...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), [])
1242  );
1243  return element;
1244}
1245
vendor: 4,100 bytes, lines 1246-1375
1246/**
1247 * Remove a class name from an element.
1248 *
1249 * @param  {Element} element
1250 *         Element to remove a class name from.
1251 *
1252 * @param  {...string} classesToRemove
1253 *         One or more class name to remove.
1254 *
1255 * @return {Element}
1256 *         The DOM element with class name removed.
1257 */
1258function removeClass(element, ...classesToRemove) {
1259  // Protect in case the player gets disposed
1260  if (!element) {
1261    log$1.warn("removeClass was called with an element that doesn't exist");
1262    return null;
1263  }
1264  element.classList.remove(
1265    ...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), [])
1266  );
1267  return element;
1268}
1269
1270/**
1271 * The callback definition for toggleClass.
1272 *
1273 * @callback PredicateCallback
1274 * @param    {Element} element
1275 *           The DOM element of the Component.
1276 *
1277 * @param    {string} classToToggle
1278 *           The `className` that wants to be toggled
1279 *
1280 * @return   {boolean|undefined}
1281 *           If `true` is returned, the `classToToggle` will be added to the
1282 *           `element`, but not removed. If `false`, the `classToToggle` will be removed from
1283 *           the `element`, but not added. If `undefined`, the callback will be ignored.
1284 *
1285 */
1286
1287/**
1288 * Adds or removes a class name to/from an element depending on an optional
1289 * condition or the presence/absence of the class name.
1290 *
1291 * @param  {Element} element
1292 *         The element to toggle a class name on.
1293 *
1294 * @param  {string} classToToggle
1295 *         The class that should be toggled.
1296 *
1297 * @param  {boolean|PredicateCallback} [predicate]
1298 *         See the return value for {@link module:dom~PredicateCallback}
1299 *
1300 * @return {Element}
1301 *         The element with a class that has been toggled.
1302 */
1303function toggleClass(element, classToToggle, predicate) {
1304  if (typeof predicate === 'function') {
1305    predicate = predicate(element, classToToggle);
1306  }
1307  if (typeof predicate !== 'boolean') {
1308    predicate = undefined;
1309  }
1310  classToToggle.split(/\s+/).forEach((className) => element.classList.toggle(className, predicate));
1311  return element;
1312}
1313
1314/**
1315 * Apply attributes to an HTML element.
1316 *
1317 * @param {Element} el
1318 *        Element to add attributes to.
1319 *
1320 * @param {Object} [attributes]
1321 *        Attributes to be applied.
1322 */
1323function setAttributes(el, attributes) {
1324  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1325    const attrValue = attributes[attrName];
1326    if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1327      el.removeAttribute(attrName);
1328    } else {
1329      el.setAttribute(attrName, attrValue === true ? '' : attrValue);
1330    }
1331  });
1332}
1333
1334/**
1335 * Get an element's attribute values, as defined on the HTML tag.
1336 *
1337 * Attributes are not the same as properties. They're defined on the tag
1338 * or with setAttribute.
1339 *
1340 * @param  {Element} tag
1341 *         Element from which to get tag attributes.
1342 *
1343 * @return {Object}
1344 *         All attributes of the element. Boolean attributes will be `true` or
1345 *         `false`, others will be strings.
1346 */
1347function getAttributes(tag) {
1348  const obj = {};
1349
1350  // known boolean attributes
1351  // we can check for matching boolean properties, but not all browsers
1352  // and not all tags know about these attributes, so, we still want to check them manually
1353  const knownBooleans = [
1354    'autoplay',
1355    'controls',
1356    'playsinline',
1357    'loop',
1358    'muted',
1359    'default',
1360    'defaultMuted',
1361  ];
1362  if (tag && tag.attributes && tag.attributes.length > 0) {
1363    const attrs = tag.attributes;
1364    for (let i = attrs.length - 1; i >= 0; i--) {
1365      const attrName = attrs[i].name;
1366      /** @type {boolean|string} */
1367      let attrVal = attrs[i].value;
1368
1369      // check for known booleans
1370      // the matching element property will return a value for typeof
1371      if (knownBooleans.includes(attrName)) {
1372        // the value of an included boolean attribute is typically an empty
1373        // string ('') which would equal false if we just check for a false value.
1374        // we also don't want support bad code like autoplay='false'
1375        attrVal = attrVal !== null ? true : false;
vendor: 8,609 bytes, lines 1376-1684
1376      }
1377      obj[attrName] = attrVal;
1378    }
1379  }
1380  return obj;
1381}
1382
1383/**
1384 * Get the value of an element's attribute.
1385 *
1386 * @param {Element} el
1387 *        A DOM element.
1388 *
1389 * @param {string} attribute
1390 *        Attribute to get the value of.
1391 *
1392 * @return {string}
1393 *         The value of the attribute.
1394 */
1395function getAttribute(el, attribute) {
1396  return el.getAttribute(attribute);
1397}
1398
1399/**
1400 * Set the value of an element's attribute.
1401 *
1402 * @param {Element} el
1403 *        A DOM element.
1404 *
1405 * @param {string} attribute
1406 *        Attribute to set.
1407 *
1408 * @param {string} value
1409 *        Value to set the attribute to.
1410 */
1411function setAttribute(el, attribute, value) {
1412  el.setAttribute(attribute, value);
1413}
1414
1415/**
1416 * Remove an element's attribute.
1417 *
1418 * @param {Element} el
1419 *        A DOM element.
1420 *
1421 * @param {string} attribute
1422 *        Attribute to remove.
1423 */
1424function removeAttribute(el, attribute) {
1425  el.removeAttribute(attribute);
1426}
1427
1428/**
1429 * Attempt to block the ability to select text.
1430 */
1431function blockTextSelection() {
1432  document__default['default'].body.focus();
1433  document__default['default'].onselectstart = function () {
1434    return false;
1435  };
1436}
1437
1438/**
1439 * Turn off text selection blocking.
1440 */
1441function unblockTextSelection() {
1442  document__default['default'].onselectstart = function () {
1443    return true;
1444  };
1445}
1446
1447/**
1448 * Identical to the native `getBoundingClientRect` function, but ensures that
1449 * the method is supported at all (it is in all browsers we claim to support)
1450 * and that the element is in the DOM before continuing.
1451 *
1452 * This wrapper function also shims properties which are not provided by some
1453 * older browsers (namely, IE8).
1454 *
1455 * Additionally, some browsers do not support adding properties to a
1456 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1457 * properties (except `x` and `y` which are not widely supported). This helps
1458 * avoid implementations where keys are non-enumerable.
1459 *
1460 * @param  {Element} el
1461 *         Element whose `ClientRect` we want to calculate.
1462 *
1463 * @return {Object|undefined}
1464 *         Always returns a plain object - or `undefined` if it cannot.
1465 */
1466function getBoundingClientRect(el) {
1467  if (el && el.getBoundingClientRect && el.parentNode) {
1468    const rect = el.getBoundingClientRect();
1469    const result = {};
1470    ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach((k) => {
1471      if (rect[k] !== undefined) {
1472        result[k] = rect[k];
1473      }
1474    });
1475    if (!result.height) {
1476      result.height = parseFloat(computedStyle(el, 'height'));
1477    }
1478    if (!result.width) {
1479      result.width = parseFloat(computedStyle(el, 'width'));
1480    }
1481    return result;
1482  }
1483}
1484
1485/**
1486 * Represents the position of a DOM element on the page.
1487 *
1488 * @typedef  {Object} module:dom~Position
1489 *
1490 * @property {number} left
1491 *           Pixels to the left.
1492 *
1493 * @property {number} top
1494 *           Pixels from the top.
1495 */
1496
1497/**
1498 * Get the position of an element in the DOM.
1499 *
1500 * Uses `getBoundingClientRect` technique from John Resig.
1501 *
1502 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1503 *
1504 * @param  {Element} el
1505 *         Element from which to get offset.
1506 *
1507 * @return {module:dom~Position}
1508 *         The position of the element that was passed in.
1509 */
1510function findPosition(el) {
1511  if (!el || (el && !el.offsetParent)) {
1512    return {
1513      left: 0,
1514      top: 0,
1515      width: 0,
1516      height: 0,
1517    };
1518  }
1519  const width = el.offsetWidth;
1520  const height = el.offsetHeight;
1521  let left = 0;
1522  let top = 0;
1523  while (el.offsetParent && el !== document__default['default'][FullscreenApi.fullscreenElement]) {
1524    left += el.offsetLeft;
1525    top += el.offsetTop;
1526    el = el.offsetParent;
1527  }
1528  return {
1529    left,
1530    top,
1531    width,
1532    height,
1533  };
1534}
1535
1536/**
1537 * Represents x and y coordinates for a DOM element or mouse pointer.
1538 *
1539 * @typedef  {Object} module:dom~Coordinates
1540 *
1541 * @property {number} x
1542 *           x coordinate in pixels
1543 *
1544 * @property {number} y
1545 *           y coordinate in pixels
1546 */
1547
1548/**
1549 * Get the pointer position within an element.
1550 *
1551 * The base on the coordinates are the bottom left of the element.
1552 *
1553 * @param  {Element} el
1554 *         Element on which to get the pointer position on.
1555 *
1556 * @param  {Event} event
1557 *         Event object.
1558 *
1559 * @return {module:dom~Coordinates}
1560 *         A coordinates object corresponding to the mouse position.
1561 *
1562 */
1563function getPointerPosition(el, event) {
1564  const translated = {
1565    x: 0,
1566    y: 0,
1567  };
1568  if (IS_IOS) {
1569    let item = el;
1570    while (item && item.nodeName.toLowerCase() !== 'html') {
1571      const transform = computedStyle(item, 'transform');
1572      if (/^matrix/.test(transform)) {
1573        const values = transform.slice(7, -1).split(/,\s/).map(Number);
1574        translated.x += values[4];
1575        translated.y += values[5];
1576      } else if (/^matrix3d/.test(transform)) {
1577        const values = transform.slice(9, -1).split(/,\s/).map(Number);
1578        translated.x += values[12];
1579        translated.y += values[13];
1580      }
1581      if (
1582        item.assignedSlot &&
1583        item.assignedSlot.parentElement &&
1584        window__default['default'].WebKitCSSMatrix
1585      ) {
1586        const transformValue = window__default['default'].getComputedStyle(
1587          item.assignedSlot.parentElement
1588        ).transform;
1589        const matrix = new window__default['default'].WebKitCSSMatrix(transformValue);
1590        translated.x += matrix.m41;
1591        translated.y += matrix.m42;
1592      }
1593      item = item.parentNode || item.host;
1594    }
1595  }
1596  const position = {};
1597  const boxTarget = findPosition(event.target);
1598  const box = findPosition(el);
1599  const boxW = box.width;
1600  const boxH = box.height;
1601  let offsetY = event.offsetY - (box.top - boxTarget.top);
1602  let offsetX = event.offsetX - (box.left - boxTarget.left);
1603  if (event.changedTouches) {
1604    offsetX = event.changedTouches[0].pageX - box.left;
1605    offsetY = event.changedTouches[0].pageY + box.top;
1606    if (IS_IOS) {
1607      offsetX -= translated.x;
1608      offsetY -= translated.y;
1609    }
1610  }
1611  position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
1612  position.x = Math.max(0, Math.min(1, offsetX / boxW));
1613  return position;
1614}
1615
1616/**
1617 * Determines, via duck typing, whether or not a value is a text node.
1618 *
1619 * @param  {*} value
1620 *         Check if this value is a text node.
1621 *
1622 * @return {boolean}
1623 *         Will be `true` if the value is a text node, `false` otherwise.
1624 */
1625function isTextNode(value) {
1626  return isObject(value) && value.nodeType === 3;
1627}
1628
1629/**
1630 * Empties the contents of an element.
1631 *
1632 * @param  {Element} el
1633 *         The element to empty children from
1634 *
1635 * @return {Element}
1636 *         The element with no children
1637 */
1638function emptyEl(el) {
1639  while (el.firstChild) {
1640    el.removeChild(el.firstChild);
1641  }
1642  return el;
1643}
1644
1645/**
1646 * This is a mixed value that describes content to be injected into the DOM
1647 * via some method. It can be of the following types:
1648 *
1649 * Type       | Description
1650 * -----------|-------------
1651 * `string`   | The value will be normalized into a text node.
1652 * `Element`  | The value will be accepted as-is.
1653 * `Text`     | A TextNode. The value will be accepted as-is.
1654 * `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).
1655 * `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.
1656 *
1657 * @typedef {string|Element|Text|Array|Function} ContentDescriptor
1658 */
1659
1660/**
1661 * Normalizes content for eventual insertion into the DOM.
1662 *
1663 * This allows a wide range of content definition methods, but helps protect
1664 * from falling into the trap of simply writing to `innerHTML`, which could
1665 * be an XSS concern.
1666 *
1667 * The content for an element can be passed in multiple types and
1668 * combinations, whose behavior is as follows:
1669 *
1670 * @param {ContentDescriptor} content
1671 *        A content descriptor value.
1672 *
1673 * @return {Array}
1674 *         All of the content that was passed in, normalized to an array of
1675 *         elements or text nodes.
1676 */
1677function normalizeContent(content) {
1678  // First, invoke content if it is a function. If it produces an array,
1679  // that needs to happen before normalization.
1680  if (typeof content === 'function') {
1681    content = content();
1682  }
1683
1684  // Next up, normalize to an array, so one or many items can be normalized,
1685  // filtered, and returned.
1686  return (Array.isArray(content) ? content : [content])
1687    .map((value) => {
1688      // First, invoke value if it is a function to produce a new value,
1689      // which will be subsequently normalized to a Node of some kind.
1690      if (typeof value === 'function') {
1691        value = value();
1692      }
1693      if (isEl(value) || isTextNode(value)) {
1694        return value;
1695      }
1696      if (typeof value === 'string' && /\S/.test(value)) {
1697        return document__default['default'].createTextNode(value);
1698      }
1699    })
1700    .filter((value) => value);
1701}
1702
1703/**
1704 * Normalizes and appends content to an element.
1705 *
1706 * @param  {Element} el
1707 *         Element to append normalized content to.
1708 *
1709 * @param {ContentDescriptor} content
1710 *        A content descriptor value.
1711 *
1712 * @return {Element}
1713 *         The element with appended normalized content.
1714 */
1715function appendContent(el, content) {
1716  normalizeContent(content).forEach((node) => el.appendChild(node));
1717  return el;
1718}
1719
1720/**
1721 * Normalizes and inserts content into an element; this is identical to
1722 * `appendContent()`, except it empties the element first.
1723 *
1724 * @param {Element} el
1725 *        Element to insert normalized content into.
1726 *
1727 * @param {ContentDescriptor} content
1728 *        A content descriptor value.
1729 *
1730 * @return {Element}
1731 *         The element with inserted normalized content.
1732 */
1733function insertContent(el, content) {
1734  return appendContent(emptyEl(el), content);
1735}
1736
1737/**
1738 * Check if an event was a single left click.
1739 *
1740 * @param  {MouseEvent} event
1741 *         Event object.
1742 *
1743 * @return {boolean}
1744 *         Will be `true` if a single left click, `false` otherwise.
1745 */
1746function isSingleLeftClick(event) {
1747  // Note: if you create something draggable, be sure to
1748  // call it on both `mousedown` and `mousemove` event,
1749  // otherwise `mousedown` should be enough for a button
1750
1751  if (event.button === undefined && event.buttons === undefined) {
1752    // Why do we need `buttons` ?
1753    // Because, middle mouse sometimes have this:
1754    // e.button === 0 and e.buttons === 4
1755    // Furthermore, we want to prevent combination click, something like
1756    // HOLD middlemouse then left click, that would be
1757    // e.button === 0, e.buttons === 5
1758    // just `button` is not gonna work
1759
1760    // Alright, then what this block does ?
1761    // this is for chrome `simulate mobile devices`
1762    // I want to support this as well
1763
1764    return true;
1765  }
1766  if (event.button === 0 && event.buttons === undefined) {
1767    // Touch screen, sometimes on some specific device, `buttons`
1768    // doesn't have anything (safari on ios, blackberry...)
1769
1770    return true;
1771  }
1772
1773  // `mouseup` event on a single left click has
1774  // `button` and `buttons` equal to 0
1775  if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
1776    return true;
1777  }
1778
1779  // MacOS Sonoma trackpad when "tap to click enabled"
1780  if (event.type === 'mousedown' && event.button === 0 && event.buttons === 0) {
1781    return true;
1782  }
1783  if (event.button !== 0 || event.buttons !== 1) {
1784    // This is the reason we have those if else block above
1785    // if any special case we can catch and let it slide
1786    // we do it above, when get to here, this definitely
1787    // is-not-left-click
1788
1789    return false;
1790  }
1791  return true;
1792}
1793
1794/**
1795 * Finds a single DOM element matching `selector` within the optional
1796 * `context` of another DOM element (defaulting to `document`).
1797 *
1798 * @param  {string} selector
1799 *         A valid CSS selector, which will be passed to `querySelector`.
1800 *
1801 * @param  {Element|String} [context=document]
1802 *         A DOM element within which to query. Can also be a selector
1803 *         string in which case the first matching element will be used
1804 *         as context. If missing (or no element matches selector), falls
1805 *         back to `document`.
1806 *
1807 * @return {Element|null}
1808 *         The element that was found or null.
1809 */
1810const $ = createQuerier('querySelector');
1811
1812/**
1813 * Finds a all DOM elements matching `selector` within the optional
1814 * `context` of another DOM element (defaulting to `document`).
1815 *
1816 * @param  {string} selector
1817 *         A valid CSS selector, which will be passed to `querySelectorAll`.
1818 *
1819 * @param  {Element|String} [context=document]
1820 *         A DOM element within which to query. Can also be a selector
vendor: 16,730 bytes, lines 1821-2410
1821 *         string in which case the first matching element will be used
1822 *         as context. If missing (or no element matches selector), falls
1823 *         back to `document`.
1824 *
1825 * @return {NodeList}
1826 *         A element list of elements that were found. Will be empty if none
1827 *         were found.
1828 *
1829 */
1830const $$ = createQuerier('querySelectorAll');
1831
1832/**
1833 * A safe getComputedStyle.
1834 *
1835 * This is needed because in Firefox, if the player is loaded in an iframe with
1836 * `display:none`, then `getComputedStyle` returns `null`, so, we do a
1837 * null-check to make sure that the player doesn't break in these cases.
1838 *
1839 * @param    {Element} el
1840 *           The element you want the computed style of
1841 *
1842 * @param    {string} prop
1843 *           The property name you want
1844 *
1845 * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
1846 */
1847function computedStyle(el, prop) {
1848  if (!el || !prop) {
1849    return '';
1850  }
1851  if (typeof window__default['default'].getComputedStyle === 'function') {
1852    let computedStyleValue;
1853    try {
1854      computedStyleValue = window__default['default'].getComputedStyle(el);
1855    } catch (e) {
1856      return '';
1857    }
1858    return computedStyleValue
1859      ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop]
1860      : '';
1861  }
1862  return '';
1863}
1864
1865/**
1866 * Copy document style sheets to another window.
1867 *
1868 * @param    {Window} win
1869 *           The window element you want to copy the document style sheets to.
1870 *
1871 */
1872function copyStyleSheetsToWindow(win) {
1873  [...document__default['default'].styleSheets].forEach((styleSheet) => {
1874    try {
1875      const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
1876      const style = document__default['default'].createElement('style');
1877      style.textContent = cssRules;
1878      win.document.head.appendChild(style);
1879    } catch (e) {
1880      const link = document__default['default'].createElement('link');
1881      link.rel = 'stylesheet';
1882      link.type = styleSheet.type;
1883      // For older Safari this has to be the string; on other browsers setting the MediaList works
1884      link.media = styleSheet.media.mediaText;
1885      link.href = styleSheet.href;
1886      win.document.head.appendChild(link);
1887    }
1888  });
1889}
1890
1891var Dom = /*#__PURE__*/ Object.freeze({
1892  __proto__: null,
1893  isReal: isReal,
1894  isEl: isEl,
1895  isInFrame: isInFrame,
1896  createEl: createEl,
1897  textContent: textContent,
1898  prependTo: prependTo,
1899  hasClass: hasClass,
1900  addClass: addClass,
1901  removeClass: removeClass,
1902  toggleClass: toggleClass,
1903  setAttributes: setAttributes,
1904  getAttributes: getAttributes,
1905  getAttribute: getAttribute,
1906  setAttribute: setAttribute,
1907  removeAttribute: removeAttribute,
1908  blockTextSelection: blockTextSelection,
1909  unblockTextSelection: unblockTextSelection,
1910  getBoundingClientRect: getBoundingClientRect,
1911  findPosition: findPosition,
1912  getPointerPosition: getPointerPosition,
1913  isTextNode: isTextNode,
1914  emptyEl: emptyEl,
1915  normalizeContent: normalizeContent,
1916  appendContent: appendContent,
1917  insertContent: insertContent,
1918  isSingleLeftClick: isSingleLeftClick,
1919  $: $,
1920  $$: $$,
1921  computedStyle: computedStyle,
1922  copyStyleSheetsToWindow: copyStyleSheetsToWindow,
1923});
1924
1925/**
1926 * @file setup.js - Functions for setting up a player without
1927 * user interaction based on the data-setup `attribute` of the video tag.
1928 *
1929 * @module setup
1930 */
1931let _windowLoaded = false;
1932let videojs$1;
1933
1934/**
1935 * Set up any tags that have a data-setup `attribute` when the player is started.
1936 */
1937const autoSetup = function () {
1938  if (videojs$1.options.autoSetup === false) {
1939    return;
1940  }
1941  const vids = Array.prototype.slice.call(
1942    document__default['default'].getElementsByTagName('video')
1943  );
1944  const audios = Array.prototype.slice.call(
1945    document__default['default'].getElementsByTagName('audio')
1946  );
1947  const divs = Array.prototype.slice.call(
1948    document__default['default'].getElementsByTagName('video-js')
1949  );
1950  const mediaEls = vids.concat(audios, divs);
1951
1952  // Check if any media elements exist
1953  if (mediaEls && mediaEls.length > 0) {
1954    for (let i = 0, e = mediaEls.length; i < e; i++) {
1955      const mediaEl = mediaEls[i];
1956
1957      // Check if element exists, has getAttribute func.
1958      if (mediaEl && mediaEl.getAttribute) {
1959        // Make sure this player hasn't already been set up.
1960        if (mediaEl.player === undefined) {
1961          const options = mediaEl.getAttribute('data-setup');
1962
1963          // Check if data-setup attr exists.
1964          // We only auto-setup if they've added the data-setup attr.
1965          if (options !== null) {
1966            // Create new video.js instance.
1967            videojs$1(mediaEl);
1968          }
1969        }
1970
1971        // If getAttribute isn't defined, we need to wait for the DOM.
1972      } else {
1973        autoSetupTimeout(1);
1974        break;
1975      }
1976    }
1977
1978    // No videos were found, so keep looping unless page is finished loading.
1979  } else if (!_windowLoaded) {
1980    autoSetupTimeout(1);
1981  }
1982};
1983
1984/**
1985 * Wait until the page is loaded before running autoSetup. This will be called in
1986 * autoSetup if `hasLoaded` returns false.
1987 *
1988 * @param {number} wait
1989 *        How long to wait in ms
1990 *
1991 * @param {module:videojs} [vjs]
1992 *        The videojs library function
1993 */
1994function autoSetupTimeout(wait, vjs) {
1995  // Protect against breakage in non-browser environments
1996  if (!isReal()) {
1997    return;
1998  }
1999  if (vjs) {
2000    videojs$1 = vjs;
2001  }
2002  window__default['default'].setTimeout(autoSetup, wait);
2003}
2004
2005/**
2006 * Used to set the internal tracking of window loaded state to true.
2007 *
2008 * @private
2009 */
2010function setWindowLoaded() {
2011  _windowLoaded = true;
2012  window__default['default'].removeEventListener('load', setWindowLoaded);
2013}
2014if (isReal()) {
2015  if (document__default['default'].readyState === 'complete') {
2016    setWindowLoaded();
2017  } else {
2018    /**
2019     * Listen for the load event on window, and set _windowLoaded to true.
2020     *
2021     * We use a standard event listener here to avoid incrementing the GUID
2022     * before any players are created.
2023     *
2024     * @listens load
2025     */
2026    window__default['default'].addEventListener('load', setWindowLoaded);
2027  }
2028}
2029
2030/**
2031 * @file stylesheet.js
2032 * @module stylesheet
2033 */
2034
2035/**
2036 * Create a DOM style element given a className for it.
2037 *
2038 * @param {string} className
2039 *        The className to add to the created style element.
2040 *
2041 * @return {Element}
2042 *         The element that was created.
2043 */
2044const createStyleElement = function (className) {
2045  const style = document__default['default'].createElement('style');
2046  style.className = className;
2047  return style;
2048};
2049
2050/**
2051 * Add text to a DOM element.
2052 *
2053 * @param {Element} el
2054 *        The Element to add text content to.
2055 *
2056 * @param {string} content
2057 *        The text to add to the element.
2058 */
2059const setTextContent = function (el, content) {
2060  if (el.styleSheet) {
2061    el.styleSheet.cssText = content;
2062  } else {
2063    el.textContent = content;
2064  }
2065};
2066
2067/**
2068 * @file dom-data.js
2069 * @module dom-data
2070 */
2071
2072/**
2073 * Element Data Store.
2074 *
2075 * Allows for binding data to an element without putting it directly on the
2076 * element. Ex. Event listeners are stored here.
2077 * (also from jsninja.com, slightly modified and updated for closure compiler)
2078 *
2079 * @type {Object}
2080 * @private
2081 */
2082var DomData = new WeakMap();
2083
2084/**
2085 * @file guid.js
2086 * @module guid
2087 */
2088
2089// Default value for GUIDs. This allows us to reset the GUID counter in tests.
2090//
2091// The initial GUID is 3 because some users have come to rely on the first
2092// default player ID ending up as `vjs_video_3`.
2093//
2094// See: https://github.com/videojs/video.js/pull/6216
2095const _initialGuid = 3;
2096
2097/**
2098 * Unique ID for an element or function
2099 *
2100 * @type {Number}
2101 */
2102let _guid = _initialGuid;
2103
2104/**
2105 * Get a unique auto-incrementing ID by number that has not been returned before.
2106 *
2107 * @return {number}
2108 *         A new unique ID.
2109 */
2110function newGUID() {
2111  return _guid++;
2112}
2113
2114/**
2115 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
2116 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
2117 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
2118 * robust as jquery's, so there's probably some differences.
2119 *
2120 * @file events.js
2121 * @module events
2122 */
2123
2124/**
2125 * Clean up the listener cache and dispatchers
2126 *
2127 * @param {Element|Object} elem
2128 *        Element to clean up
2129 *
2130 * @param {string} type
2131 *        Type of event to clean up
2132 */
2133function _cleanUpEvents(elem, type) {
2134  if (!DomData.has(elem)) {
2135    return;
2136  }
2137  const data = DomData.get(elem);
2138
2139  // Remove the events of a particular type if there are none left
2140  if (data.handlers[type].length === 0) {
2141    delete data.handlers[type];
2142    // data.handlers[type] = null;
2143    // Setting to null was causing an error with data.handlers
2144
2145    // Remove the meta-handler from the element
2146    if (elem.removeEventListener) {
2147      elem.removeEventListener(type, data.dispatcher, false);
2148    } else if (elem.detachEvent) {
2149      elem.detachEvent('on' + type, data.dispatcher);
2150    }
2151  }
2152
2153  // Remove the events object if there are no types left
2154  if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
2155    delete data.handlers;
2156    delete data.dispatcher;
2157    delete data.disabled;
2158  }
2159
2160  // Finally remove the element data if there is no data left
2161  if (Object.getOwnPropertyNames(data).length === 0) {
2162    DomData.delete(elem);
2163  }
2164}
2165
2166/**
2167 * Loops through an array of event types and calls the requested method for each type.
2168 *
2169 * @param {Function} fn
2170 *        The event method we want to use.
2171 *
2172 * @param {Element|Object} elem
2173 *        Element or object to bind listeners to
2174 *
2175 * @param {string[]} types
2176 *        Type of event to bind to.
2177 *
2178 * @param {Function} callback
2179 *        Event listener.
2180 */
2181function _handleMultipleEvents(fn, elem, types, callback) {
2182  types.forEach(function (type) {
2183    // Call the event method for each one of the types
2184    fn(elem, type, callback);
2185  });
2186}
2187
2188/**
2189 * Fix a native event to have standard property values
2190 *
2191 * @param {Object} event
2192 *        Event object to fix.
2193 *
2194 * @return {Object}
2195 *         Fixed event object.
2196 */
2197function fixEvent(event) {
2198  if (event.fixed_) {
2199    return event;
2200  }
2201  function returnTrue() {
2202    return true;
2203  }
2204  function returnFalse() {
2205    return false;
2206  }
2207
2208  // Test if fixing up is needed
2209  // Used to check if !event.stopPropagation instead of isPropagationStopped
2210  // But native events return true for stopPropagation, but don't have
2211  // other expected methods like isPropagationStopped. Seems to be a problem
2212  // with the Javascript Ninja code. So we're just overriding all events now.
2213  if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
2214    const old = event || window__default['default'].event;
2215    event = {};
2216    // Clone the old object so that we can modify the values event = {};
2217    // IE8 Doesn't like when you mess with native event properties
2218    // Firefox returns false for event.hasOwnProperty('type') and other props
2219    //  which makes copying more difficult.
2220
2221    // TODO: Probably best to create an allowlist of event props
2222    const deprecatedProps = [
2223      'layerX',
2224      'layerY',
2225      'keyLocation',
2226      'path',
2227      'webkitMovementX',
2228      'webkitMovementY',
2229      'mozPressure',
2230      'mozInputSource',
2231    ];
2232    for (const key in old) {
2233      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
2234      // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
2235      // and webkitMovementX/Y
2236      // Lighthouse complains if Event.path is copied
2237      if (!deprecatedProps.includes(key)) {
2238        // Chrome 32+ warns if you try to copy deprecated returnValue, but
2239        // we still want to if preventDefault isn't supported (IE8).
2240        if (!(key === 'returnValue' && old.preventDefault)) {
2241          event[key] = old[key];
2242        }
2243      }
2244    }
2245
2246    // The event occurred on this element
2247    if (!event.target) {
2248      event.target = event.srcElement || document__default['default'];
2249    }
2250
2251    // Handle which other element the event is related to
2252    if (!event.relatedTarget) {
2253      event.relatedTarget =
2254        event.fromElement === event.target ? event.toElement : event.fromElement;
2255    }
2256
2257    // Stop the default browser action
2258    event.preventDefault = function () {
2259      if (old.preventDefault) {
2260        old.preventDefault();
2261      }
2262      event.returnValue = false;
2263      old.returnValue = false;
2264      event.defaultPrevented = true;
2265    };
2266    event.defaultPrevented = false;
2267
2268    // Stop the event from bubbling
2269    event.stopPropagation = function () {
2270      if (old.stopPropagation) {
2271        old.stopPropagation();
2272      }
2273      event.cancelBubble = true;
2274      old.cancelBubble = true;
2275      event.isPropagationStopped = returnTrue;
2276    };
2277    event.isPropagationStopped = returnFalse;
2278
2279    // Stop the event from bubbling and executing other handlers
2280    event.stopImmediatePropagation = function () {
2281      if (old.stopImmediatePropagation) {
2282        old.stopImmediatePropagation();
2283      }
2284      event.isImmediatePropagationStopped = returnTrue;
2285      event.stopPropagation();
2286    };
2287    event.isImmediatePropagationStopped = returnFalse;
2288
2289    // Handle mouse position
2290    if (event.clientX !== null && event.clientX !== undefined) {
2291      const doc = document__default['default'].documentElement;
2292      const body = document__default['default'].body;
2293      event.pageX =
2294        event.clientX +
2295        ((doc && doc.scrollLeft) || (body && body.scrollLeft) || 0) -
2296        ((doc && doc.clientLeft) || (body && body.clientLeft) || 0);
2297      event.pageY =
2298        event.clientY +
2299        ((doc && doc.scrollTop) || (body && body.scrollTop) || 0) -
2300        ((doc && doc.clientTop) || (body && body.clientTop) || 0);
2301    }
2302
2303    // Handle key presses
2304    event.which = event.charCode || event.keyCode;
2305
2306    // Fix button for mouse clicks:
2307    // 0 == left; 1 == middle; 2 == right
2308    if (event.button !== null && event.button !== undefined) {
2309      // The following is disabled because it does not pass videojs-standard
2310      // and... yikes.
2311      /* eslint-disable */
2312      event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
2313      /* eslint-enable */
2314    }
2315  }
2316  event.fixed_ = true;
2317  // Returns fixed-up instance
2318  return event;
2319}
2320
2321/**
2322 * Whether passive event listeners are supported
2323 */
2324let _supportsPassive;
2325const supportsPassive = function () {
2326  if (typeof _supportsPassive !== 'boolean') {
2327    _supportsPassive = false;
2328    try {
2329      const opts = Object.defineProperty({}, 'passive', {
2330        get() {
2331          _supportsPassive = true;
2332        },
2333      });
2334      window__default['default'].addEventListener('test', null, opts);
2335      window__default['default'].removeEventListener('test', null, opts);
2336    } catch (e) {
2337      // disregard
2338    }
2339  }
2340  return _supportsPassive;
2341};
2342
2343/**
2344 * Touch events Chrome expects to be passive
2345 */
2346const passiveEvents = ['touchstart', 'touchmove'];
2347
2348/**
2349 * Add an event listener to element
2350 * It stores the handler function in a separate cache object
2351 * and adds a generic handler to the element's event,
2352 * along with a unique id (guid) to the element.
2353 *
2354 * @param {Element|Object} elem
2355 *        Element or object to bind listeners to
2356 *
2357 * @param {string|string[]} type
2358 *        Type of event to bind to.
2359 *
2360 * @param {Function} fn
2361 *        Event listener.
2362 */
2363function on(elem, type, fn) {
2364  if (Array.isArray(type)) {
2365    return _handleMultipleEvents(on, elem, type, fn);
2366  }
2367  if (!DomData.has(elem)) {
2368    DomData.set(elem, {});
2369  }
2370  const data = DomData.get(elem);
2371
2372  // We need a place to store all our handler data
2373  if (!data.handlers) {
2374    data.handlers = {};
2375  }
2376  if (!data.handlers[type]) {
2377    data.handlers[type] = [];
2378  }
2379  if (!fn.guid) {
2380    fn.guid = newGUID();
2381  }
2382  data.handlers[type].push(fn);
2383  if (!data.dispatcher) {
2384    data.disabled = false;
2385    data.dispatcher = function (event, hash) {
2386      if (data.disabled) {
2387        return;
2388      }
2389      event = fixEvent(event);
2390      const handlers = data.handlers[event.type];
2391      if (handlers) {
2392        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
2393        const handlersCopy = handlers.slice(0);
2394        for (let m = 0, n = handlersCopy.length; m < n; m++) {
2395          if (event.isImmediatePropagationStopped()) {
2396            break;
2397          } else {
2398            try {
2399              handlersCopy[m].call(elem, event, hash);
2400            } catch (e) {
2401              log$1.error(e);
2402            }
2403          }
2404        }
2405      }
2406    };
2407  }
2408  if (data.handlers[type].length === 1) {
2409    if (elem.addEventListener) {
2410      let options = false;
vendor: 4,198 bytes, lines 2411-2554
2411      if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
2412        options = {
2413          passive: true,
2414        };
2415      }
2416      elem.addEventListener(type, data.dispatcher, options);
2417    } else if (elem.attachEvent) {
2418      elem.attachEvent('on' + type, data.dispatcher);
2419    }
2420  }
2421}
2422
2423/**
2424 * Removes event listeners from an element
2425 *
2426 * @param {Element|Object} elem
2427 *        Object to remove listeners from.
2428 *
2429 * @param {string|string[]} [type]
2430 *        Type of listener to remove. Don't include to remove all events from element.
2431 *
2432 * @param {Function} [fn]
2433 *        Specific listener to remove. Don't include to remove listeners for an event
2434 *        type.
2435 */
2436function off(elem, type, fn) {
2437  // Don't want to add a cache object through getElData if not needed
2438  if (!DomData.has(elem)) {
2439    return;
2440  }
2441  const data = DomData.get(elem);
2442
2443  // If no events exist, nothing to unbind
2444  if (!data.handlers) {
2445    return;
2446  }
2447  if (Array.isArray(type)) {
2448    return _handleMultipleEvents(off, elem, type, fn);
2449  }
2450
2451  // Utility function
2452  const removeType = function (el, t) {
2453    data.handlers[t] = [];
2454    _cleanUpEvents(el, t);
2455  };
2456
2457  // Are we removing all bound events?
2458  if (type === undefined) {
2459    for (const t in data.handlers) {
2460      if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
2461        removeType(elem, t);
2462      }
2463    }
2464    return;
2465  }
2466  const handlers = data.handlers[type];
2467
2468  // If no handlers exist, nothing to unbind
2469  if (!handlers) {
2470    return;
2471  }
2472
2473  // If no listener was provided, remove all listeners for type
2474  if (!fn) {
2475    removeType(elem, type);
2476    return;
2477  }
2478
2479  // We're only removing a single handler
2480  if (fn.guid) {
2481    for (let n = 0; n < handlers.length; n++) {
2482      if (handlers[n].guid === fn.guid) {
2483        handlers.splice(n--, 1);
2484      }
2485    }
2486  }
2487  _cleanUpEvents(elem, type);
2488}
2489
2490/**
2491 * Trigger an event for an element
2492 *
2493 * @param {Element|Object} elem
2494 *        Element to trigger an event on
2495 *
2496 * @param {EventTarget~Event|string} event
2497 *        A string (the type) or an event object with a type attribute
2498 *
2499 * @param {Object} [hash]
2500 *        data hash to pass along with the event
2501 *
2502 * @return {boolean|undefined}
2503 *         Returns the opposite of `defaultPrevented` if default was
2504 *         prevented. Otherwise, returns `undefined`
2505 */
2506function trigger(elem, event, hash) {
2507  // Fetches element data and a reference to the parent (for bubbling).
2508  // Don't want to add a data object to cache for every parent,
2509  // so checking hasElData first.
2510  const elemData = DomData.has(elem) ? DomData.get(elem) : {};
2511  const parent = elem.parentNode || elem.ownerDocument;
2512  // type = event.type || event,
2513  // handler;
2514
2515  // If an event name was passed as a string, creates an event out of it
2516  if (typeof event === 'string') {
2517    event = {
2518      type: event,
2519      target: elem,
2520    };
2521  } else if (!event.target) {
2522    event.target = elem;
2523  }
2524
2525  // Normalizes the event properties.
2526  event = fixEvent(event);
2527
2528  // If the passed element has a dispatcher, executes the established handlers.
2529  if (elemData.dispatcher) {
2530    elemData.dispatcher.call(elem, event, hash);
2531  }
2532
2533  // Unless explicitly stopped or the event does not bubble (e.g. media events)
2534  // recursively calls this function to bubble the event up the DOM.
2535  if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2536    trigger.call(null, parent, event, hash);
2537
2538    // If at the top of the DOM, triggers the default action unless disabled.
2539  } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2540    if (!DomData.has(event.target)) {
2541      DomData.set(event.target, {});
2542    }
2543    const targetData = DomData.get(event.target);
2544
2545    // Checks if the target has a default action for this event.
2546    if (event.target[event.type]) {
2547      // Temporarily disables event dispatching on the target as we have already executed the handler.
2548      targetData.disabled = true;
2549      // Executes the default action.
2550      if (typeof event.target[event.type] === 'function') {
2551        event.target[event.type]();
2552      }
2553      // Re-enables event dispatching.
2554      targetData.disabled = false;
vendor: 5,945 bytes, lines 2555-2774
2555    }
2556  }
2557
2558  // Inform the triggerer if the default was prevented by returning false
2559  return !event.defaultPrevented;
2560}
2561
2562/**
2563 * Trigger a listener only once for an event.
2564 *
2565 * @param {Element|Object} elem
2566 *        Element or object to bind to.
2567 *
2568 * @param {string|string[]} type
2569 *        Name/type of event
2570 *
2571 * @param {Event~EventListener} fn
2572 *        Event listener function
2573 */
2574function one(elem, type, fn) {
2575  if (Array.isArray(type)) {
2576    return _handleMultipleEvents(one, elem, type, fn);
2577  }
2578  const func = function () {
2579    off(elem, type, func);
2580    fn.apply(this, arguments);
2581  };
2582
2583  // copy the guid to the new function so it can removed using the original function's ID
2584  func.guid = fn.guid = fn.guid || newGUID();
2585  on(elem, type, func);
2586}
2587
2588/**
2589 * Trigger a listener only once and then turn if off for all
2590 * configured events
2591 *
2592 * @param {Element|Object} elem
2593 *        Element or object to bind to.
2594 *
2595 * @param {string|string[]} type
2596 *        Name/type of event
2597 *
2598 * @param {Event~EventListener} fn
2599 *        Event listener function
2600 */
2601function any(elem, type, fn) {
2602  const func = function () {
2603    off(elem, type, func);
2604    fn.apply(this, arguments);
2605  };
2606
2607  // copy the guid to the new function so it can removed using the original function's ID
2608  func.guid = fn.guid = fn.guid || newGUID();
2609
2610  // multiple ons, but one off for everything
2611  on(elem, type, func);
2612}
2613
2614var Events = /*#__PURE__*/ Object.freeze({
2615  __proto__: null,
2616  fixEvent: fixEvent,
2617  on: on,
2618  off: off,
2619  trigger: trigger,
2620  one: one,
2621  any: any,
2622});
2623
2624/**
2625 * @file fn.js
2626 * @module fn
2627 */
2628const UPDATE_REFRESH_INTERVAL = 30;
2629
2630/**
2631 * A private, internal-only function for changing the context of a function.
2632 *
2633 * It also stores a unique id on the function so it can be easily removed from
2634 * events.
2635 *
2636 * @private
2637 * @function
2638 * @param    {*} context
2639 *           The object to bind as scope.
2640 *
2641 * @param    {Function} fn
2642 *           The function to be bound to a scope.
2643 *
2644 * @param    {number} [uid]
2645 *           An optional unique ID for the function to be set
2646 *
2647 * @return   {Function}
2648 *           The new function that will be bound into the context given
2649 */
2650const bind_ = function (context, fn, uid) {
2651  // Make sure the function has a unique ID
2652  if (!fn.guid) {
2653    fn.guid = newGUID();
2654  }
2655
2656  // Create the new function that changes the context
2657  const bound = fn.bind(context);
2658
2659  // Allow for the ability to individualize this function
2660  // Needed in the case where multiple objects might share the same prototype
2661  // IF both items add an event listener with the same function, then you try to remove just one
2662  // it will remove both because they both have the same guid.
2663  // when using this, you need to use the bind method when you remove the listener as well.
2664  // currently used in text tracks
2665  bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2666  return bound;
2667};
2668
2669/**
2670 * Wraps the given function, `fn`, with a new function that only invokes `fn`
2671 * at most once per every `wait` milliseconds.
2672 *
2673 * @function
2674 * @param    {Function} fn
2675 *           The function to be throttled.
2676 *
2677 * @param    {number}   wait
2678 *           The number of milliseconds by which to throttle.
2679 *
2680 * @return   {Function}
2681 */
2682const throttle = function (fn, wait) {
2683  let last = window__default['default'].performance.now();
2684  const throttled = function (...args) {
2685    const now = window__default['default'].performance.now();
2686    if (now - last >= wait) {
2687      fn(...args);
2688      last = now;
2689    }
2690  };
2691  return throttled;
2692};
2693
2694/**
2695 * Creates a debounced function that delays invoking `func` until after `wait`
2696 * milliseconds have elapsed since the last time the debounced function was
2697 * invoked.
2698 *
2699 * Inspired by lodash and underscore implementations.
2700 *
2701 * @function
2702 * @param    {Function} func
2703 *           The function to wrap with debounce behavior.
2704 *
2705 * @param    {number} wait
2706 *           The number of milliseconds to wait after the last invocation.
2707 *
2708 * @param    {boolean} [immediate]
2709 *           Whether or not to invoke the function immediately upon creation.
2710 *
2711 * @param    {Object} [context=window]
2712 *           The "context" in which the debounced function should debounce. For
2713 *           example, if this function should be tied to a Video.js player,
2714 *           the player can be passed here. Alternatively, defaults to the
2715 *           global `window` object.
2716 *
2717 * @return   {Function}
2718 *           A debounced function.
2719 */
2720const debounce = function (func, wait, immediate, context = window__default['default']) {
2721  let timeout;
2722  const cancel = () => {
2723    context.clearTimeout(timeout);
2724    timeout = null;
2725  };
2726
2727  /* eslint-disable consistent-this */
2728  const debounced = function () {
2729    const self = this;
2730    const args = arguments;
2731    let later = function () {
2732      timeout = null;
2733      later = null;
2734      if (!immediate) {
2735        func.apply(self, args);
2736      }
2737    };
2738    if (!timeout && immediate) {
2739      func.apply(self, args);
2740    }
2741    context.clearTimeout(timeout);
2742    timeout = context.setTimeout(later, wait);
2743  };
2744  /* eslint-enable consistent-this */
2745
2746  debounced.cancel = cancel;
2747  return debounced;
2748};
2749
2750var Fn = /*#__PURE__*/ Object.freeze({
2751  __proto__: null,
2752  UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
2753  bind_: bind_,
2754  throttle: throttle,
2755  debounce: debounce,
2756});
2757
2758/**
2759 * @file src/js/event-target.js
2760 */
2761let EVENT_MAP;
2762
2763/**
2764 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2765 * adds shorthand functions that wrap around lengthy functions. For example:
2766 * the `on` function is a wrapper around `addEventListener`.
2767 *
2768 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2769 * @class EventTarget
2770 */
2771class EventTarget$2 {
2772  /**
2773   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2774   * function that will get called when an event with a certain 
2774name gets triggered.
2775   *
2776   * @param {string|string[]} type
2777   *        An event name or an array of event names.
2778   *
2779   * @param {Function} fn
2780   *        The function to call with `EventTarget`s
2781   */
2782  on(type, fn) {
2783    // Remove the addEventListener alias before calling Events.on
2784    // so we don't get into an infinite type loop
2785    const ael = this.addEventListener;
2786    this.addEventListener = () => {};
2787    on(this, type, fn);
2788    this.addEventListener = ael;
2789  }
2790  /**
2791   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2792   * This makes it so that the `event listener` will no longer get called when the
2793   * named event happens.
2794   *
2795   * @param {string|string[]} type
2796   *        An event name or an array of event names.
2797   *
2798   * @param {Function} fn
2799   *        The function to remove.
2800   */
2801  off(type, fn) {
2802    off(this, type, fn);
2803  }
2804  /**
2805   * This function will add an `event listener` that gets triggered only once. After the
2806   * first trigger it will get removed. This is like adding an `event listener`
2807   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2808   *
2809   * @param {string|string[]} type
2810   *        An event name or an array of event names.
2811   *
2812   * @param {Function} fn
2813   *        The function to be called once for each event name.
2814   */
2815  one(type, fn) {
2816    // Remove the addEventListener aliasing Events.on
2817    // so we don't get into an infinite type loop
2818    const ael = this.addEventListener;
2819    this.addEventListener = () => {};
2820    one(this, type, fn);
2821    this.addEventListener = ael;
2822  }
2823  /**
2824   * This function will add an `event listener` that gets triggered only once and is
2825   * removed from all events. This is like adding an array of `event listener`s
2826   * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
2827   * first time it is triggered.
2828   *
2829   * @param {string|string[]} type
2830   *        An event name or an array of event names.
2831   *
2832   * @param {Function} fn
2833   *        The function to be called once for each event name.
2834   */
2835  any(type, fn) {
2836    // Remove the addEventListener aliasing Events.on
2837    // so we don't get into an infinite type loop
2838    const ael = this.addEventListener;
2839    this.addEventListener = () => {};
2840    any(this, type, fn);
2841    this.addEventListener = ael;
2842  }
2843  /**
2844   * This function causes an event to happen. This will then cause any `event listeners`
2845   * that are waiting for that event, to get called. If there are no `event listeners`
2846   * for an event then nothing will happen.
2847   *
2848   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2849   * Trigger will also call the `on` + `uppercaseEventName` function.
2850   *
2851   * Example:
2852   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2853   * `onClick` if it exists.
2854   *
2855   * @param {string|EventTarget~Event|Object} event
2856   *        The name of the event, an `Event`, or an object with a key of type set to
2857   *        an event name.
2858   */
2859  trigger(event) {
2860    const type = event.type || event;
2861
2862    // deprecation
2863    // In a future version we should default target to `this`
2864    // similar to how we default the target to `elem` in
2865    // `Events.trigger`. Right now the default `target` will be
2866    // `document` due to the `Event.fixEvent` call.
2867    if (typeof event === 'string') {
2868      event = {
2869        type,
2870      };
2871    }
2872    event = fixEvent(event);
2873    if (this.allowedEvents_[type] && this['on' + type]) {
2874      this['on' + type](event);
2875    }
2876    trigger(this, event);
2877  }
2878  queueTrigger(event) {
2879    // only set up EVENT_MAP if it'll be used
2880    if (!EVENT_MAP) {
2881      EVENT_MAP = new Map();
2882    }
2883    const type = event.type || event;
2884    let map = EVENT_MAP.get(this);
2885    if (!map) {
2886      map = new Map();
2887      EVENT_MAP.set(this, map);
2888    }
2889    const oldTimeout = map.get(type);
2890    map.delete(type);
2891    window__default['default'].clearTimeout(oldTimeout);
2892    const timeout = window__default['default'].setTimeout(() => {
2893      map.delete(type);
2894      // if we cleared out all timeouts for the current target, delete its map
2895      if (map.size === 0) {
2896        map = null;
2897        EVENT_MAP.delete(this);
2898      }
2899      this.trigger(event);
2900    }, 0);
2901    map.set(type, timeout);
2902  }
2903}
2904
2905/**
2906 * A Custom DOM event.
2907 *
2908 * @typedef {CustomEvent} Event
2909 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2910 */
2911
2912/**
2913 * All event listeners should follow the following format.
2914 *
2915 * @callback EventListener
2916 * @this {EventTarget}
2917 *
2918 * @param {Event} event
2919 *        the event that triggered this function
2920 *
2921 * @param {Object} [hash]
2922 *        hash of data sent during the event
2923 */
2924
2925/**
2926 * An object containing event names as keys and booleans as values.
2927 *
2928 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2929 *         will have extra functionality. See that function for more information.
2930 *
2931 * @property EventTarget.prototype.allowedEvents_
2932 * @protected
2933 */
2934EventTarget$2.prototype.allowedEvents_ = {};
2935
2936/**
2937 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2938 * the standard DOM API.
2939 *
2940 * @function
2941 * @see {@link EventTarget#on}
2942 */
2943EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
2944
2945/**
2946 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2947 * the standard DOM API.
2948 *
2949 * @function
2950 * @see {@link EventTarget#off}
2951 */
2952EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
2953
2954/**
2955 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2956 * the standard DOM API.
2957 *
2958 * @function
2959 * @see {@link EventTarget#trigger}
2960 */
2961EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
2962
2963/**
2964 * @file mixins/evented.js
2965 * @module evented
2966 */
2967const objName = (obj) => {
2968  if (typeof obj.name === 'function') {
2969    return obj.name();
2970  }
2971  if (typeof obj.name === 'string') {
2972    return obj.name;
2973  }
2974  if (obj.name_) {
2975    return obj.name_;
2976  }
2977  if (obj.constructor && obj.constructor.name) {
2978    return obj.constructor.name;
2979  }
2980  return typeof obj;
2981};
2982
2983/**
2984 * Returns whether or not an object has had the evented mixin applied.
2985 *
2986 * @param  {Object} object
2987 *         An object to test.
2988 *
2989 * @return {boolean}
2990 *         Whether or not the object appears to be evented.
2991 */
2992const isEvented = (object) =>
2993  object instanceof EventTarget$2 ||
2994  (!!object.eventBusEl_ &&
2995    ['on', 'one', 'off', 'trigger'].every((k) => typeof object[k] === 'function'));
2996
2997/**
2998 * Adds a callback to run after the evented mixin applied.
2999 *
3000 * @param  {Object} target
3001 *         An object to Add
3002 * @param  {Function} callback
3003 *         The callback to run.
3004 */
3005const addEventedCallback = (target, callback) => {
3006  if (isEvented(target)) {
3007    callback();
3008  } else {
3009    if (!target.eventedCallbacks) {
3010      target.eventedCallbacks = [];
3011    }
3012    target.eventedCallbacks.push(callback);
3013  }
3014};
3015
3016/**
3017 * Whether a value is a valid event type - non-empty string or array.
3018 *
3019 * @private
3020 * @param  {string|Array} type
3021 *         The type value to test.
3022 *
3023 * @return {boolean}
3024 *         Whether or not the type is a valid event type.
3025 */
3026const isValidEventType = (type) =>
3027  // The regex here verifies that the `type` contains at least one non-
3028  // whitespace character.
3029  (typeof type === 'string' && /\S/.test(type)) || (Array.isArray(type) && !!type.length);
3030
3031/**
3032 * Validates a value to determine if it is a valid event target. Throws if not.
3033 *
3034 * @private
3035 * @throws {Error}
3036 *         If the target does not appear to be a valid event target.
3037 *
3038 * @param  {Object} target
3039 *         The object to test.
3040 *
3041 * @param  {Object} obj
3042 *         The evented object we are validating for
3043 *
3044 * @param  {string} fnName
3045 *         The name of the evented mixin function that called this.
3046 */
3047const validateTarget = (target, obj, fnName) => {
3048  if (!target || (!target.nodeName && !isEvented(target))) {
3049    throw new Error(
3050      `Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`
3051    );
3052  }
3053};
3054
3055/**
3056 * Validates a value to determine if it is a valid event target. Throws if not.
3057 *
3058 * @private
3059 * @throws {Error}
3060 *         If the type does not appear to be a valid event type.
3061 *
3062 * @param  {string|Array} type
3063 *         The type to test.
3064 *
3065 * @param  {Object} obj
3066 *         The evented object we are validating for
3067 *
3068 * @param  {string} fnName
3069 *         The name of the evented mixin function that called this.
3070 */
3071const validateEventType = (type, obj, fnName) => {
3072  if (!isValidEventType(type)) {
3073    throw new Error(
3074      `Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`
3075    );
3076  }
3077};
3078
3079/**
3080 * Validates a value to determine if it is a valid listener. Throws if not.
3081 *
3082 * @private
3083 * @throws {Error}
3084 *         If the listener is not a function.
3085 *
3086 * @param  {Function} listener
3087 *         The listener to test.
3088 *
3089 * @param  {Object} obj
3090 *         The evented object we are validating for
3091 *
3092 * @param  {string} fnName
3093 *         The name of the evented mixin function that called this.
3094 */
3095const validateListener = (listener, obj, fnName) => {
3096  if (typeof listener !== 'function') {
3097    throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
3098  }
3099};
3100
3101/**
3102 * Takes an array of arguments given to `on()` or `one()`, validates them, and
3103 * normalizes them into an object.
3104 *
3105 * @private
3106 * @param  {Object} self
3107 *         The evented object on which `on()` or `one()` was called. This
3108 *         object will be bound as the `this` value for the listener.
3109 *
3110 * @param  {Array} args
3111 *         An array of arguments passed to `on()` or `one()`.
3112 *
3113 * @param  {string} fnName
3114 *         The name of the evented mixin function that called this.
3115 *
3116 * @return {Object}
vendor: 4,280 bytes, lines 3117-3239
3117 *         An object containing useful values for `on()` or `one()` calls.
3118 */
3119const normalizeListenArgs = (self, args, fnName) => {
3120  // If the number of arguments is less than 3, the target is always the
3121  // evented object itself.
3122  const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
3123  let target;
3124  let type;
3125  let listener;
3126  if (isTargetingSelf) {
3127    target = self.eventBusEl_;
3128
3129    // Deal with cases where we got 3 arguments, but we are still listening to
3130    // the evented object itself.
3131    if (args.length >= 3) {
3132      args.shift();
3133    }
3134    [type, listener] = args;
3135  } else {
3136    // This was `[target, type, listener] = args;` but this block needs more than
3137    // one statement to produce minified output compatible with Chrome 53.
3138    // See https://github.com/videojs/video.js/pull/8810
3139    target = args[0];
3140    type = args[1];
3141    listener = args[2];
3142  }
3143  validateTarget(target, self, fnName);
3144  validateEventType(type, self, fnName);
3145  validateListener(listener, self, fnName);
3146  listener = bind_(self, listener);
3147  return {
3148    isTargetingSelf,
3149    target,
3150    type,
3151    listener,
3152  };
3153};
3154
3155/**
3156 * Adds the listener to the event type(s) on the target, normalizing for
3157 * the type of target.
3158 *
3159 * @private
3160 * @param  {Element|Object} target
3161 *         A DOM node or evented object.
3162 *
3163 * @param  {string} method
3164 *         The event binding method to use ("on" or "one").
3165 *
3166 * @param  {string|Array} type
3167 *         One or more event type(s).
3168 *
3169 * @param  {Function} listener
3170 *         A listener function.
3171 */
3172const listen = (target, method, type, listener) => {
3173  validateTarget(target, target, method);
3174  if (target.nodeName) {
3175    Events[method](target, type, listener);
3176  } else {
3177    target[method](type, listener);
3178  }
3179};
3180
3181/**
3182 * Contains methods that provide event capabilities to an object which is passed
3183 * to {@link module:evented|evented}.
3184 *
3185 * @mixin EventedMixin
3186 */
3187const EventedMixin = {
3188  /**
3189   * Add a listener to an event (or events) on this object or another evented
3190   * object.
3191   *
3192   * @param  {string|Array|Element|Object} targetOrType
3193   *         If this is a string or array, it represents the event type(s)
3194   *         that will trigger the listener.
3195   *
3196   *         Another evented object can be passed here instead, which will
3197   *         cause the listener to listen for events on _that_ object.
3198   *
3199   *         In either case, the listener's `this` value will be bound to
3200   *         this object.
3201   *
3202   * @param  {string|Array|Function} typeOrListener
3203   *         If the first argument was a string or array, this should be the
3204   *         listener function. Otherwise, this is a string or array of event
3205   *         type(s).
3206   *
3207   * @param  {Function} [listener]
3208   *         If the first argument was another evented object, this will be
3209   *         the listener function.
3210   */
3211  on(...args) {
3212    const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'on');
3213    listen(target, 'on', type, listener);
3214
3215    // If this object is listening to another evented object.
3216    if (!isTargetingSelf) {
3217      // If this object is disposed, remove the listener.
3218      const removeListenerOnDispose = () => this.off(target, type, listener);
3219
3220      // Use the same function ID as the listener so we can remove it later it
3221      // using the ID of the original listener.
3222      removeListenerOnDispose.guid = listener.guid;
3223
3224      // Add a listener to the target's dispose event as well. This ensures
3225      // that if the target is disposed BEFORE this object, we remove the
3226      // removal listener that was just added. Otherwise, we create a memory leak.
3227      const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
3228
3229      // Use the same function ID as the listener so we can remove it later
3230      // it using the ID of the original listener.
3231      removeRemoverOnTargetDispose.guid = listener.guid;
3232      listen(this, 'on', 'dispose', removeListenerOnDispose);
3233      listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
3234    }
3235  },
3236  /**
3237   * Add a listener to an event (or events) on this object or another evented
3238   * object. The listener will be called once per event and then removed.
3239   *
3240   * @param  {string|Array|Element|Object} targetOrType
3241   *         If this is a string or array, it represents the event type(s)
3242   *         that will trigger the listener.
3243   *
3244   *         Another evented object can be passed here instead, which will
3245   *         cause the listener to listen for events on _that_ object.
3246   *
3247   *         In either case, the listener's `this` value will be bound to
3248   *         this object.
3249   *
3250   * @param  {string|Array|Function} typeOrListener
3251   *         If the first argument was a string or array, this should be the
3252   *         listener function. Otherwise, this is a string or array of event
3253   *         type(s).
3254   *
3255   * @param  {Function} [listener]
3256   *         If the first argument was another evented object, this will be
3257   *         the listener function.
3258   */
3259  one(...args) {
3260    const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'one');
3261
3262    // Targeting this evented object.
3263    if (isTargetingSelf) {
3264      listen(target, 'one', type, listener);
3265
3266      // Targeting another evented object.
3267    } else {
3268      // TODO: This wrapper is incorrect! It should only
3269      //       remove the wrapper for the event type that called it.
3270      //       Instead all listeners are removed on the first trigger!
3271      //       see https://github.com/videojs/video.js/issues/5962
3272      const wrapper = (...largs) => {
3273        this.off(target, type, wrapper);
3274        listener.apply(null, largs);
3275      };
3276
3277      // Use the same function ID as the listener so we can remove it later
3278      // it using the ID of the original listener.
3279      wrapper.guid = listener.guid;
3280      listen(target, 'one', type, wrapper);
3281    }
3282  },
3283  /**
3284   * Add a listener to an event (or events) on this object or another evented
3285   * object. The listener will only be called once for the first event that is triggered
3286   * then removed.
3287   *
3288   * @param  {string|Array|Element|Object} targetOrType
3289   *         If this is a string or array, it represents the event type(s)
3290   *         that will trigger the listener.
3291   *
3292   *         Another evented object can be passed here instead, which will
3293   *         cause the listener to listen for events on _that_ object.
3294   *
3295   *         In either case, the listener's `this` value will be bound to
3296   *         this object.
3297   *
3298   * @param  {string|Array|Function} typeOrListener
3299   *         If the first argument was a string or array, this should be the
3300   *         listener function. Otherwise, this is a string or array of event
3301   *         type(s).
3302   *
3303   * @param  {Function} [listener]
3304   *         If the first argument was another evented object, this will be
3305   *         the listener function.
3306   */
3307  any(...args) {
3308    const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'any');
3309
3310    // Targeting this evented object.
3311    if (isTargetingSelf) {
3312      listen(target, 'any', type, listener);
3313
3314      // Targeting another evented object.
3315    } else {
3316      const wrapper = (...largs) => {
3317        this.off(target, type, wrapper);
3318        listener.apply(null, largs);
3319      };
3320
3321      // Use the same function ID as the listener so we can remove it later
3322      // it using the ID of the original listener.
3323      wrapper.guid = listener.guid;
3324      listen(target, 'any', type, wrapper);
3325    }
3326  },
3327  /**
3328   * Removes listener(s) from event(s) on an evented object.
3329   *
3330   * @param  {string|Array|Element|Object} [targetOrType]
3331   *         If this is a string or array, it represents the event type(s).
3332   *
3333   *         Another evented object can be passed here instead, in which case
3334   *         ALL 3 arguments are _required_.
3335   *
3336   * @param  {string|Array|Function} [typeOrListener]
3337   *         If the first argument was a string or array, this may be the
3338   *         listener function. Otherwise, this is a string or array of event
3339   *         type(s).
3340   *
3341   * @param  {Function} [listener]
3342   *         If the first argument was another evented object, this will be
3343   *         the listener function; otherwise, _all_ listeners bound to the
3344   *         event type(s) will be removed.
3345   */
3346  off(targetOrType, typeOrListener, listener) {
3347    // Targeting this evented object.
3348    if (!targetOrType || isValidEventType(targetOrType)) {
3349      off(this.eventBusEl_, targetOrType, typeOrListener);
3350
3351      // Targeting another evented object.
3352    } else {
3353      const target = targetOrType;
3354      const type = typeOrListener;
3355
3356      // Fail fast and in a meaningful way!
3357      validateTarget(target, this, 'off');
3358      validateEventType(type, this, 'off');
3359      validateListener(listener, this, 'off');
3360
3361      // Ensure there's at least a guid, even if the function hasn't been used
3362      listener = bind_(this, listener);
3363
3364      // Remove the dispose listener on this evented object, which was given
3365      // the same guid as the event listener in on().
3366      this.off('dispose', listener);
3367      if (target.nodeName) {
3368        off(target, type, listener);
3369        off(target, 'dispose', listener);
3370      } else if (isEvented(target)) {
3371        target.off(type, listener);
3372        target.off('dispose', listener);
3373      }
3374    }
3375  },
3376  /**
3377   * Fire an event on this evented object, causing its listeners to be called.
3378   *
3379   * @param   {string|Object} event
3380   *          An event type or an object with a type property.
3381   *
3382   * @param   {Object} [hash]
3383   *          An additional object to pass along to listeners.
3384   *
3385   * @return {boolean}
3386   *          Whether or not the default behavior was prevented.
3387   */
3388  trigger(event, hash) {
3389    validateTarget(this.eventBusEl_, this, 'trigger');
3390    const type = event && typeof event !== 'string' ? event.type : event;
3391    if (!isValidEventType(type)) {
3392      throw new Error(
3393        `Invalid event type for ${objName(this)}#trigger; ` +
3394          'must be a non-empty string or object with a type key that has a non-empty value.'
3395      );
3396    }
3397    return trigger(this.eventBusEl_, event, hash);
3398  },
3399};
3400
3401/**
3402 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
3403 *
3404 * @param  {Object} target
3405 *         The object to which to add event methods.
3406 *
3407 * @param  {Object} [options={}]
3408 *         Options for customizing the mixin behavior.
3409 *
3410 * @param  {string} [options.eventBusKey]
3411 *         By default, adds a `eventBusEl_` DOM element to the target object,
3412 *         which is used as an event bus. If the target object already has a
3413 *         DOM element that should be used, pass its key here.
3414 *
3415 * @return {Object}
3416 *         The target object.
3417 */
3418function evented(target, options = {}) {
3419  const { eventBusKey } = options;
3420
3421  // Set or create the eventBusEl_.
vendor: 5,759 bytes, lines 3422-3636
3422  if (eventBusKey) {
3423    if (!target[eventBusKey].nodeName) {
3424      throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
3425    }
3426    target.eventBusEl_ = target[eventBusKey];
3427  } else {
3428    target.eventBusEl_ = createEl('span', {
3429      className: 'vjs-event-bus',
3430    });
3431  }
3432  Object.assign(target, EventedMixin);
3433  if (target.eventedCallbacks) {
3434    target.eventedCallbacks.forEach((callback) => {
3435      callback();
3436    });
3437  }
3438
3439  // When any evented object is disposed, it removes all its listeners.
3440  target.on('dispose', () => {
3441    target.off();
3442    [target, target.el_, target.eventBusEl_].forEach(function (val) {
3443      if (val && DomData.has(val)) {
3444        DomData.delete(val);
3445      }
3446    });
3447    window__default['default'].setTimeout(() => {
3448      target.eventBusEl_ = null;
3449    }, 0);
3450  });
3451  return target;
3452}
3453
3454/**
3455 * @file mixins/stateful.js
3456 * @module stateful
3457 */
3458
3459/**
3460 * Contains methods that provide statefulness to an object which is passed
3461 * to {@link module:stateful}.
3462 *
3463 * @mixin StatefulMixin
3464 */
3465const StatefulMixin = {
3466  /**
3467   * A hash containing arbitrary keys and values representing the state of
3468   * the object.
3469   *
3470   * @type {Object}
3471   */
3472  state: {},
3473  /**
3474   * Set the state of an object by mutating its
3475   * {@link module:stateful~StatefulMixin.state|state} object in place.
3476   *
3477   * @fires   module:stateful~StatefulMixin#statechanged
3478   * @param   {Object|Function} stateUpdates
3479   *          A new set of properties to shallow-merge into the plugin state.
3480   *          Can be a plain object or a function returning a plain object.
3481   *
3482   * @return {Object|undefined}
3483   *          An object containing changes that occurred. If no changes
3484   *          occurred, returns `undefined`.
3485   */
3486  setState(stateUpdates) {
3487    // Support providing the `stateUpdates` state as a function.
3488    if (typeof stateUpdates === 'function') {
3489      stateUpdates = stateUpdates();
3490    }
3491    let changes;
3492    each(stateUpdates, (value, key) => {
3493      // Record the change if the value is different from what's in the
3494      // current state.
3495      if (this.state[key] !== value) {
3496        changes = changes || {};
3497        changes[key] = {
3498          from: this.state[key],
3499          to: value,
3500        };
3501      }
3502      this.state[key] = value;
3503    });
3504
3505    // Only trigger "statechange" if there were changes AND we have a trigger
3506    // function. This allows us to not require that the target object be an
3507    // evented object.
3508    if (changes && isEvented(this)) {
3509      /**
3510       * An event triggered on an object that is both
3511       * {@link module:stateful|stateful} and {@link module:evented|evented}
3512       * indicating that its state has changed.
3513       *
3514       * @event    module:stateful~StatefulMixin#statechanged
3515       * @type     {Object}
3516       * @property {Object} changes
3517       *           A hash containing the properties that were changed and
3518       *           the values they were changed `from` and `to`.
3519       */
3520      this.trigger({
3521        changes,
3522        type: 'statechanged',
3523      });
3524    }
3525    return changes;
3526  },
3527};
3528
3529/**
3530 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3531 * object.
3532 *
3533 * If the target object is {@link module:evented|evented} and has a
3534 * `handleStateChanged` method, that method will be automatically bound to the
3535 * `statechanged` event on itself.
3536 *
3537 * @param   {Object} target
3538 *          The object to be made stateful.
3539 *
3540 * @param   {Object} [defaultState]
3541 *          A default set of properties to populate the newly-stateful object's
3542 *          `state` property.
3543 *
3544 * @return {Object}
3545 *          Returns the `target`.
3546 */
3547function stateful(target, defaultState) {
3548  Object.assign(target, StatefulMixin);
3549
3550  // This happens after the mixing-in because we need to replace the `state`
3551  // added in that step.
3552  target.state = Object.assign({}, target.state, defaultState);
3553
3554  // Auto-bind the `handleStateChanged` method of the target object if it exists.
3555  if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3556    target.on('statechanged', target.handleStateChanged);
3557  }
3558  return target;
3559}
3560
3561/**
3562 * @file str.js
3563 * @module to-lower-case
3564 */
3565
3566/**
3567 * Lowercase the first letter of a string.
3568 *
3569 * @param {string} string
3570 *        String to be lowercased
3571 *
3572 * @return {string}
3573 *         The string with a lowercased first letter
3574 */
3575const toLowerCase = function (string) {
3576  if (typeof string !== 'string') {
3577    return string;
3578  }
3579  return string.replace(/./, (w) => w.toLowerCase());
3580};
3581
3582/**
3583 * Uppercase the first letter of a string.
3584 *
3585 * @param {string} string
3586 *        String to be uppercased
3587 *
3588 * @return {string}
3589 *         The string with an uppercased first letter
3590 */
3591const toTitleCase$1 = function (string) {
3592  if (typeof string !== 'string') {
3593    return string;
3594  }
3595  return string.replace(/./, (w) => w.toUpperCase());
3596};
3597
3598/**
3599 * Compares the TitleCase versions of the two strings for equality.
3600 *
3601 * @param {string} str1
3602 *        The first string to compare
3603 *
3604 * @param {string} str2
3605 *        The second string to compare
3606 *
3607 * @return {boolean}
3608 *         Whether the TitleCase versions of the strings are equal
3609 */
3610const titleCaseEquals = function (str1, str2) {
3611  return toTitleCase$1(str1) === toTitleCase$1(str2);
3612};
3613
3614var Str = /*#__PURE__*/ Object.freeze({
3615  __proto__: null,
3616  toLowerCase: toLowerCase,
3617  toTitleCase: toTitleCase$1,
3618  titleCaseEquals: titleCaseEquals,
3619});
3620
3621/**
3622 * Player Component - Base class for all UI objects
3623 *
3624 * @file component.js
3625 */
3626
3627/** @import Player from './player' */
3628
3629/**
3630 * A callback to be called if and when the component is ready.
3631 * `this` will be the Component instance.
3632 *
3633 * @callback ReadyCallback
3634 * @returns  {void}
3635 */
3636
3637/**
3638 * Base class for all UI Components.
3639 * Components are UI objects which represent both a javascript object and an element
3640 * in the DOM. They can be children of other components, and can have
3641 * children themselves.
3642 *
3643 * Components can also use methods from {@link EventTarget}
3644 */
3645class Component$1 {
3646  /**
3647   * Creates an instance of this class.
3648   *
3649   * @param {Player} player
3650   *        The `Player` that this class should be attached to.
3651   *
3652   * @param {Object} [options]
3653   *        The key/value store of component options.
3654   *
3655   * @param {Object[]} [options.children]
3656   *        An array of children objects to initialize this component with. Children objects have
3657   *        a name property that will be used if more than one component of the same type needs to be
3658   *        added.
3659   *
3660   * @param  {string} [options.className]
3661   *         A class or space separated list of classes to add the component
3662   *
3663   * @param {ReadyCallback} [ready]
3664   *        Function that gets called when the `Component` is ready.
3665   */
3666  constructor(player, options, ready) {
3667    // The component might be the player itself and we can't pass `this` to super
3668    if (!player && this.play) {
3669      this.player_ = player = this; // eslint-disable-line
3670    } else {
3671      this.player_ = player;
3672    }
3673    this.isDisposed_ = false;
3674
3675    // Hold the reference to the parent component via `addChild` method
3676    this.parentComponent_ = null;
3677
3678    // Make a copy of prototype.options_ to protect against overriding defaults
3679    this.options_ = merge$1({}, this.options_);
3680
3681    // Updated options with supplied options
3682    options = this.options_ = merge$1(this.options_, options);
3683
3684    // Get ID from options or options element if one is supplied
3685    this.id_ = options.id || (options.el && options.el.id);
3686
3687    // If there was no ID from the options, generate one
3688    if (!this.id_) {
3689      // Don't require the player ID function in the case of mock players
3690      const id = (player && player.id && player.id()) || 'no_player';
3691      this.id_ = `${id}_component_${newGUID()}`;
3692    }
3693    this.name_ = options.name || null;
3694
3695    // Create element if one wasn't provided in options
3696    if (options.el) {
3697      this.el_ = options.el;
3698    } else if (options.createEl !== false) {
3699      this.el_ = this.createEl();
3700    }
3701    if (options.className && this.el_) {
3702      options.className.split(' ').forEach((c) => this.addClass(c));
3703    }
3704
3705    // Remove the placeholder event methods. If the component is evented, the
3706    // real methods are added next
3707    ['on', 'off', 'one', 'any', 'trigger'].forEach((fn) => {
3708      this[fn] = undefined;
3709    });
3710
3711    // if evented is anything except false, we want to mixin in evented
3712    if (options.evented !== false) {
3713      // Make this an evented object and use `el_`, if available, as its event bus
3714      evented(this, {
3715        eventBusKey: this.el_ ? 'el_' : null,
3716      });
3717      this.handleLanguagechange = this.handleLanguagechange.bind(this);
3718      this.on(this.player_, 'languagechange', this.handleLanguagechange);
3719    }
3720    stateful(this, this.constructor.defaultState);
3721    this.children_ = [];
3722    this.childIndex_ = {};
3723    this.childNameIndex_ = {};
3724    this.setTimeoutIds_ = new Set();
3725    this.setIntervalIds_ = new Set();
3726    this.rafIds_ = new Set();
3727    this.namedRafs_ = new Map();
3728    this.clearingTimersOnDispose_ = false;
3729
3730    // Add any child components in options
3731    if (options.initChildren !== false) {
3732      this.initChildren();
3733    }
3734
3735    // Don't want to trigger ready here or it will go before init is actually
3736    // finished for all children that run this constructor
3737    this.ready(ready);
3738    if (options.reportTouchActivity !== false) {
3739      this.enableTouchActivity();
3740    }
3741  }
3742
3743  // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
3744  // They are replaced or removed in the constructor
3745
3746  /**
3747   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
3748   * function that will get called when an event with a certain name gets triggered.
3749   *
3750   * @param {string|string[]} type
3751   *        An event name or an array of event names.
3752   *
3753   * @param {Function} fn
3754   *        The function to call with `EventTarget`s
3755   */
3756
3757  /**
3758   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
3759   * This makes it so that the `event listener` will no longer get called when the
3760   * named event happens.
3761   *
3762   * @param {string|string[]} type
3763   *        An event name or an array of event names.
3764   *
3765   * @param {Function}
3765 [fn]
3766   *        The function to remove. If not specified, all listeners managed by Video.js will be removed.
3767   */
3768
3769  /**
3770   * This function will add an `event listener` that gets triggered only once. After the
3771   * first trigger it will get removed. This is like adding an `event listener`
3772   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
3773   *
3774   * @param {string|string[]} type
3775   *        An event name or an array of event names.
3776   *
3777   * @param {Function} fn
3778   *        The function to be called once for each event name.
3779   */
3780
3781  /**
3782   * This function will add an `event listener` that gets triggered only once and is
3783   * removed from all events. This is like adding an array of `event listener`s
3784   * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
3785   * first time it is triggered.
3786   *
3787   * @param {string|string[]} type
3788   *        An event name or an array of event names.
3789   *
3790   * @param {Function} fn
3791   *        The function to be called once for each event name.
3792   */
3793
3794  /**
3795   * This function causes an event to happen. This will then cause any `event listeners`
3796   * that are waiting for that event, to get called. If there are no `event listeners`
3797   * for an event then nothing will happen.
3798   *
3799   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
3800   * Trigger will also call the `on` + `uppercaseEventName` function.
3801   *
3802   * Example:
3803   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
3804   * `onClick` if it exists.
3805   *
3806   * @param {string|Event|Object} event
3807   *        The name of the event, an `Event`, or an object with a key of type set to
3808   *        an event name.
3809   *
3810   * @param {Object} [hash]
3811   *        Optionally extra argument to pass through to an event listener
3812   */
3813
3814  /**
3815   * Dispose of the `Component` and all child components.
3816   *
3817   * @fires Component#dispose
3818   *
3819   * @param {Object} options
3820   * @param {Element} options.originalEl element with which to replace player element
3821   */
3822  dispose(options = {}) {
3823    // Bail out if the component has already been disposed.
3824    if (this.isDisposed_) {
3825      return;
3826    }
3827    if (this.readyQueue_) {
3828      this.readyQueue_.length = 0;
3829    }
3830
3831    /**
3832     * Triggered when a `Component` is disposed.
3833     *
3834     * @event Component#dispose
3835     * @type {Event}
3836     *
3837     * @property {boolean} [bubbles=false]
3838     *           set to false so that the dispose event does not
3839     *           bubble up
3840     */
3841    this.trigger({
3842      type: 'dispose',
3843      bubbles: false,
vendor: 8,608 bytes, lines 3843-4138
3843
3844    });
3845    this.isDisposed_ = true;
3846
3847    // Dispose all children.
3848    if (this.children_) {
3849      for (let i = this.children_.length - 1; i >= 0; i--) {
3850        if (this.children_[i].dispose) {
3851          this.children_[i].dispose();
3852        }
3853      }
3854    }
3855
3856    // Delete child references
3857    this.children_ = null;
3858    this.childIndex_ = null;
3859    this.childNameIndex_ = null;
3860    this.parentComponent_ = null;
3861    if (this.el_) {
3862      // Remove element from DOM
3863      if (this.el_.parentNode) {
3864        if (options.restoreEl) {
3865          this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
3866        } else {
3867          this.el_.parentNode.removeChild(this.el_);
3868        }
3869      }
3870      this.el_ = null;
3871    }
3872
3873    // remove reference to the player after disposing of the element
3874    this.player_ = null;
3875  }
3876
3877  /**
3878   * Determine whether or not this component has been disposed.
3879   *
3880   * @return {boolean}
3881   *         If the component has been disposed, will be `true`. Otherwise, `false`.
3882   */
3883  isDisposed() {
3884    return Boolean(this.isDisposed_);
3885  }
3886
3887  /**
3888   * Return the {@link Player} that the `Component` has attached to.
3889   *
3890   * @return {Player}
3891   *         The player that this `Component` has attached to.
3892   */
3893  player() {
3894    return this.player_;
3895  }
3896
3897  /**
3898   * Deep merge of options objects with new options.
3899   * > Note: When both `obj` and `options` contain properties whose values are objects.
3900   *         The two properties get merged using {@link module:obj.merge}
3901   *
3902   * @param {Object} obj
3903   *        The object that contains new options.
3904   *
3905   * @return {Object}
3906   *         A new object of `this.options_` and `obj` merged together.
3907   */
3908  options(obj) {
3909    if (!obj) {
3910      return this.options_;
3911    }
3912    this.options_ = merge$1(this.options_, obj);
3913    return this.options_;
3914  }
3915
3916  /**
3917   * Get the `Component`s DOM element
3918   *
3919   * @return {Element}
3920   *         The DOM element for this `Component`.
3921   */
3922  el() {
3923    return this.el_;
3924  }
3925
3926  /**
3927   * Create the `Component`s DOM element.
3928   *
3929   * @param {string} [tagName]
3930   *        Element's DOM node type. e.g. 'div'
3931   *
3932   * @param {Object} [properties]
3933   *        An object of properties that should be set.
3934   *
3935   * @param {Object} [attributes]
3936   *        An object of attributes that should be set.
3937   *
3938   * @return {Element}
3939   *         The element that gets created.
3940   */
3941  createEl(tagName, properties, attributes) {
3942    return createEl(tagName, properties, attributes);
3943  }
3944
3945  /**
3946   * Localize a string given the string in english.
3947   *
3948   * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3949   * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3950   *
3951   * If a `defaultValue` is provided, it'll use that over `string`,
3952   * if a value isn't found in provided language files.
3953   * This is useful if you want to have a descriptive key for token replacement
3954   * but have a succinct localized string and not require `en.json` to be included.
3955   *
3956   * Currently, it is used for the progress bar timing.
3957   * ```js
3958   * {
3959   *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3960   * }
3961   * ```
3962   * It is then used like so:
3963   * ```js
3964   * this.localize('progress bar timing: currentTime={1} duration{2}',
3965   *               [this.player_.currentTime(), this.player_.duration()],
3966   *               '{1} of {2}');
3967   * ```
3968   *
3969   * Which outputs something like: `01:23 of 24:56`.
3970   *
3971   *
3972   * @param {string} string
3973   *        The string to localize and the key to lookup in the language files.
3974   * @param {string[]} [tokens]
3975   *        If the current item has token replacements, provide the tokens here.
3976   * @param {string} [defaultValue]
3977   *        Defaults to `string`. Can be a default value to use for token replacement
3978   *        if the lookup key is needed to be separate.
3979   *
3980   * @return {string}
3981   *         The localized string or if no localization exists the english string.
3982   */
3983  localize(string, tokens, defaultValue = string) {
3984    const code = this.player_.language && this.player_.language();
3985    const languages = this.player_.languages && this.player_.languages();
3986    const language = languages && languages[code];
3987    const primaryCode = code && code.split('-')[0];
3988    const primaryLang = languages && languages[primaryCode];
3989    let localizedString = defaultValue;
3990    if (language && language[string]) {
3991      localizedString = language[string];
3992    } else if (primaryLang && primaryLang[string]) {
3993      localizedString = primaryLang[string];
3994    }
3995    if (tokens) {
3996      localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3997        const value = tokens[index - 1];
3998        let ret = value;
3999        if (typeof value === 'undefined') {
4000          ret = match;
4001        }
4002        return ret;
4003      });
4004    }
4005    return localizedString;
4006  }
4007
4008  /**
4009   * Handles language change for the player in components. Should be overridden by sub-components.
4010   *
4011   * @abstract
4012   */
4013  handleLanguagechange() {}
4014
4015  /**
4016   * Return the `Component`s DOM element. This is where children get inserted.
4017   * This will usually be the the same as the element returned in {@link Component#el}.
4018   *
4019   * @return {Element}
4020   *         The content element for this `Component`.
4021   */
4022  contentEl() {
4023    return this.contentEl_ || this.el_;
4024  }
4025
4026  /**
4027   * Get this `Component`s ID
4028   *
4029   * @return {string}
4030   *         The id of this `Component`
4031   */
4032  id() {
4033    return this.id_;
4034  }
4035
4036  /**
4037   * Get the `Component`s name. The name gets used to reference the `Component`
4038   * and is set during registration.
4039   *
4040   * @return {string}
4041   *         The name of this `Component`.
4042   */
4043  name() {
4044    return this.name_;
4045  }
4046
4047  /**
4048   * Get an array of all child components
4049   *
4050   * @return {Array}
4051   *         The children
4052   */
4053  children() {
4054    return this.children_;
4055  }
4056
4057  /**
4058   * Returns the child `Component` with the given `id`.
4059   *
4060   * @param {string} id
4061   *        The id of the child `Component` to get.
4062   *
4063   * @return {Component|undefined}
4064   *         The child `Component` with the given `id` or undefined.
4065   */
4066  getChildById(id) {
4067    return this.childIndex_[id];
4068  }
4069
4070  /**
4071   * Returns the child `Component` with the given `name`.
4072   *
4073   * @param {string} name
4074   *        The name of the child `Component` to get.
4075   *
4076   * @return {Component|undefined}
4077   *         The child `Component` with the given `name` or undefined.
4078   */
4079  getChild(name) {
4080    if (!name) {
4081      return;
4082    }
4083    return this.childNameIndex_[name];
4084  }
4085
4086  /**
4087   * Returns the descendant `Component` following the givent
4088   * descendant `names`. For instance ['foo', 'bar', 'baz'] would
4089   * try to get 'foo' on the current component, 'bar' on the 'foo'
4090   * component and 'baz' on the 'bar' component and return undefined
4091   * if any of those don't exist.
4092   *
4093   * @param {...string[]|...string} names
4094   *        The name of the child `Component` to get.
4095   *
4096   * @return {Component|undefined}
4097   *         The descendant `Component` following the given descendant
4098   *         `names` or undefined.
4099   */
4100  getDescendant(...names) {
4101    // flatten array argument into the main array
4102    names = names.reduce((acc, n) => acc.concat(n), []);
4103    let currentChild = this;
4104    for (let i = 0; i < names.length; i++) {
4105      currentChild = currentChild.getChild(names[i]);
4106      if (!currentChild || !currentChild.getChild) {
4107        return;
4108      }
4109    }
4110    return currentChild;
4111  }
4112
4113  /**
4114   * Adds an SVG icon element to another element or component.
4115   *
4116   * @param {string} iconName
4117   *        The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html'
4118   *
4119   * @param {Element} [el=this.el()]
4120   *        Element to set the title on. Defaults to the current Component's element.
4121   *
4122   * @return {Element}
4123   *        The newly created icon element.
4124   */
4125  setIcon(iconName, el = this.el()) {
4126    // TODO: In v9 of video.js, we will want to remove font icons entirely.
4127    // This means this check, as well as the others throughout the code, and
4128    // the unecessary CSS for font icons, will need to be removed.
4129    // See https://github.com/videojs/video.js/pull/8260 as to which components
4130    // need updating.
4131    if (!this.player_.options_.experimentalSvgIcons) {
4132      return;
4133    }
4134    const xmlnsURL = 'http://www.w3.org/2000/svg';
4135
4136    // The below creates an element in the format of:
4137    // <span><svg><use>....</use></svg></span>
4138    const iconContainer = createEl(
vendor: 1,427 bytes, lines 4138-4182
4138
4139      'span',
4140      {
4141        className: 'vjs-icon-placeholder vjs-svg-icon',
4142      },
4143      {
4144        'aria-hidden': 'true',
4145      }
4146    );
4147    const svgEl = document__default['default'].createElementNS(xmlnsURL, 'svg');
4148    svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512');
4149    const useEl = document__default['default'].createElementNS(xmlnsURL, 'use');
4150    svgEl.appendChild(useEl);
4151    useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`);
4152    iconContainer.appendChild(svgEl);
4153
4154    // Replace a pre-existing icon if one exists.
4155    if (this.iconIsSet_) {
4156      el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder'));
4157    } else {
4158      el.appendChild(iconContainer);
4159    }
4160    this.iconIsSet_ = true;
4161    return iconContainer;
4162  }
4163
4164  /**
4165   * Add a child `Component` inside the current `Component`.
4166   *
4167   * @param {string|Component} child
4168   *        The name or instance of a child to add.
4169   *
4170   * @param {Object} [options={}]
4171   *        The key/value store of options that will get passed to children of
4172   *        the child.
4173   *
4174   * @param {number} [index=this.children_.length]
4175   *        The index to attempt to add a child into.
4176   *
4177   *
4178   * @return {Component}
4179   *         The `Component` that gets added as a child. When using a string the
4180   *         `Component` will get created by this process.
4181   */
4182  addChild(child, options = {}, index = this.children_.length) {
vendor: 10,421 bytes, lines 4183-4501
4183    let component;
4184    let componentName;
4185
4186    // If child is a string, create component with options
4187    if (typeof child === 'string') {
4188      componentName = toTitleCase$1(child);
4189      const componentClassName = options.componentClass || componentName;
4190
4191      // Set name through options
4192      options.name = componentName;
4193
4194      // Create a new object & element for this controls set
4195      // If there's no .player_, this is a player
4196      const ComponentClass = Component$1.getComponent(componentClassName);
4197      if (!ComponentClass) {
4198        throw new Error(`Component ${componentClassName} does not exist`);
4199      }
4200
4201      // data stored directly on the videojs object may be
4202      // misidentified as a component to retain
4203      // backwards-compatibility with 4.x. check to make sure the
4204      // component class can be instantiated.
4205      if (typeof ComponentClass !== 'function') {
4206        return null;
4207      }
4208      component = new ComponentClass(this.player_ || this, options);
4209
4210      // child is a component instance
4211    } else {
4212      component = child;
4213    }
4214    if (component.parentComponent_) {
4215      component.parentComponent_.removeChild(component);
4216    }
4217    this.children_.splice(index, 0, component);
4218    component.parentComponent_ = this;
4219    if (typeof component.id === 'function') {
4220      this.childIndex_[component.id()] = component;
4221    }
4222
4223    // If a name wasn't used to create the component, check if we can use the
4224    // name function of the component
4225    componentName = componentName || (component.name && toTitleCase$1(component.name()));
4226    if (componentName) {
4227      this.childNameIndex_[componentName] = component;
4228      this.childNameIndex_[toLowerCase(componentName)] = component;
4229    }
4230
4231    // Add the UI object's element to the container div (box)
4232    // Having an element is not required
4233    if (typeof component.el === 'function' && component.el()) {
4234      // If inserting before a component, insert before that component's element
4235      let refNode = null;
4236      if (this.children_[index + 1]) {
4237        // Most children are components, but the video tech is an HTML element
4238        if (this.children_[index + 1].el_) {
4239          refNode = this.children_[index + 1].el_;
4240        } else if (isEl(this.children_[index + 1])) {
4241          refNode = this.children_[index + 1];
4242        }
4243      }
4244      this.contentEl().insertBefore(component.el(), refNode);
4245    }
4246
4247    // Return so it can stored on parent object if desired.
4248    return component;
4249  }
4250
4251  /**
4252   * Remove a child `Component` from this `Component`s list of children. Also removes
4253   * the child `Component`s element from this `Component`s element.
4254   *
4255   * @param {Component} component
4256   *        The child `Component` to remove.
4257   */
4258  removeChild(component) {
4259    if (typeof component === 'string') {
4260      component = this.getChild(component);
4261    }
4262    if (!component || !this.children_) {
4263      return;
4264    }
4265    let childFound = false;
4266    for (let i = this.children_.length - 1; i >= 0; i--) {
4267      if (this.children_[i] === component) {
4268        childFound = true;
4269        this.children_.splice(i, 1);
4270        break;
4271      }
4272    }
4273    if (!childFound) {
4274      return;
4275    }
4276    component.parentComponent_ = null;
4277    this.childIndex_[component.id()] = null;
4278    this.childNameIndex_[toTitleCase$1(component.name())] = null;
4279    this.childNameIndex_[toLowerCase(component.name())] = null;
4280    const compEl = component.el();
4281    if (compEl && compEl.parentNode === this.contentEl()) {
4282      this.contentEl().removeChild(component.el());
4283    }
4284  }
4285
4286  /**
4287   * Add and initialize default child `Component`s based upon options.
4288   */
4289  initChildren() {
4290    const children = this.options_.children;
4291    if (children) {
4292      // `this` is `parent`
4293      const parentOptions = this.options_;
4294      const handleAdd = (child) => {
4295        const name = child.name;
4296        let opts = child.opts;
4297
4298        // Allow options for children to be set at the parent options
4299        // e.g. videojs(id, { controlBar: false });
4300        // instead of videojs(id, { children: { controlBar: false });
4301        if (parentOptions[name] !== undefined) {
4302          opts = parentOptions[name];
4303        }
4304
4305        // Allow for disabling default components
4306        // e.g. options['children']['posterImage'] = false
4307        if (opts === false) {
4308          return;
4309        }
4310
4311        // Allow options to be passed as a simple boolean if no configuration
4312        // is necessary.
4313        if (opts === true) {
4314          opts = {};
4315        }
4316
4317        // We also want to pass the original player options
4318        // to each component as well so they don't need to
4319        // reach back into the player for options later.
4320        opts.playerOptions = this.options_.playerOptions;
4321
4322        // Create and add the child component.
4323        // Add a direct reference to the child by name on the parent instance.
4324        // If two of the same component are used, different names should be supplied
4325        // for each
4326        const newChild = this.addChild(name, opts);
4327        if (newChild) {
4328          this[name] = newChild;
4329        }
4330      };
4331
4332      // Allow for an array of children details to passed in the options
4333      let workingChildren;
4334      const Tech = Component$1.getComponent('Tech');
4335      if (Array.isArray(children)) {
4336        workingChildren = children;
4337      } else {
4338        workingChildren = Object.keys(children);
4339      }
4340      workingChildren
4341        // children that are in this.options_ but also in workingChildren  would
4342        // give us extra children we do not want. So, we want to filter them out.
4343        .concat(
4344          Object.keys(this.options_).filter(function (child) {
4345            return !workingChildren.some(function (wchild) {
4346              if (typeof wchild === 'string') {
4347                return child === wchild;
4348              }
4349              return child === wchild.name;
4350            });
4351          })
4352        )
4353        .map((child) => {
4354          let name;
4355          let opts;
4356          if (typeof child === 'string') {
4357            name = child;
4358            opts = children[name] || this.options_[name] || {};
4359          } else {
4360            name = child.name;
4361            opts = child;
4362          }
4363          return {
4364            name,
4365            opts,
4366          };
4367        })
4368        .filter((child) => {
4369          // we have to make sure that child.name isn't in the techOrder since
4370          // techs are registered as Components but can't aren't compatible
4371          // See https://github.com/videojs/video.js/issues/2772
4372          const c = Component$1.getComponent(
4373            child.opts.componentClass || toTitleCase$1(child.name)
4374          );
4375          return c && !Tech.isTech(c);
4376        })
4377        .forEach(handleAdd);
4378    }
4379  }
4380
4381  /**
4382   * Builds the default DOM class name. Should be overridden by sub-components.
4383   *
4384   * @return {string}
4385   *         The DOM class name for this object.
4386   *
4387   * @abstract
4388   */
4389  buildCSSClass() {
4390    // Child classes can include a function that does:
4391    // return 'CLASS NAME' + this._super();
4392    return '';
4393  }
4394
4395  /**
4396   * Bind a listener to the component's ready state.
4397   * Different from event listeners in that if the ready event has already happened
4398   * it will trigger the function immediately.
4399   *
4400   * @param {ReadyCallback} fn
4401   *        Function that gets called when the `Component` is ready.
4402   */
4403  ready(fn, sync = false) {
4404    if (!fn) {
4405      return;
4406    }
4407    if (!this.isReady_) {
4408      this.readyQueue_ = this.readyQueue_ || [];
4409      this.readyQueue_.push(fn);
4410      return;
4411    }
4412    if (sync) {
4413      fn.call(this);
4414    } else {
4415      // Call the function asynchronously by default for consistency
4416      this.setTimeout(fn, 1);
4417    }
4418  }
4419
4420  /**
4421   * Trigger all the ready listeners for this `Component`.
4422   *
4423   * @fires Component#ready
4424   */
4425  triggerReady() {
4426    this.isReady_ = true;
4427
4428    // Ensure ready is triggered asynchronously
4429    this.setTimeout(function () {
4430      const readyQueue = this.readyQueue_;
4431
4432      // Reset Ready Queue
4433      this.readyQueue_ = [];
4434      if (readyQueue && readyQueue.length > 0) {
4435        readyQueue.forEach(function (fn) {
4436          fn.call(this);
4437        }, this);
4438      }
4439
4440      // Allow for using event listeners also
4441      /**
4442       * Triggered when a `Component` is ready.
4443       *
4444       * @event Component#ready
4445       * @type {Event}
4446       */
4447      this.trigger('ready');
4448    }, 1);
4449  }
4450
4451  /**
4452   * Find a single DOM element matching a `selector`. This can be within the `Component`s
4453   * `contentEl()` or another custom context.
4454   *
4455   * @param {string} selector
4456   *        A valid CSS selector, which will be passed to `querySelector`.
4457   *
4458   * @param {Element|string} [context=this.contentEl()]
4459   *        A DOM element within which to query. Can also be a selector string in
4460   *        which case the first matching element will get used as context. If
4461   *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4462   *        nothing it falls back to `document`.
4463   *
4464   * @return {Element|null}
4465   *         the dom element that was found, or null
4466   *
4467   * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4468   */
4469  $(selector, context) {
4470    return $(selector, context || this.contentEl());
4471  }
4472
4473  /**
4474   * Finds all DOM element matching a `selector`. This can be within the `Component`s
4475   * `contentEl()` or another custom context.
4476   *
4477   * @param {string} selector
4478   *        A valid CSS selector, which will be passed to `querySelectorAll`.
4479   *
4480   * @param {Element|string} [context=this.contentEl()]
4481   *        A DOM element within which to query. Can also be a selector string in
4482   *        which case the first matching element will get used as context. If
4483   *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4484   *        nothing it falls back to `document`.
4485   *
4486   * @return {NodeList}
4487   *         a list of dom elements that were found
4488   *
4489   * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4490   */
4491  $$(selector, context) {
4492    return $$(selector, context || this.contentEl());
4493  }
4494
4495  /**
4496   * Check if a component's element has a CSS class name.
4497   *
4498   * @param {string} classToCheck
4499   *        CSS class name to check.
4500   *
4501   * @return {boolean}
4502   *         - True if the `Component` has the class.
4503   *         - False if the `Component` does not have the class`
4504   */
4505  hasClass(classToCheck) {
4506    return hasClass(this.el_, classToCheck);
4507  }
4508
4509  /**
4510   * Add a CSS class name to the `Component`s element.
4511   *
4512   * @param {...string} classesToAdd
4513   *        One or more CSS class name to add.
4514   */
4515  addClass(...classesToAdd) {
4516    addClass(this.el_, ...classesToAdd);
4517  }
4518
4519  /**
4520   * Remove a CSS class name from the `Component`s element.
4521   *
4522   * @param {...string} classesToRemove
4523   *        One or more CSS class name to remove.
4524   */
4525  removeClass(...classesToRemove) {
4526    removeClass(this.el_, ...classesToRemove);
4527  }
4528
4529  /**
4530   * Add or remove a CSS class name from the component's element.
4531   * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4532   * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4533   *
4534   * @param  {string} classToToggle
4535   *         The class to add or remove. Passed to DOMTokenList's toggle()
4536   *
4537   * @param  {boolean|Dom.PredicateCallback} [predicate]
4538   *         A boolean or function that returns a boolean. Passed to DOMTokenList's toggle().
4539   */
4540  toggleClass(classToToggle, predicate) {
4541    toggleClass(this.el_, classToToggle, predicate);
4542  }
4543
4544  /**
4545   * Show the `Component`s element if it is hidden by removing the
4546   * 'vjs-hidden' class name from it.
4547   */
4548  show() {
4549    this.removeClass('vjs-hidden');
4550  }
4551
4552  /**
4553   * Hide the `Component`s element if it is currently showing by adding the
4554   * 'vjs-hidden` class name to it.
4555   */
4556  hide() {
4557    this.addClass('vjs-hidden');
4558  }
4559
4560  /**
4561   * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4562   * class name to it. Used during fadeIn/fadeOut.
4563   *
4564   * @private
4565   */
4566  lockShowing() {
4567    this.addClass('vjs-lock-showing');
4568  }
4569
4570  /**
4571   * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4572   * class name from it. Used during fadeIn/fadeOut.
4573   *
4574   * @private
4575   */
4576  unlockShowing() {
4577    this.removeClass('vjs-lock-showing');
4578  }
4579
4580  /**
4581   * Get the value of an attribute on the `Component`s element.
4582   *
4583   * @param {string} attribute
4584   *        Name of the attribute to get the value from.
4585   *
4586   * @return {string|null}
4587   *         - The value of the attribute that was asked for.
4588   *         - Can be an empty string on some browsers if the attribute does not exist
4589   *           or has no value
4590   *         - Most browsers will return null if the attribute does not exist or has
4591   *           no value.
4592   *
4593   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4594   */
4595  getAttribute(attribute) {
4596    return getAttribute(this.el_, attribute);
4597  }
4598
4599  /**
4600   * Set the value of an attribute on the `Component`'s element
4601   *
4602   * @param {string} attribute
4603   *        Name of the attribute to set.
4604   *
4605   * @param {string} value
4606   *        Value to set the attribute to.
4607   *
4608   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4609   */
4610  setAttribute(attribute, value) {
4611    setAttribute(this.el_, attribute, value);
4612  }
4613
4614  /**
4615   * Remove an attribute from the `Component`s element.
4616   *
4617   * @param {string} attribute
4618   *        Name of the attribute to remove.
4619   *
4620   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4621   */
4622  removeAttribute(attribute) {
4623    removeAttribute(this.el_, attribute);
4624  }
4625
4626  /**
4627   * Get or set the width of the component based upon the CSS styles.
4628   * See {@link Component#dimension} for more detailed information.
4629   *
4630   * @param {number|string} [num]
4631   *        The width that you want to set postfixed with '%', 'px' or nothing.
4632   *
4633   * @param {boolean} [skipListeners]
4634   *        Skip the componentresize event trigger
4635   *
4636   * @return {number|undefined}
4637   *         The width when getting, zero if there is no width
4638   */
4639  width(num, skipListeners) {
4640    return this.dimension('width', num, skipListeners);
4641  }
4642
4643  /**
4644   * Get or set the height of the component based upon the CSS styles.
4645   * See {@link Component#dimension} for more detailed information.
4646   *
4647   * @param {number|string} [num]
4648   *        The height that you want to set postfixed with '%', 'px' or nothing.
4649   *
4650   * @param {boolean} [skipListeners]
4651   *        Skip the componentresize event trigger
4652   *
4653   * @return {number|undefined}
4654   *         The height when getting, zero if there is no height
4655   */
4656  height(num, skipListeners) {
4657    return this.dimension('height', num, skipListeners);
4658  }
4659
4660  /**
4661   * Set both the width and height of the `Component` element at the same time.
4662   *
4663   * @param  {number|string} width
4664   *         Width to set the `Component`s element to.
4665   *
4666   * @param  {number|string} height
4667   *         Height to set the `Component`s element to.
4668   */
4669  dimensions(width, height) {
4670    // Skip componentresize listeners on width for optimization
4671    this.width(width, true);
4672    this.height(height);
4673  }
4674
4675  /**
4676   * Get or set width or height of the `Component` element. This is the shared code
4677   * for the {@link Component#width} and {@link Component#height}.
4678   *
4679   * Things to know:
4680   * - If the width or height in an number this will return the number postfixed with 'px'.
4681   * - If the width/height is a percent this will return the percent postfixed with '%'
4682   * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4683   *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4684   *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4684sing-width/}
4685   *   for more information
4686   * - If you want the computed style of the component, use {@link Component#currentWidth}
4687   *   and {@link {Component#currentHeight}
4688   *
4689   * @fires Component#componentresize
4690   *
4691   * @param {string} widthOrHeight
4692   8        'width' or 'height'
4693   *
4694   * @param  {number|string} [num]
4695   8         New dimension
4696   *
4697   * @param  {boolean} [skipListeners]
4698   *         Skip componentresize event trigger
4699   *
4700   * @return {number|undefined}
4701   *         The dimension when getting or 0 if unset
4702   */
4703  dimension(widthOrHeight, num, skipListeners) {
4704    if (num !== undefined) {
4705      // Set to zero if null or literally NaN (NaN !== NaN)
4706      if (num === null || num !== num) {
4707        num = 0;
4708      }
4709
4710      // Check if using css width/height (% or px) and adjust
4711      if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4712        this.el_.style[widthOrHeight] = num;
4713      } else if (num === 'auto') {
4714        this.el_.style[widthOrHeight] = '';
4715      } else {
4716        this.el_.style[widthOrHeight] = num + 'px';
4717      }
4718
4719      // skipListeners allows us to avoid triggering the resize event when setting both width and height
4720      if (!skipListeners) {
4721        /**
4722         * Triggered when a component is resized.
4723         *
4724         * @event Component#componentresize
4725         * @type {Event}
4726         */
4727        this.trigger('componentresize');
4728      }
4729      return;
4730    }
4731
4732    // Not setting a value, so getting it
4733    // Make sure element exists
4734    if (!this.el_) {
4735      return 0;
4736    }
4737
4738    // Get dimension value from style
4739    const val = this.el_.style[widthOrHeight];
4740    const pxIndex = val.indexOf('px');
4741    if (pxIndex !== -1) {
4742      // Return the pixel value with no 'px'
4743      return parseInt(val.slice(0, pxIndex), 10);
4744    }
4745
4746    // No px so using % or no style was set, so falling back to offsetWidth/height
4747    // If component has display:none, offset will return 0
4748    // TODO: handle display:none and no dimension style using px
4749    return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
4750  }
4751
4752  /**
4753   * Get the computed width or the height of the component's element.
4754   *
4755   * Uses `window.getComputedStyle`.
4756   *
4757   * @param {string} widthOrHeight
4758   *        A string containing 'width' or 'height'. Whichever one you want to get.
4759   *
4760   * @return {number}
4761   *         The dimension that gets asked for or 0 if nothing was set
4762   *         for that dimension.
4763   */
4764  currentDimension(widthOrHeight) {
4765    let computedWidthOrHeight = 0;
4766    if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4767      throw new Error('currentDimension only accepts width or height value');
4768    }
4769    computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
4770
4771    // remove 'px' from variable and parse as integer
4772    computedWidthOrHeight = parseFloat(computedWidthOrHeight);
4773
4774    // if the computed value is still 0, it's possible that the browser is lying
4775    // and we want to check the offset values.
4776    // This code also runs wherever getComputedStyle doesn't exist.
4777    if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4778      const rule = `offset${toTitleCase$1(widthOrHeight)}`;
4779      computedWidthOrHeight = this.el_[rule];
4780    }
4781    return computedWidthOrHeight;
4782  }
4783
4784  /**
4785   * An object that contains width and height values of the `Component`s
4786   * computed style. Uses `window.getComputedStyle`.
4787   *
4788   * @typedef {Object} Component~DimensionObject
4789   *
4790   * @property {number} width
4791   *           The width of the `Component`s computed style.
4792   *
4793   * @property {number} height
4794   *           The height of the `Component`s computed style.
4795   */
4796
4797  /**
4798   * Get an object that contains computed width and height values of the
4799   * component's element.
4800   *
4801   * Uses `window.getComputedStyle`.
4802   *
4803   * @return {Component~DimensionObject}
4804   *         The computed dimensions of the component's element.
4805   */
4806  currentDimensions() {
4807    return {
4808      width: this.currentDimension('width'),
4809      height: this.currentDimension('height'),
4810    };
4811  }
4812
4813  /**
4814   * Get the computed width of the component's element.
4815   *
4816   * Uses `window.getComputedStyle`.
4817   *
4818   * @return {number}
4819   *         The computed width of the component's element.
4820   */
4821  currentWidth() {
4822    return this.currentDimension('width');
4823  }
4824
4825  /**
4826   * Get the computed height of the component's element.
4827   *
4828   * Uses `window.getComputedStyle`.
4829   *
4830   * @return {number}
4831   *         The computed height of the component's element.
4832   */
4833  currentHeight() {
4834    return this.currentDimension('height');
4835  }
4836
4837  /**
4838   * Retrieves the position and size information of the component's element.
4839   *
4840   * @return {Object} An object with `boundingClientRect` and `center` properties.
4841   *         - `boundingClientRect`: An object with properties `x`, `y`, `width`,
4842   *           `height`, `top`, `right`, `bottom`, and `left`, representing
4843   *           the bounding rectangle of the element.
4844   *         - `center`: An object with properties `x` and `y`, representing
4845   *           the center point of the element. `width` and `height` are set to 0.
4846   */
4847  getPositions() {
4848    const rect = this.el_.getBoundingClientRect();
4849
4850    // Creating objects that mirror DOMRectReadOnly for boundingClientRect and center
4851    const boundingClientRect = {
4852      x: rect.x,
4853      y: rect.y,
4854      width: rect.width,
4855      height: rect.height,
4856      top: rect.top,
4857      right: rect.right,
4858      bottom: rect.bottom,
4859      left: rect.left,
4860    };
4861
4862    // Calculating the center position
4863    const center = {
4864      x: rect.left + rect.width / 2,
4865      y: rect.top + rect.height / 2,
4866      width: 0,
4867      height: 0,
4868      top: rect.top + rect.height / 2,
4869      right: rect.left + rect.width / 2,
4870      bottom: rect.top + rect.height / 2,
4871      left: rect.left + rect.width / 2,
4872    };
4873    return {
4874      boundingClientRect,
4875      center,
4876    };
4877  }
4878
4879  /**
4880   * Set the focus to this component
4881   */
4882  focus() {
4883    this.el_.focus();
4884  }
4885
4886  /**
4887   * Remove the focus from this component
4888   */
4889  blur() {
4890    this.el_.blur();
4891  }
4892
4893  /**
4894   * When this Component receives a `keydown` event which it does not process,
4895   *  it passes the event to the Player for handling.
4896   *
4897   * @param {KeyboardEvent} event
vendor: 30,222 bytes, lines 4898-5762
4898   *        The `keydown` event that caused this function to be called.
4899   */
4900  handleKeyDown(event) {
4901    if (this.player_) {
4902      // We only stop propagation here because we want unhandled events to fall
4903      // back to the browser. Exclude Tab for focus trapping, exclude also when spatialNavigation is enabled.
4904      if (
4905        event.key !== 'Tab' &&
4906        !(
4907          this.player_.options_.playerOptions.spatialNavigation &&
4908          this.player_.options_.playerOptions.spatialNavigation.enabled
4909        )
4910      ) {
4911        event.stopPropagation();
4912      }
4913      this.player_.handleKeyDown(event);
4914    }
4915  }
4916
4917  /**
4918   * Many components used to have a `handleKeyPress` method, which was poorly
4919   * named because it listened to a `keydown` event. This method name now
4920   * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4921   * will not see their method calls stop working.
4922   *
4923   * @param {KeyboardEvent} event
4924   *        The event that caused this function to be called.
4925   */
4926  handleKeyPress(event) {
4927    this.handleKeyDown(event);
4928  }
4929
4930  /**
4931   * Emit a 'tap' events when touch event support gets detected. This gets used to
4932   * support toggling the controls through a tap on the video. They get enabled
4933   * because every sub-component would have extra overhead otherwise.
4934   *
4935   * @protected
4936   * @fires Component#tap
4937   * @listens Component#touchstart
4938   * @listens Component#touchmove
4939   * @listens Component#touchleave
4940   * @listens Component#touchcancel
4941   * @listens Component#touchend
4942   */
4943  emitTapEvents() {
4944    // Track the start time so we can determine how long the touch lasted
4945    let touchStart = 0;
4946    let firstTouch = null;
4947
4948    // Maximum movement allowed during a touch event to still be considered a tap
4949    // Other popular libs use anywhere from 2 (hammer.js) to 15,
4950    // so 10 seems like a nice, round number.
4951    const tapMovementThreshold = 10;
4952
4953    // The maximum length a touch can be while still being considered a tap
4954    const touchTimeThreshold = 200;
4955    let couldBeTap;
4956    this.on('touchstart', function (event) {
4957      // If more than one finger, don't consider treating this as a click
4958      if (event.touches.length === 1) {
4959        // Copy pageX/pageY from the object
4960        firstTouch = {
4961          pageX: event.touches[0].pageX,
4962          pageY: event.touches[0].pageY,
4963        };
4964        // Record start time so we can detect a tap vs. "touch and hold"
4965        touchStart = window__default['default'].performance.now();
4966        // Reset couldBeTap tracking
4967        couldBeTap = true;
4968      }
4969    });
4970    this.on('touchmove', function (event) {
4971      // If more than one finger, don't consider treating this as a click
4972      if (event.touches.length > 1) {
4973        couldBeTap = false;
4974      } else if (firstTouch) {
4975        // Some devices will throw touchmoves for all but the slightest of taps.
4976        // So, if we moved only a small distance, this could still be a tap
4977        const xdiff = event.touches[0].pageX - firstTouch.pageX;
4978        const ydiff = event.touches[0].pageY - firstTouch.pageY;
4979        const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4980        if (touchDistance > tapMovementThreshold) {
4981          couldBeTap = false;
4982        }
4983      }
4984    });
4985    const noTap = function () {
4986      couldBeTap = false;
4987    };
4988
4989    // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4990    this.on('touchleave', noTap);
4991    this.on('touchcancel', noTap);
4992
4993    // When the touch ends, measure how long it took and trigger the appropriate
4994    // event
4995    this.on('touchend', function (event) {
4996      firstTouch = null;
4997      // Proceed only if the touchmove/leave/cancel event didn't happen
4998      if (couldBeTap === true) {
4999        // Measure how long the touch lasted
5000        const touchTime = window__default['default'].performance.now() - touchStart;
5001
5002        // Make sure the touch was less than the threshold to be considered a tap
5003        if (touchTime < touchTimeThreshold) {
5004          // Don't let browser turn this into a click
5005          event.preventDefault();
5006          /**
5007           * Triggered when a `Component` is tapped.
5008           *
5009           * @event Component#tap
5010           * @type {MouseEvent}
5011           */
5012          this.trigger('tap');
5013          // It may be good to copy the touchend event object and change the
5014          // type to tap, if the other event properties aren't exact after
5015          // Events.fixEvent runs (e.g. event.target)
5016        }
5017      }
5018    });
5019  }
5020
5021  /**
5022   * This function reports user activity whenever touch events happen. This can get
5023   * turned off by any sub-components that wants touch events to act another way.
5024   *
5025   * Report user touch activity when touch events occur. User activity gets used to
5026   * determine when controls should show/hide. It is simple when it comes to mouse
5027   * events, because any mouse event should show the controls. So we capture mouse
5028   * events that bubble up to the player and report activity when that happens.
5029   * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
5030   * controls. So touch events can't help us at the player level either.
5031   *
5032   * User activity gets checked asynchronously. So what could happen is a tap event
5033   * on the video turns the controls off. Then the `touchend` event bubbles up to
5034   * the player. Which, if it reported user activity, would turn the controls right
5035   * back on. We also don't want to completely block touch events from bubbling up.
5036   * Furthermore a `touchmove` event and anything other than a tap, should not turn
5037   * controls back on.
5038   *
5039   * @listens Component#touchstart
5040   * @listens Component#touchmove
5041   * @listens Component#touchend
5042   * @listens Component#touchcancel
5043   */
5044  enableTouchActivity() {
5045    // Don't continue if the root player doesn't support reporting user activity
5046    if (!this.player() || !this.player().reportUserActivity) {
5047      return;
5048    }
5049
5050    // listener for reporting that the user is active
5051    const report = bind_(this.player(), this.player().reportUserActivity);
5052    let touchHolding;
5053    this.on('touchstart', function () {
5054      report();
5055      // For as long as the they are touching the device or have their mouse down,
5056      // we consider them active even if they're not moving their finger or mouse.
5057      // So we want to continue to update that they are active
5058      this.clearInterval(touchHolding);
5059      // report at the same interval as activityCheck
5060      touchHolding = this.setInterval(report, 250);
5061    });
5062    const touchEnd = function (event) {
5063      report();
5064      // stop the interval that maintains activity if the touch is holding
5065      this.clearInterval(touchHolding);
5066    };
5067    this.on('touchmove', report);
5068    this.on('touchend', touchEnd);
5069    this.on('touchcancel', touchEnd);
5070  }
5071
5072  /**
5073   * A callback that has no parameters and is bound into `Component`s context.
5074   *
5075   * @callback Component~GenericCallback
5076   * @this Component
5077   */
5078
5079  /**
5080   * Creates a function that runs after an `x` millisecond timeout. This function is a
5081   * wrapper around `window.setTimeout`. There are a few reasons to use this one
5082   * instead though:
5083   * 1. It gets cleared via  {@link Component#clearTimeout} when
5084   *    {@link Component#dispose} gets called.
5085   * 2. The function callback will gets turned into a {@link Component~GenericCallback}
5086   *
5087   * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
5088   *         will cause its dispose listener not to get cleaned up! Please use
5089   *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
5090   *
5091   * @param {Component~GenericCallback} fn
5092   *        The function that will be run after `timeout`.
5093   *
5094   * @param {number} timeout
5095   *        Timeout in milliseconds to delay before executing the specified function.
5096   *
5097   * @return {number}
5098   *         Returns a timeout ID that gets used to identify the timeout. It can also
5099   *         get used in {@link Component#clearTimeout} to clear the timeout that
5100   *         was set.
5101   *
5102   * @listens Component#dispose
5103   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
5104   */
5105  setTimeout(fn, timeout) {
5106    // declare as variables so they are properly available in timeout function
5107    // eslint-disable-next-line
5108    var timeoutId;
5109    fn = bind_(this, fn);
5110    this.clearTimersOnDispose_();
5111    timeoutId = window__default['default'].setTimeout(() => {
5112      if (this.setTimeoutIds_.has(timeoutId)) {
5113        this.setTimeoutIds_.delete(timeoutId);
5114      }
5115      fn();
5116    }, timeout);
5117    this.setTimeoutIds_.add(timeoutId);
5118    return timeoutId;
5119  }
5120
5121  /**
5122   * Clears a timeout that gets created via `window.setTimeout` or
5123   * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
5124   * use this function instead of `window.clearTimout`. If you don't your dispose
5125   * listener will not get cleaned up until {@link Component#dispose}!
5126   *
5127   * @param {number} timeoutId
5128   *        The id of the timeout to clear. The return value of
5129   *        {@link Component#setTimeout} or `window.setTimeout`.
5130   *
5131   * @return {number}
5132   *         Returns the timeout id that was cleared.
5133   *
5134   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
5135   */
5136  clearTimeout(timeoutId) {
5137    if (this.setTimeoutIds_.has(timeoutId)) {
5138      this.setTimeoutIds_.delete(timeoutId);
5139      window__default['default'].clearTimeout(timeoutId);
5140    }
5141    return timeoutId;
5142  }
5143
5144  /**
5145   * Creates a function that gets run every `x` milliseconds. This function is a wrapper
5146   * around `window.setInterval`. There are a few reasons to use this one instead though.
5147   * 1. It gets cleared via  {@link Component#clearInterval} when
5148   *    {@link Component#dispose} gets called.
5149   * 2. The function callback will be a {@link Component~GenericCallback}
5150   *
5151   * @param {Component~GenericCallback} fn
5152   *        The function to run every `x` seconds.
5153   *
5154   * @param {number} interval
5155   *        Execute the specified function every `x` milliseconds.
5156   *
5157   * @return {number}
5158   *         Returns an id that can be used to identify the interval. It can also be be used in
5159   *         {@link Component#clearInterval} to clear the interval.
5160   *
5161   * @listens Component#dispose
5162   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
5163   */
5164  setInterval(fn, interval) {
5165    fn = bind_(this, fn);
5166    this.clearTimersOnDispose_();
5167    const intervalId = window__default['default'].setInterval(fn, interval);
5168    this.setIntervalIds_.add(intervalId);
5169    return intervalId;
5170  }
5171
5172  /**
5173   * Clears an interval that gets created via `window.setInterval` or
5174   * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
5175   * use this function instead of `window.clearInterval`. If you don't your dispose
5176   * listener will not get cleaned up until {@link Component#dispose}!
5177   *
5178   * @param {number} intervalId
5179   *        The id of the interval to clear. The return value of
5180   *        {@link Component#setInterval} or `window.setInterval`.
5181   *
5182   * @return {number}
5183   *         Returns the interval id that was cleared.
5184   *
5185   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
5186   */
5187  clearInterval(intervalId) {
5188    if (this.setIntervalIds_.has(intervalId)) {
5189      this.setIntervalIds_.delete(intervalId);
5190      window__default['default'].clearInterval(intervalId);
5191    }
5192    return intervalId;
5193  }
5194
5195  /**
5196   * Queues up a callback to be passed to requestAnimationFrame (rAF), but
5197   * with a few extra bonuses:
5198   *
5199   * - Supports browsers that do not support rAF by falling back to
5200   *   {@link Component#setTimeout}.
5201   *
5202   * - The callback is turned into a {@link Component~GenericCallback} (i.e.
5203   *   bound to the component).
5204   *
5205   * - Automatic cancellation of the rAF callback is handled if the component
5206   *   is disposed before it is called.
5207   *
5208   * @param  {Component~GenericCallback} fn
5209   *         A function that will be bound to this component and executed just
5210   *         before the browser's next repaint.
5211   *
5212   * @return {number}
5213   *         Returns an rAF ID that gets used to identify the timeout. It can
5214   *         also be used in {@link Component#cancelAnimationFrame} to cancel
5215   *         the animation frame callback.
5216   *
5217   * @listens Component#dispose
5218   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
5219   */
5220  requestAnimationFrame(fn) {
5221    this.clearTimersOnDispose_();
5222
5223    // declare as variables so they are properly available in rAF function
5224    // eslint-disable-next-line
5225    var id;
5226    fn = bind_(this, fn);
5227    id = window__default['default'].requestAnimationFrame(() => {
5228      if (this.rafIds_.has(id)) {
5229        this.rafIds_.delete(id);
5230      }
5231      fn();
5232    });
5233    this.rafIds_.add(id);
5234    return id;
5235  }
5236
5237  /**
5238   * Request an animation frame, but only one named animation
5239   * frame will be queued. Another will never be added until
5240   * the previous one finishes.
5241   *
5242   * @param {string} name
5243   *        The name to give this requestAnimationFrame
5244   *
5245   * @param  {Component~GenericCallback} fn
5246   *         A function that will be bound to this component and executed just
5247   *         before the browser's next repaint.
5248   */
5249  requestNamedAnimationFrame(name, fn) {
5250    if (this.namedRafs_.has(name)) {
5251      this.cancelNamedAnimationFrame(name);
5252    }
5253    this.clearTimersOnDispose_();
5254    fn = bind_(this, fn);
5255    const id = this.requestAnimationFrame(() => {
5256      fn();
5257      if (this.namedRafs_.has(name)) {
5258        this.namedRafs_.delete(name);
5259      }
5260    });
5261    this.namedRafs_.set(name, id);
5262    return name;
5263  }
5264
5265  /**
5266   * Cancels a current named animation frame if it exists.
5267   *
5268   * @param {string} name
5269   *        The name of the requestAnimationFrame to cancel.
5270   */
5271  cancelNamedAnimationFrame(name) {
5272    if (!this.namedRafs_.has(name)) {
5273      return;
5274    }
5275    this.cancelAnimationFrame(this.namedRafs_.get(name));
5276    this.namedRafs_.delete(name);
5277  }
5278
5279  /**
5280   * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
5281   * (rAF).
5282   *
5283   * If you queue an rAF callback via {@link Component#requestAnimationFrame},
5284   * use this function instead of `window.cancelAnimationFrame`. If you don't,
5285   * your dispose listener will not get cleaned up until {@link Component#dispose}!
5286   *
5287   * @param {number} id
5288   *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
5289   *
5290   * @return {number}
5291   *         Returns the rAF ID that was cleared.
5292   *
5293   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
5294   */
5295  cancelAnimationFrame(id) {
5296    if (this.rafIds_.has(id)) {
5297      this.rafIds_.delete(id);
5298      window__default['default'].cancelAnimationFrame(id);
5299    }
5300    return id;
5301  }
5302
5303  /**
5304   * A function to setup `requestAnimationFrame`, `setTimeout`,
5305   * and `setInterval`, clearing on dispose.
5306   *
5307   * > Previously each timer added and removed dispose listeners on it's own.
5308   * For better performance it was decided to batch them all, and use `Set`s
5309   * to track outstanding timer ids.
5310   *
5311   * @private
5312   */
5313  clearTimersOnDispose_() {
5314    if (this.clearingTimersOnDispose_) {
5315      return;
5316    }
5317    this.clearingTimersOnDispose_ = true;
5318    this.one('dispose', () => {
5319      [
5320        ['namedRafs_', 'cancelNamedAnimationFrame'],
5321        ['rafIds_', 'cancelAnimationFrame'],
5322        ['setTimeoutIds_', 'clearTimeout'],
5323        ['setIntervalIds_', 'clearInterval'],
5324      ].forEach(([idName, cancelName]) => {
5325        // for a `Set` key will actually be the value again
5326        // so forEach((val, val) =>` but for maps we want to use
5327        // the key.
5328        this[idName].forEach((val, key) => this[cancelName](key));
5329      });
5330      this.clearingTimersOnDispose_ = false;
5331    });
5332  }
5333
5334  /**
5335   * Decide whether an element is actually disabled or not.
5336   *
5337   * @function isActuallyDisabled
5338   * @param element {Node}
5339   * @return {boolean}
5340   *
5341   * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled}
5342   */
5343  getIsDisabled() {
5344    return Boolean(this.el_.disabled);
5345  }
5346
5347  /**
5348   * Decide whether the element is expressly inert or not.
5349   *
5350   * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert}
5351   * @function isExpresslyInert
5352   * @param element {Node}
5353   * @return {boolean}
5354   */
5355  getIsExpresslyInert() {
5356    return this.el_.inert && !this.el_.ownerDocument.documentElement.inert;
5357  }
5358
5359  /**
5360   * Determine whether or not this component can be considered as focusable component.
5361   *
5362   * @param {HTMLElement} el - The HTML element representing the component.
5363   * @return {boolean}
5364   *         If the component can be focused, will be `true`. Otherwise, `false`.
5365   */
5366  getIsFocusable(el) {
5367    const element = el || this.el_;
5368    return element.tabIndex >= 0 && !(this.getIsDisabled() || this.getIsExpresslyInert());
5369  }
5370
5371  /**
5372   * Determine whether or not this component is currently visible/enabled/etc...
5373   *
5374   * @param {HTMLElement} el - The HTML element representing the component.
5375   * @return {boolean}
5376   *         If the component can is currently visible & enabled, will be `true`. Otherwise, `false`.
5377   */
5378  getIsAvailableToBeFocused(el) {
5379    /**
5380     * Decide the style property of this element is specified whether it's visible or not.
5381     *
5382     * @function isVisibleStyleProperty
5383     * @param element {CSSStyleDeclaration}
5384     * @return {boolean}
5385     */
5386    function isVisibleStyleProperty(element) {
5387      const elementStyle = window__default['default'].getComputedStyle(element, null);
5388      const thisVisibility = elementStyle.getPropertyValue('visibility');
5389      const thisDisplay = elementStyle.getPropertyValue('display');
5390      const invisibleStyle = ['hidden', 'collapse'];
5391      return thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility);
5392    }
5393
5394    /**
5395     * Decide whether the element is being rendered or not.
5396     * 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered.
5397     * 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible).
5398     * 3. If width and height of an element are explicitly set to 0, it is not being rendered.
5399     * 4. If a parent element is hidden, an element itself is not being rendered.
5400     * (CSS visibility property and display property are inherited.)
5401     *
5402     * @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered}
5403     * @function isBeingRendered
5404     * @param element {Node}
5405     * @return {boolean}
5406     */
5407    function isBeingRendered(element) {
5408      if (!isVisibleStyleProperty(element.parentElement)) {
5409        return false;
5410      }
5411      if (
5412        !isVisibleStyleProperty(element) ||
5413        element.style.opacity === '0' ||
5414        window__default['default'].getComputedStyle(element).height === '0px' ||
5415        window__default['default'].getComputedStyle(element).width === '0px'
5416      ) {
5417        return false;
5418      }
5419      return true;
5420    }
5421
5422    /**
5423     * Determine if the element is visible for the user or not.
5424     * 1. If an element sum of its offsetWidth, offsetHeight, height and width is less than 1 is not visible.
5425     * 2. If elementCenter.x is less than is not visible.
5426     * 3. If elementCenter.x is more than the document's width is not visible.
5427     * 4. If elementCenter.y is less than 0 is not visible.
5428     * 5. If elementCenter.y is the document's height is not visible.
5429     *
5430     * @function isVisible
5431     * @param element {Node}
5432     * @return {boolean}
5433     */
5434    function isVisible(element) {
5435      if (
5436        element.offsetWidth +
5437          element.offsetHeight +
5438          element.getBoundingClientRect().height +
5439          element.getBoundingClientRect().width ===
5440        0
5441      ) {
5442        return false;
5443      }
5444
5445      // Define elementCenter object with props of x and y
5446      // x: Left position relative to the viewport plus element's width (no margin) divided between 2.
5447      // y: Top position relative to the viewport plus element's height (no margin) divided between 2.
5448      const elementCenter = {
5449        x: element.getBoundingClientRect().left + element.offsetWidth / 2,
5450        y: element.getBoundingClientRect().top + element.offsetHeight / 2,
5451      };
5452      if (elementCenter.x < 0) {
5453        return false;
5454      }
5455      if (
5456        elementCenter.x >
5457        (document__default['default'].documentElement.clientWidth ||
5458          window__default['default'].innerWidth)
5459      ) {
5460        return false;
5461      }
5462      if (elementCenter.y < 0) {
5463        return false;
5464      }
5465      if (
5466        elementCenter.y >
5467        (document__default['default'].documentElement.clientHeight ||
5468          window__default['default'].innerHeight)
5469      ) {
5470        return false;
5471      }
5472      let pointContainer = document__default['default'].elementFromPoint(
5473        elementCenter.x,
5474        elementCenter.y
5475      );
5476      while (pointContainer) {
5477        if (pointContainer === element) {
5478          return true;
5479        }
5480        if (pointContainer.parentNode) {
5481          pointContainer = pointContainer.parentNode;
5482        } else {
5483          return false;
5484        }
5485      }
5486    }
5487
5488    // If no DOM element was passed as argument use this component's element.
5489    if (!el) {
5490      el = this.el();
5491    }
5492
5493    // If element is visible, is being rendered & either does not have a parent element or its tabIndex is not negative.
5494    if (isVisible(el) && isBeingRendered(el) && (!el.parentElement || el.tabIndex >= 0)) {
5495      return true;
5496    }
5497    return false;
5498  }
5499
5500  /**
5501   * Register a `Component` with `videojs` given the name and the component.
5502   *
5503   * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
5504   *         should be registered using {@link Tech.registerTech} or
5505   *         {@link videojs:videojs.registerTech}.
5506   *
5507   * > NOTE: This function can also be seen on videojs as
5508   *         {@link videojs:videojs.registerComponent}.
5509   *
5510   * @param {string} name
5511   *        The name of the `Component` to register.
5512   *
5513   * @param {Component} ComponentToRegister
5514   *        The `Component` class to register.
5515   *
5516   * @return {Component}
5517   *         The `Component` that was registered.
5518   */
5519  static registerComponent(name, ComponentToRegister) {
5520    if (typeof name !== 'string' || !name) {
5521      throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
5522    }
5523    const Tech = Component$1.getComponent('Tech');
5524
5525    // We need to make sure this check is only done if Tech has been registered.
5526    const isTech = Tech && Tech.isTech(ComponentToRegister);
5527    const isComp =
5528      Component$1 === ComponentToRegister ||
5529      Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
5530    if (isTech || !isComp) {
5531      let reason;
5532      if (isTech) {
5533        reason = 'techs must be registered using Tech.registerTech()';
5534      } else {
5535        reason = 'must be a Component subclass';
5536      }
5537      throw new Error(`Illegal component, "${name}"; ${reason}.`);
5538    }
5539    name = toTitleCase$1(name);
5540    if (!Component$1.components_) {
5541      Component$1.components_ = {};
5542    }
5543    const Player = Component$1.getComponent('Player');
5544    if (name === 'Player' && Player && Player.players) {
5545      const players = Player.players;
5546      const playerNames = Object.keys(players);
5547
5548      // If we have players that were disposed, then their name will still be
5549      // in Players.players. So, we must loop through and verify that the value
5550      // for each item is not null. This allows registration of the Player component
5551      // after all players have been disposed or before any were created.
5552      if (
5553        players &&
5554        playerNames.length > 0 &&
5555        playerNames.map((pname) => players[pname]).every(Boolean)
5556      ) {
5557        throw new Error('Can not register Player component after player has been created.');
5558      }
5559    }
5560    Component$1.components_[name] = ComponentToRegister;
5561    Component$1.components_[toLowerCase(name)] = ComponentToRegister;
5562    return ComponentToRegister;
5563  }
5564
5565  /**
5566   * Get a `Component` based on the name it was registered with.
5567   *
5568   * @param {string} name
5569   *        The Name of the component to get.
5570   *
5571   * @return {typeof Component}
5572   *         The `Component` that got registered under the given name.
5573   */
5574  static getComponent(name) {
5575    if (!name || !Component$1.components_) {
5576      return;
5577    }
5578    return Component$1.components_[name];
5579  }
5580}
5581Component$1.registerComponent('Component', Component$1);
5582
5583/**
5584 * @file time.js
5585 * @module time
5586 */
5587
5588/**
5589 * Returns the time for the specified index at the start or end
5590 * of a TimeRange object.
5591 *
5592 * @typedef    {Function} TimeRangeIndex
5593 *
5594 * @param      {number} [index=0]
5595 *             The range number to return the time for.
5596 *
5597 * @return     {number}
5598 *             The time offset at the specified index.
5599 *
5600 * @deprecated The index argument must be provided.
5601 *             In the future, leaving it out will throw an error.
5602 */
5603
5604/**
5605 * An object that contains ranges of time, which mimics {@link TimeRanges}.
5606 *
5607 * @typedef  {Object} TimeRange
5608 *
5609 * @property {number} length
5610 *           The number of time ranges represented by this object.
5611 *
5612 * @property {module:time~TimeRangeIndex} start
5613 *           Returns the time offset at which a specified time range begins.
5614 *
5615 * @property {module:time~TimeRangeIndex} end
5616 *           Returns the time offset at which a specified time range ends.
5617 *
5618 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5619 */
5620
5621/**
5622 * Check if any of the time ranges are over the maximum index.
5623 *
5624 * @private
5625 * @param   {string} fnName
5626 *          The function name to use for logging
5627 *
5628 * @param   {number} index
5629 *          The index to check
5630 *
5631 * @param   {number} maxIndex
5632 *          The maximum possible index
5633 *
5634 * @throws  {Error} if the timeRanges provided are over the maxIndex
5635 */
5636function rangeCheck(fnName, index, maxIndex) {
5637  if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5638    throw new Error(
5639      `Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`
5640    );
5641  }
5642}
5643
5644/**
5645 * Get the time for the specified index at the start or end
5646 * of a TimeRange object.
5647 *
5648 * @private
5649 * @param      {string} fnName
5650 *             The function name to use for logging
5651 *
5652 * @param      {string} valueIndex
5653 *             The property that should be used to get the time. should be
5654 *             'start' or 'end'
5655 *
5656 * @param      {Array} ranges
5657 *             An array of time ranges
5658 *
5659 * @param      {Array} [rangeIndex=0]
5660 *             The index to start the search at
5661 *
5662 * @return     {number}
5663 *             The time that offset at the specified index.
5664 *
5665 * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5666 * @throws     {Error} if rangeIndex is more than the length of ranges
5667 */
5668function getRange(fnName, valueIndex, ranges, rangeIndex) {
5669  rangeCheck(fnName, rangeIndex, ranges.length - 1);
5670  return ranges[rangeIndex][valueIndex];
5671}
5672
5673/**
5674 * Create a time range object given ranges of time.
5675 *
5676 * @private
5677 * @param   {Array} [ranges]
5678 *          An array of time ranges.
5679 *
5680 * @return  {TimeRange}
5681 */
5682function createTimeRangesObj(ranges) {
5683  let timeRangesObj;
5684  if (ranges === undefined || ranges.length === 0) {
5685    timeRangesObj = {
5686      length: 0,
5687      start() {
5688        throw new Error('This TimeRanges object is empty');
5689      },
5690      end() {
5691        throw new Error('This TimeRanges object is empty');
5692      },
5693    };
5694  } else {
5695    timeRangesObj = {
5696      length: ranges.length,
5697      start: getRange.bind(null, 'start', 0, ranges),
5698      end: getRange.bind(null, 'end', 1, ranges),
5699    };
5700  }
5701  if (window__default['default'].Symbol && window__default['default'].Symbol.iterator) {
5702    timeRangesObj[window__default['default'].Symbol.iterator] = () => (ranges || []).values();
5703  }
5704  return timeRangesObj;
5705}
5706
5707/**
5708 * Create a `TimeRange` object which mimics an
5709 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5710 *
5711 * @param {number|Array[]} start
5712 *        The start of a single range (a number) or an array of ranges (an
5713 *        array of arrays of two numbers each).
5714 *
5715 * @param {number} end
5716 *        The end of a single range. Cannot be used with the array form of
5717 *        the `start` argument.
5718 *
5719 * @return {TimeRange}
5720 */
5721function createTimeRanges$1(start, end) {
5722  if (Array.isArray(start)) {
5723    return createTimeRangesObj(start);
5724  } else if (start === undefined || end === undefined) {
5725    return createTimeRangesObj();
5726  }
5727  return createTimeRangesObj([[start, end]]);
5728}
5729
5730/**
5731 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
5732 * seconds) will force a number of leading zeros to cover the length of the
5733 * guide.
5734 *
5735 * @private
5736 * @param  {number} seconds
5737 *         Number of seconds to be turned into a string
5738 *
5739 * @param  {number} guide
5740 *         Number (in seconds) to model the string after
5741 *
5742 * @return {string}
5743 *         Time formatted as H:MM:SS or M:SS
5744 */
5745const defaultImplementation = function (seconds, guide) {
5746  seconds = seconds < 0 ? 0 : seconds;
5747  let s = Math.floor(seconds % 60);
5748  let m = Math.floor((seconds / 60) % 60);
5749  let h = Math.floor(seconds / 3600);
5750  const gm = Math.floor((guide / 60) % 60);
5751  const gh = Math.floor(guide / 3600);
5752
5753  // handle invalid times
5754  if (isNaN(seconds) || seconds === Infinity) {
5755    // '-' is false for all relational operators (e.g. <, >=) so this setting
5756    // will add the minimum number of fields specified by the guide
5757    h = m = s = '-';
5758  }
5759
5760  // Check if we need to show hours
5761  h = h > 0 || gh > 0 ? h + ':' : '';
5762
vendor: 9,700 bytes, lines 5763-6119
5763  // If hours are showing, we may need to add a leading zero.
5764  // Always show at least one digit of minutes.
5765  m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
5766
5767  // Check if leading zero is need for seconds
5768  s = s < 10 ? '0' + s : s;
5769  return h + m + s;
5770};
5771
5772// Internal pointer to the current implementation.
5773let implementation = defaultImplementation;
5774
5775/**
5776 * Replaces the default formatTime implementation with a custom implementation.
5777 *
5778 * @param {Function} customImplementation
5779 *        A function which will be used in place of the default formatTime
5780 *        implementation. Will receive the current time in seconds and the
5781 *        guide (in seconds) as arguments.
5782 */
5783function setFormatTime(customImplementation) {
5784  implementation = customImplementation;
5785}
5786
5787/**
5788 * Resets formatTime to the default implementation.
5789 */
5790function resetFormatTime() {
5791  implementation = defaultImplementation;
5792}
5793
5794/**
5795 * Delegates to either the default time formatting function or a custom
5796 * function supplied via `setFormatTime`.
5797 *
5798 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
5799 * guide (in seconds) will force a number of leading zeros to cover the
5800 * length of the guide.
5801 *
5802 * @example  formatTime(125, 600) === "02:05"
5803 * @param    {number} seconds
5804 *           Number of seconds to be turned into a string
5805 *
5806 * @param    {number} guide
5807 *           Number (in seconds) to model the string after
5808 *
5809 * @return   {string}
5810 *           Time formatted as H:MM:SS or M:SS
5811 */
5812function formatTime(seconds, guide = seconds) {
5813  return implementation(seconds, guide);
5814}
5815
5816var Time = /*#__PURE__*/ Object.freeze({
5817  __proto__: null,
5818  createTimeRanges: createTimeRanges$1,
5819  createTimeRange: createTimeRanges$1,
5820  setFormatTime: setFormatTime,
5821  resetFormatTime: resetFormatTime,
5822  formatTime: formatTime,
5823});
5824
5825/**
5826 * @file buffer.js
5827 * @module buffer
5828 */
5829
5830/** @import { TimeRange } from './time' */
5831
5832/**
5833 * Compute the percentage of the media that has been buffered.
5834 *
5835 * @param {TimeRange} buffered
5836 *        The current `TimeRanges` object representing buffered time ranges
5837 *
5838 * @param {number} duration
5839 *        Total duration of the media
5840 *
5841 * @return {number}
5842 *         Percent buffered of the total duration in decimal form.
5843 */
5844function bufferedPercent(buffered, duration) {
5845  let bufferedDuration = 0;
5846  let start;
5847  let end;
5848  if (!duration) {
5849    return 0;
5850  }
5851  if (!buffered || !buffered.length) {
5852    buffered = createTimeRanges$1(0, 0);
5853  }
5854  for (let i = 0; i < buffered.length; i++) {
5855    start = buffered.start(i);
5856    end = buffered.end(i);
5857
5858    // buffered end can be bigger than duration by a very small fraction
5859    if (end > duration) {
5860      end = duration;
5861    }
5862    bufferedDuration += end - start;
5863  }
5864  return bufferedDuration / duration;
5865}
5866
5867/**
5868 * @file media-error.js
5869 */
5870
5871/**
5872 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5873 *
5874 * @param {number|string|Object|MediaError} value
5875 *        This can be of multiple types:
5876 *        - number: should be a standard error code
5877 *        - string: an error message (the code will be 0)
5878 *        - Object: arbitrary properties
5879 *        - `MediaError` (native): used to populate a video.js `MediaError` object
5880 *        - `MediaError` (video.js): will return itself if it's already a
5881 *          video.js `MediaError` object.
5882 *
5883 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5884 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5885 *
5886 * @class MediaError
5887 */
5888function MediaError(value) {
5889  // Allow redundant calls to this constructor to avoid having `instanceof`
5890  // checks peppered around the code.
5891  if (value instanceof MediaError) {
5892    return value;
5893  }
5894  if (typeof value === 'number') {
5895    this.code = value;
5896  } else if (typeof value === 'string') {
5897    // default code is zero, so this is a custom error
5898    this.message = value;
5899  } else if (isObject(value)) {
5900    // We assign the `code` property manually because native `MediaError` objects
5901    // do not expose it as an own/enumerable property of the object.
5902    if (typeof value.code === 'number') {
5903      this.code = value.code;
5904    }
5905    Object.assign(this, value);
5906  }
5907  if (!this.message) {
5908    this.message = MediaError.defaultMessages[this.code] || '';
5909  }
5910}
5911
5912/**
5913 * The error code that refers two one of the defined `MediaError` types
5914 *
5915 * @type {Number}
5916 */
5917MediaError.prototype.code = 0;
5918
5919/**
5920 * An optional message that to show with the error. Message is not part of the HTML5
5921 * video spec but allows for more informative custom errors.
5922 *
5923 * @type {String}
5924 */
5925MediaError.prototype.message = '';
5926
5927/**
5928 * An optional status code that can be set by plugins to allow even more detail about
5929 * the error. For example a plugin might provide a specific HTTP status code and an
5930 * error message for that code. Then when the plugin gets that error this class will
5931 * know how to display an error message for it. This allows a custom message to show
5932 * up on the `Player` error overlay.
5933 *
5934 * @type {Array}
5935 */
5936MediaError.prototype.status = null;
5937
5938/**
5939 * An object containing an error type, as well as other information regarding the error.
5940 *
5941 * @typedef {{errorType: string, [key: string]: any}} ErrorMetadata
5942 */
5943
5944/**
5945 * An optional object to give more detail about the error. This can be used to give
5946 * a higher level of specificity to an error versus the more generic MediaError codes.
5947 * `metadata` expects an `errorType` string that should align with the values from videojs.Error.
5948 *
5949 * @type {ErrorMetadata}
5950 */
5951MediaError.prototype.metadata = null;
5952
5953/**
5954 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5955 * specification listed under {@link MediaError} for more information.
5956 *
5957 * @enum {array}
5958 * @readonly
5959 * @property {string} 0 - MEDIA_ERR_CUSTOM
5960 * @property {string} 1 - MEDIA_ERR_ABORTED
5961 * @property {string} 2 - MEDIA_ERR_NETWORK
5962 * @property {string} 3 - MEDIA_ERR_DECODE
5963 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5964 * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5965 */
5966MediaError.errorTypes = [
5967  'MEDIA_ERR_CUSTOM',
5968  'MEDIA_ERR_ABORTED',
5969  'MEDIA_ERR_NETWORK',
5970  'MEDIA_ERR_DECODE',
5971  'MEDIA_ERR_SRC_NOT_SUPPORTED',
5972  'MEDIA_ERR_ENCRYPTED',
5973];
5974
5975/**
5976 * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5977 *
5978 * @type {Array}
5979 * @constant
5980 */
5981MediaError.defaultMessages = {
5982  1: 'You aborted the media playback',
5983  2: 'A network error caused the media download to fail part-way.',
5984  3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5985  4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5986  5: 'The media is encrypted and we do not have the keys to decrypt it.',
5987};
5988
5989/**
5990 * W3C error code for any custom error.
5991 *
5992 * @member MediaError#MEDIA_ERR_CUSTOM
5993 * @constant {number}
5994 * @default 0
5995 */
5996MediaError.MEDIA_ERR_CUSTOM = 0;
5997
5998/**
5999 * W3C error code for any custom error.
6000 *
6001 * @member MediaError.MEDIA_ERR_CUSTOM
6002 * @constant {number}
6003 * @default 0
6004 */
6005MediaError.prototype.MEDIA_ERR_CUSTOM = 0;
6006
6007/**
6008 * W3C error code for media error aborted.
6009 *
6010 * @member MediaError#MEDIA_ERR_ABORTED
6011 * @constant {number}
6012 * @default 1
6013 */
6014MediaError.MEDIA_ERR_ABORTED = 1;
6015
6016/**
6017 * W3C error code for media error aborted.
6018 *
6019 * @member MediaError.MEDIA_ERR_ABORTED
6020 * @constant {number}
6021 * @default 1
6022 */
6023MediaError.prototype.MEDIA_ERR_ABORTED = 1;
6024
6025/**
6026 * W3C error code for any network error.
6027 *
6028 * @member MediaError#MEDIA_ERR_NETWORK
6029 * @constant {number}
6030 * @default 2
6031 */
6032MediaError.MEDIA_ERR_NETWORK = 2;
6033
6034/**
6035 * W3C error code for any network error.
6036 *
6037 * @member MediaError.MEDIA_ERR_NETWORK
6038 * @constant {number}
6039 * @default 2
6040 */
6041MediaError.prototype.MEDIA_ERR_NETWORK = 2;
6042
6043/**
6044 * W3C error code for any decoding error.
6045 *
6046 * @member MediaError#MEDIA_ERR_DECODE
6047 * @constant {number}
6048 * @default 3
6049 */
6050MediaError.MEDIA_ERR_DECODE = 3;
6051
6052/**
6053 * W3C error code for any decoding error.
6054 *
6055 * @member MediaError.MEDIA_ERR_DECODE
6056 * @constant {number}
6057 * @default 3
6058 */
6059MediaError.prototype.MEDIA_ERR_DECODE = 3;
6060
6061/**
6062 * W3C error code for any time that a source is not supported.
6063 *
6064 * @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED
6065 * @constant {number}
6066 * @default 4
6067 */
6068MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
6069
6070/**
6071 * W3C error code for any time that a source is not supported.
6072 *
6073 * @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
6074 * @constant {number}
6075 * @default 4
6076 */
6077MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
6078
6079/**
6080 * W3C error code for any time that a source is encrypted.
6081 *
6082 * @member MediaError#MEDIA_ERR_ENCRYPTED
6083 * @constant {number}
6084 * @default 5
6085 */
6086MediaError.MEDIA_ERR_ENCRYPTED = 5;
6087
6088/**
6089 * W3C error code for any time that a source is encrypted.
6090 *
6091 * @member MediaError.MEDIA_ERR_ENCRYPTED
6092 * @constant {number}
6093 * @default 5
6094 */
6095MediaError.prototype.MEDIA_ERR_ENCRYPTED = 5;
6096
6097/**
6098 * Returns whether an object is `Promise`-like (i.e. has a `then` method).
6099 *
6100 * @param  {Object}  value
6101 *         An object that may or may not be `Promise`-like.
6102 *
6103 * @return {boolean}
6104 *         Whether or not the object is `Promise`-like.
6105 */
6106function isPromise(value) {
6107  return value !== undefined && value !== null && typeof value.then === 'function';
6108}
6109
6110/**
6111 * Silence a Promise-like object.
6112 *
6113 * This is useful for avoiding non-harmful, but potentially confusing "uncaught
6114 * play promise" rejection error messages.
6115 *
6116 * @param  {Object} value
6117 *         An object that may or may not be `Promise`-like.
6118 */
6119function silencePromise(value) {
6120  if (isPromise(value)) {
6121    value.then(null, (e) => {});
6122  }
6123}
6124
6125/**
6126 * @file text-track-list-converter.js Utilities for capturing text track state and
6127 * re-creating tracks based on a capture.
6128 *
6129 * @module text-track-list-converter
6130 */
6131
6132/** @import Tech from '../tech/tech' */
6133
6134/**
6135 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
6136 * represents the {@link TextTrack}'s state.
6137 *
6138 * @param {TextTrack} track
6139 *        The text track to query.
6140 *
6141 * @return {Object}
6142 *         A serializable javascript representation of the TextTrack.
6143 * @private
6144 */
6145const trackToJson_ = function (track) {
6146  const ret = [
6147    'kind',
6148    'label',
6149    'language',
6150    'id',
6151    'inBandMetadataTrackDispatchType',
6152    'mode',
6153    'src',
6154  ].reduce(
6155    (acc, prop, i) => {
6156      if (track[prop]) {
6157        acc[prop] = track[prop];
6158      }
6159      return acc;
6160    },
6161    {
6162      cues:
6163        track.cues &&
6164        Array.prototype.map.call(track.cues, function (cue) {
6165          return {
6166            startTime: cue.startTime,
6167            endTime: cue.endTime,
6168            text: cue.text,
6169            id: cue.id,
6170          };
6171        }),
6172    }
6173  );
6174  return ret;
6175};
6176
6177/**
6178 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
6179 * state of all {@link TextTrack}s currently configured. The return array is compatible with
6180 * {@link text-track-list-converter:jsonToTextTracks}.
6181 *
6182 * @param {Tech} tech
6183 *        The tech object to query
6184 *
6185 * @return {Array}
6186 *         A serializable javascript representation of the {@link Tech}s
6187 *         {@link TextTrackList}.
6188 */
6189const textTracksToJson = function (tech) {
6190  const trackEls = tech.$$('track');
6191  const trackObjs = Array.prototype.map.call(trackEls, (t) => t.track);
6192  const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
6193    const json = trackToJson_(trackEl.track);
6194    if (trackEl.src) {
6195      json.src = trackEl.src;
6196    }
6197    return json;
6198  });
6199  return tracks.concat(
6200    Array.prototype.filter
6201      .call(tech.textTracks(), function (track) {
6202        return trackObjs.indexOf(track) === -1;
6203      })
6204      .map(trackToJson_)
6205  );
6206};
6207
6208/**
6209 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
6210 * object {@link TextTrack} representations.
6211 *
6212 * @param {Array} json
6213 *        An array of `TextTrack` representation objects, like those that would be
6214 *        produced by `textTracksToJson`.
6215 *
6216 * @param {Tech} tech
6217 *        The `Tech` to create the `TextTrack`s on.
6218 */
6219const jsonToTextTracks = function (json, tech) {
6220  json.forEach(function (track) {
6221    const addedTrack = tech.addRemoteTextTrack(track).track;
6222    if (!track.src && track.cues) {
6223      track.cues.forEach((cue) => addedTrack.addCue(cue));
6224    }
6225  });
6226  return tech.textTracks();
6227};
6228var textTrackConverter = {
6229  textTracksToJson,
6230  jsonToTextTracks,
6231  trackToJson_,
6232};
6233
6234/**
6235 * @file modal-dialog.js
6236 */
6237
6238/** @import Player from './player' */
6239/** @import { ContentDescriptor } from './utils/dom' */
6240
6241const MODAL_CLASS_NAME = 'vjs-modal-dialog';
6242
6243/**
6244 * The `ModalDialog` displays over the video and its controls, which blocks
6245 * interaction with the player until it is closed.
6246 *
6247 * Modal dialogs include a "Close" button and will close when that button
6248 * is activated - or when ESC is pressed anywhere.
6249 *
6250 * @extends Component
6251 */
6252class ModalDialog extends Component$1 {
6253  /**
6254   * Creates an instance of this class.
6255   *
6256   * @param {Player} player
6257   *        The `Player` that this class should be attached to.
6258   *
6259   * @param {Object} [options]
6260   *        The key/value store of player options.
6261   *
6262   * @param {ContentDescriptor} [options.content=undefined]
6263   *        Provide customized content for this modal.
6264   *
6265   * @param {string} [options.description]
6266   *        A text description for the modal, primarily for accessibility.
6267   *
6268   * @param {boolean} [options.fillAlways=false]
6269   *        Normally, modals are automatically filled only the first time
6270   *        they open. This tells the modal to refresh its content
6271   *        every time it opens.
6272   *
6273   * @param {string} [options.label]
6274   *        A text label for the modal, primarily for accessibility.
6275   *
6276   * @param {boolean}
6276 [options.pauseOnOpen=true]
6277   *        If `true`, playback will will be paused if playing when
6278   *        the modal opens, and resumed when it closes.
6279   *
6280   * @param {boolean} [options.temporary=true]
6281   *        If `true`, the modal can only be opened once; it will be
6282   *        disposed as soon as it's closed.
6283   *
6284   * @param {boolean} [options.uncloseable=false]
6285   *        If `true`, the user will not be able to close the modal
6286   *        through the UI in the normal ways. Programmatic closing is
6287   *        still possible.
6288   */
6289  constructor(player, options) {
6290    super(player, options);
6291    this.handleKeyDown_ = (e) => this.handleKeyDown(e);
6292    this.close_ = (e) => this.close(e);
6293    this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
6294    this.closeable(!this.options_.uncloseable);
6295    this.content(this.options_.content);
6296
6297    // Make sure the contentEl is defined AFTER any children are initialized
6298    // because we only want the contents of the modal in the contentEl
6299    // (not the UI elements like the close button).
6300    this.contentEl_ = createEl(
6301      'div',
6302      {
6303        className: `${MODAL_CLASS_NAME}-content`,
6304      },
6305      {
6306        role: 'document',
6307      }
6308    );
6309    this.descEl_ = createEl('p', {
6310      className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
6311      id: this.el().getAttribute('aria-describedby'),
6312    });
6313    textContent(this.descEl_, this.description());
6314    this.el_.appendChild(this.descEl_);
6315    this.el_.appendChild(this.contentEl_);
6316  }
6317
6318  /**
6319   * Create the `ModalDialog`'s DOM element
6320   *
6321   * @return {Element}
6322   *         The DOM element that gets created.
6323   */
6324  createEl() {
6325    return super.createEl(
6326      'div',
6327      {
6328        className: this.buildCSSClass(),
6329        tabIndex: -1,
6330      },
6331      {
6332        'aria-describedby': `${this.id()}_description`,
6333        'aria-hidden': 'true',
6334        'aria-label': this.label(),
6335        role: 'dialog',
6336        'aria-live': 'polite',
6337      }
6338    );
6339  }
6340  dispose() {
6341    this.contentEl_ = null;
6342    this.descEl_ = null;
6343    this.previouslyActiveEl_ = null;
6344    super.dispose();
6345  }
6346
6347  /**
6348   * Builds the default DOM `className`.
6349   *
6350   * @return {string}
6351   *         The DOM `className` for this object.
6352   */
6353  buildCSSClass() {
6354    return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
6355  }
6356
6357  /**
6358   * Returns the label string for this modal. Primarily used for accessibility.
6359   *
6360   * @return {string}
6361   *         the localized or raw label of this modal.
6362   */
6363  label() {
6364    return this.localize(this.options_.label || 'Modal Window');
6365  }
6366
6367  /**
6368   * Returns the description string for this modal. Primarily used for
6369   * accessibility.
6370   *
6371   * @return {string}
6372   *         The localized or raw description of this modal.
6373   */
6374  description() {
6375    let desc = this.options_.description || this.localize('This is a modal window.');
6376
6377    // Append a universal closeability message if the modal is closeable.
6378    if (this.closeable()) {
6379      desc +=
6380        ' ' +
6381        this.localize(
6382          'This modal can be closed by pressing the Escape key or activating the close button.'
6383        );
6384    }
6385    return desc;
6386  }
6387
6388  /**
6389   * Opens the modal.
6390   *
6391   * @fires ModalDialog#beforemodalopen
6392   * @fires ModalDialog#modalopen
6393   */
6394  open() {
6395    if (this.opened_) {
6396      if (this.options_.fillAlways) {
6397        this.fill();
6398      }
6399      return;
6400    }
6401    const player = this.player();
6402
6403    /**
6404     * Fired just before a `ModalDialog` is opened.
6405     *
6406     * @event ModalDialog#beforemodalopen
6407     * @type {Event}
6408     */
6409    this.trigger('beforemodalopen');
6410    this.opened_ = true;
6411
6412    // Fill content if the modal has never opened before and
6413    // never been filled.
6414    if (this.options_.fillAlways || (!this.hasBeenOpened_ && !this.hasBeenFilled_)) {
6415      this.fill();
6416    }
6417
6418    // If the player was playing, pause it and take note of its previously
6419    // playing state.
6420    this.wasPlaying_ = !player.paused();
6421    if (this.options_.pauseOnOpen && this.wasPlaying_) {
6422      player.pause();
6423    }
6424    this.on('keydown', this.handleKeyDown_);
6425
6426    // Hide controls and note if they were enabled.
6427    this.hadControls_ = player.controls();
6428    player.controls(false);
6429    this.show();
6430    this.conditionalFocus_();
6431    this.el().setAttribute('aria-hidden', 'false');
6432
6433    /**
6434     * Fired just after a `ModalDialog` is opened.
6435     *
6436     * @event ModalDialog#modalopen
6437     * @type {Event}
6438     */
6439    this.trigger('modalopen');
6440    this.hasBeenOpened_ = true;
6441  }
6442
6443  /**
6444   * If the `ModalDialog` is currently open or closed.
6445   *
6446   * @param  {boolean} [value]
6447   *         If given, it will open (`true`) or close (`false`) the modal.
6448   *
6449   * @return {boolean}
6450   *         the current open state of the modaldialog
6451   */
6452  opened(value) {
6453    if (typeof value === 'boolean') {
6454      this[value ? 'open' : 'close']();
6455    }
6456    return this.opened_;
6457  }
6458
6459  /**
6460   * Closes the modal, does nothing if the `ModalDialog` is
6461   * not open.
6462   *
6463   * @fires ModalDialog#beforemodalclose
6464   * @fires ModalDialog#modalclose
6465   */
6466  close() {
6467    if (!this.opened_) {
6468      return;
6469    }
6470    const player = this.player();
6471
6472    /**
6473     * Fired just before a `ModalDialog` is closed.
6474     *
6475     * @event ModalDialog#beforemodalclose
6476     * @type {Event}
6477     */
6478    this.trigger('beforemodalclose');
6479    this.opened_ = false;
6480    if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6481      player.play();
6482    }
6483    this.off('keydown', this.handleKeyDown_);
6484    if (this.hadControls_) {
6485      player.controls(true);
6486    }
6487    this.hide();
6488    this.el().setAttribute('aria-hidden', 'true');
6489
6490    /**
6491     * Fired just after a `ModalDialog` is closed.
6492     *
6493     * @event ModalDialog#modalclose
6494     * @type {Event}
6495     *
6496     * @property {boolean} [bubbles=true]
6497     */
6498    this.trigger({
6499      type: 'modalclose',
6500      bubbles: true,
6501    });
6502    this.conditionalBlur_();
6503    if (this.options_.temporary) {
6504      this.dispose();
6505    }
6506  }
6507
6508  /**
6509   * Check to see if the `ModalDialog` is closeable via the UI.
6510   *
6511   * @param  {boolean} [value]
6512   *         If given as a boolean, it will set the `closeable` option.
6513   *
6514   * @return {boolean}
6515   *         Returns the final value of the closable option.
6516   */
6517  closeable(value) {
6518    if (typeof value === 'boolean') {
6519      const closeable = (this.closeable_ = !!value);
6520      let close = this.getChild('closeButton');
6521
6522      // If this is being made closeable and has no close button, add one.
6523      if (closeable && !close) {
6524        // The close button should be a child of the modal - not its
6525        // content element, so temporarily change the content element.
6526        const temp = this.contentEl_;
6527        this.contentEl_ = this.el_;
6528        close = this.addChild('closeButton', {
6529          controlText: 'Close Modal Dialog',
6530        });
6531        this.contentEl_ = temp;
6532        this.on(close, 'close', this.close_);
6533      }
6534
6535      // If this is being made uncloseable and has a close button, remove it.
6536      if (!closeable && close) {
6537        this.off(close, 'close', this.close_);
6538        this.removeChild(close);
6539        close.dispose();
6540      }
6541    }
6542    return this.closeable_;
6543  }
6544
6545  /**
vendor: 4,228 bytes, lines 6546-6703
6546   * Fill the modal's content element with the modal's "content" option.
6547   * The content element will be emptied before this change takes place.
6548   */
6549  fill() {
6550    this.fillWith(this.content());
6551  }
6552
6553  /**
6554   * Fill the modal's content element with arbitrary content.
6555   * The content element will be emptied before this change takes place.
6556   *
6557   * @fires ModalDialog#beforemodalfill
6558   * @fires ModalDialog#modalfill
6559   *
6560   * @param {ContentDescriptor} [content]
6561   *        The same rules apply to this as apply to the `content` option.
6562   */
6563  fillWith(content) {
6564    const contentEl = this.contentEl();
6565    const parentEl = contentEl.parentNode;
6566    const nextSiblingEl = contentEl.nextSibling;
6567
6568    /**
6569     * Fired just before a `ModalDialog` is filled with content.
6570     *
6571     * @event ModalDialog#beforemodalfill
6572     * @type {Event}
6573     */
6574    this.trigger('beforemodalfill');
6575    this.hasBeenFilled_ = true;
6576
6577    // Detach the content element from the DOM before performing
6578    // manipulation to avoid modifying the live DOM multiple times.
6579    parentEl.removeChild(contentEl);
6580    this.empty();
6581    insertContent(contentEl, content);
6582    /**
6583     * Fired just after a `ModalDialog` is filled with content.
6584     *
6585     * @event ModalDialog#modalfill
6586     * @type {Event}
6587     */
6588    this.trigger('modalfill');
6589
6590    // Re-inject the re-filled content element.
6591    if (nextSiblingEl) {
6592      parentEl.insertBefore(contentEl, nextSiblingEl);
6593    } else {
6594      parentEl.appendChild(contentEl);
6595    }
6596
6597    // make sure that the close button is last in the dialog DOM
6598    const closeButton = this.getChild('closeButton');
6599    if (closeButton) {
6600      parentEl.appendChild(closeButton.el_);
6601    }
6602
6603    /**
6604     * Fired after `ModalDialog` is re-filled with content & close button is appended.
6605     *
6606     * @event ModalDialog#aftermodalfill
6607     * @type {Event}
6608     */
6609    this.trigger('aftermodalfill');
6610  }
6611
6612  /**
6613   * Empties the content element. This happens anytime the modal is filled.
6614   *
6615   * @fires ModalDialog#beforemodalempty
6616   * @fires ModalDialog#modalempty
6617   */
6618  empty() {
6619    /**
6620     * Fired just before a `ModalDialog` is emptied.
6621     *
6622     * @event ModalDialog#beforemodalempty
6623     * @type {Event}
6624     */
6625    this.trigger('beforemodalempty');
6626    emptyEl(this.contentEl());
6627
6628    /**
6629     * Fired just after a `ModalDialog` is emptied.
6630     *
6631     * @event ModalDialog#modalempty
6632     * @type {Event}
6633     */
6634    this.trigger('modalempty');
6635  }
6636
6637  /**
6638   * Gets or sets the modal content, which gets normalized before being
6639   * rendered into the DOM.
6640   *
6641   * This does not update the DOM or fill the modal, but it is called during
6642   * that process.
6643   *
6644   * @param  {ContentDescriptor} [value]
6645   *         If defined, sets the internal content value to be used on the
6646   *         next call(s) to `fill`. This value is normalized before being
6647   *         inserted. To "clear" the internal content value, pass `null`.
6648   *
6649   * @return {ContentDescriptor}
6650   *         The current content of the modal dialog
6651   */
6652  content(value) {
6653    if (typeof value !== 'undefined') {
6654      this.content_ = value;
6655    }
6656    return this.content_;
6657  }
6658
6659  /**
6660   * conditionally focus the modal dialog if focus was previously on the player.
6661   *
6662   * @private
6663   */
6664  conditionalFocus_() {
6665    const activeEl = document__default['default'].activeElement;
6666    const playerEl = this.player_.el_;
6667    this.previouslyActiveEl_ = null;
6668    if (playerEl.contains(activeEl) || playerEl === activeEl) {
6669      this.previouslyActiveEl_ = activeEl;
6670      this.focus();
6671    }
6672  }
6673
6674  /**
6675   * conditionally blur the element and refocus the last focused element
6676   *
6677   * @private
6678   */
6679  conditionalBlur_() {
6680    if (this.previouslyActiveEl_) {
6681      this.previouslyActiveEl_.focus();
6682      this.previouslyActiveEl_ = null;
6683    }
6684  }
6685
6686  /**
6687   * Keydown handler. Attached when modal is focused.
6688   *
6689   * @listens keydown
6690   */
6691  handleKeyDown(event) {
6692    /**
6693     * Fired a custom keyDown event that bubbles.
6694     *
6695     * @event ModalDialog#modalKeydown
6696     * @type {Event}
6697     */
6698    this.trigger({
6699      type: 'modalKeydown',
6700      originalEvent: event,
6701      target: this,
6702      bubbles: true,
6703    });
6704    // Do not allow keydowns to reach out of the modal dialog.
6705    event.stopPropagation();
6706    if (event.key === 'Escape' && this.closeable()) {
6707      event.preventDefault();
6708      this.close();
6709      return;
6710    }
6711
6712    // exit early if it isn't a tab key
6713    if (event.key !== 'Tab') {
6714      return;
6715    }
6716    const focusableEls = this.focusableEls_();
6717    const activeEl = this.el_.querySelector(':focus');
6718    let focusIndex;
6719    for (let i = 0; i < focusableEls.length; i++) {
6720      if (activeEl === focusableEls[i]) {
6721        focusIndex = i;
6722        break;
6723      }
6724    }
6725    if (document__default['default'].activeElement === this.el_) {
6726      focusIndex = 0;
6727    }
6728    if (event.shiftKey && focusIndex === 0) {
6729      focusableEls[focusableEls.length - 1].focus();
6730      event.preventDefault();
6731    } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6732      focusableEls[0].focus();
6733      event.preventDefault();
6734    }
6735  }
6736
6737  /**
6738   * get all focusable elements
6739   *
6740   * @private
6741   */
6742  focusableEls_() {
6743    const allChildren = this.el_.querySelectorAll('*');
6744    return Array.prototype.filter.call(allChildren, (child) => {
6745      return (
6746        ((child instanceof window__default['default'].HTMLAnchorElement ||
6747          child instanceof window__default['default'].HTMLAreaElement) &&
6748          child.hasAttribute('href')) ||
6749        ((child instanceof window__default['default'].HTMLInputElement ||
6750          child instanceof window__default['default'].HTMLSelectElement ||
6751          child instanceof window__default['default'].HTMLTextAreaElement ||
6752          child instanceof window__default['default'].HTMLButtonElement) &&
6753          !child.hasAttribute('disabled')) ||
6754        child instanceof window__default['default'].HTMLIFrameElement ||
6755        child instanceof window__default['default'].HTMLObjectElement ||
6756        child instanceof window__default['default'].HTMLEmbedElement ||
6757        (child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1) ||
6758        child.hasAttribute('contenteditable')
6759      );
6760    });
6761  }
6762}
6763
6764/**
6765 * Default options for `ModalDialog` default options.
6766 *
6767 * @type {Object}
6768 * @private
6769 */
6770ModalDialog.prototype.options_ = {
6771  pauseOnOpen: true,
6772  temporary: true,
6773};
6774Component$1.registerComponent('ModalDialog', ModalDialog);
6775
6776/**
6777 * @file track-list.js
6778 */
6779
6780/** @import Track from './track' */
6781
6782/**
6783 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6784 * {@link VideoTrackList}
6785 *
6786 * @extends EventTarget
6787 */
6788class TrackList extends EventTarget$2 {
6789  /**
6790   * Create an instance of this class
6791   *
6792   * @param { Track[] } tracks
6793   *        A list of tracks to initialize the list with.
6794   *
6795   * @abstract
6796   */
6797  constructor(tracks = []) {
6798    super();
6799    this.tracks_ = [];
6800
6801    /**
6802     * @memberof TrackList
6803     * @member {number} length
6804     *         The current number of `Track`s in the this Trackist.
6805     * @instance
6806     */
6807    Object.defineProperty(this, 'length', {
6808      get() {
6809        return this.tracks_.length;
6810      },
6811    });
6812    for (let i = 0; i < tracks.length; i++) {
6813      this.addTrack(tracks[i]);
6814    }
6815  }
6816
6817  /**
6818   * Add a {@link Track} to the `TrackList`
6819   *
6820   * @param {Track} track
6821   *        The audio, video, or text track to add to the list.
6822   *
6823   * @fires TrackList#addtrack
6824   */
6825  addTrack(track) {
6826    const index = this.tracks_.length;
6827    if (!('' + index in this)) {
6828      Object.defineProperty(this, index, {
6829        get() {
6830          return this.tracks_[index];
6831        },
6832      });
6833    }
6834
6835    // Do not add duplicate tracks
6836    if (this.tracks_.indexOf(track) === -1) {
6837      this.tracks_.push(track);
6838      /**
6839       * Triggered when a track is added to a track list.
6840       *
6841       * @event TrackList#addtrack
6842       * @type {Event}
6843       * @property {Track} track
6844       *           A reference to track that was added.
6845       */
6846      this.trigger({
6847        track,
6848        type: 'addtrack',
6849        target: this,
6850      });
6851    }
6852
6853    /**
6854     * Triggered when a track label is changed.
6855     *
6856     * @event TrackList#addtrack
6857     * @type {Event}
6858     * @property {Track} track
6859     *           A reference to track that was added.
6860     */
6861    track.labelchange_ = () => {
6862      this.trigger({
6863        track,
6864        type: 'labelchange',
6865        target: this,
6866      });
6867    };
6868    if (isEvented(track)) {
6869      track.addEventListener('labelchange', track.labelchange_);
6870    }
6871  }
6872
6873  /**
6874   * Remove a {@link Track} from the `TrackList`
6875   *
6876   * @param {Track} rtrack
6877   *        The audio, video, or text track to remove from the list.
6878   *
6879   * @fires TrackList#removetrack
6880   */
6881  removeTrack(rtrack) {
6882    let track;
6883    for (let i = 0, l = this.length; i < l; i++) {
6884      if (this[i] === rtrack) {
6885        track = this[i];
6886        if (track.off) {
6887          track.off();
6888        }
6889        this.tracks_.splice(i, 1);
6890        break;
6891      }
6892    }
6893    if (!track) {
6894      return;
6895    }
6896
6897    /**
6898     * Triggered when a track is removed from track list.
6899     *
6900     * @event TrackList#removetrack
6901     * @type {Event}
6902     * @property {Track} track
6903     *           A reference to track that was removed.
6904     */
6905    this.trigger({
6906      track,
6907      type: 'removetrack',
6908      target: this,
6909    });
6910  }
6911
6912  /**
6913   * Get a Track from the TrackList by a tracks id
6914   *
6915   * @param {string} id - the id of the track to get
6916   * @method getTrackById
6917   * @return {Track}
6918   * @private
6919   */
6920  getTrackById(id) {
6921    let result = null;
6922    for (let i = 0, l = this.length; i < l; i++) {
6923      const track = this[i];
6924      if (track.id === id) {
6925        result = track;
6926        break;
6927      }
6928    }
6929    return result;
6930  }
6931}
6932
6933/**
6934 * Triggered when a different track is selected/enabled.
6935 *
6936 * @event TrackList#change
6937 * @type {Event}
6938 */
6939
6940/**
6941 * Events that can be called with on + eventName. See {@link EventHandler}.
6942 *
6943 * @property {Object} TrackList#allowedEvents_
6944 * @protected
6945 */
6946TrackList.prototype.allowedEvents_ = {
6947  change: 'change',
6948  addtrack: 'addtrack',
6949  removetrack: 'removetrack',
6950  labelchange: 'labelchange',
6951};
6952
6953// emulate attribute EventHandler support to allow for feature detection
6954for (const event in TrackList.prototype.allowedEvents_) {
6955  TrackList.prototype['on' + event] = null;
6956}
6957
6958/**
6959 * @file audio-track-list.js
6960 */
6961
6962/** @import AudioTrack from './audio-track' */
6963
6964/**
6965 * Anywhere we call this function we diverge from the spec
6966 * as we only support one enabled audiotrack at a time
6967 *
6968 * @param {AudioTrackList} list
6969 *        list to work on
6970 *
6971 * @param {AudioTrack} track
6972 *        The track to skip
6973 *
6974 * @private
6975 */
6976const disableOthers$1 = function (list, track) {
6977  for (let i = 0; i < list.length; i++) {
6978    if (!Object.keys(list[i]).length || track.id === list[i].id) {
6979      continue;
6980    }
6981    // another audio track is enabled, disable it
6982    list[i].enabled = false;
vendor: 12,922 bytes, lines 6983-7508
6983  }
6984};
6985
6986/**
6987 * The current list of {@link AudioTrack} for a media file.
6988 *
6989 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6990 * @extends TrackList
6991 */
6992class AudioTrackList extends TrackList {
6993  /**
6994   * Create an instance of this class.
6995   *
6996   * @param {AudioTrack[]} [tracks=[]]
6997   *        A list of `AudioTrack` to instantiate the list with.
6998   */
6999  constructor(tracks = []) {
7000    // make sure only 1 track is enabled
7001    // sorted from last index to first index
7002    for (let i = tracks.length - 1; i >= 0; i--) {
7003      if (tracks[i].enabled) {
7004        disableOthers$1(tracks, tracks[i]);
7005        break;
7006      }
7007    }
7008    super(tracks);
7009    this.changing_ = false;
7010  }
7011
7012  /**
7013   * Add an {@link AudioTrack} to the `AudioTrackList`.
7014   *
7015   * @param {AudioTrack} track
7016   *        The AudioTrack to add to the list
7017   *
7018   * @fires TrackList#addtrack
7019   */
7020  addTrack(track) {
7021    if (track.enabled) {
7022      disableOthers$1(this, track);
7023    }
7024    super.addTrack(track);
7025    // native tracks don't have this
7026    if (!track.addEventListener) {
7027      return;
7028    }
7029    track.enabledChange_ = () => {
7030      // when we are disabling other tracks (since we don't support
7031      // more than one track at a time) we will set changing_
7032      // to true so that we don't trigger additional change events
7033      if (this.changing_) {
7034        return;
7035      }
7036      this.changing_ = true;
7037      disableOthers$1(this, track);
7038      this.changing_ = false;
7039      this.trigger('change');
7040    };
7041
7042    /**
7043     * @listens AudioTrack#enabledchange
7044     * @fires TrackList#change
7045     */
7046    track.addEventListener('enabledchange', track.enabledChange_);
7047  }
7048  removeTrack(rtrack) {
7049    super.removeTrack(rtrack);
7050    if (rtrack.removeEventListener && rtrack.enabledChange_) {
7051      rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
7052      rtrack.enabledChange_ = null;
7053    }
7054  }
7055}
7056
7057/**
7058 * @file video-track-list.js
7059 */
7060
7061/** @import VideoTrack from './video-track' */
7062
7063/**
7064 * Un-select all other {@link VideoTrack}s that are selected.
7065 *
7066 * @param {VideoTrackList} list
7067 *        list to work on
7068 *
7069 * @param {VideoTrack} track
7070 *        The track to skip
7071 *
7072 * @private
7073 */
7074const disableOthers = function (list, track) {
7075  for (let i = 0; i < list.length; i++) {
7076    if (!Object.keys(list[i]).length || track.id === list[i].id) {
7077      continue;
7078    }
7079    // another video track is enabled, disable it
7080    list[i].selected = false;
7081  }
7082};
7083
7084/**
7085 * The current list of {@link VideoTrack} for a video.
7086 *
7087 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
7088 * @extends TrackList
7089 */
7090class VideoTrackList extends TrackList {
7091  /**
7092   * Create an instance of this class.
7093   *
7094   * @param {VideoTrack[]} [tracks=[]]
7095   *        A list of `VideoTrack` to instantiate the list with.
7096   */
7097  constructor(tracks = []) {
7098    // make sure only 1 track is enabled
7099    // sorted from last index to first index
7100    for (let i = tracks.length - 1; i >= 0; i--) {
7101      if (tracks[i].selected) {
7102        disableOthers(tracks, tracks[i]);
7103        break;
7104      }
7105    }
7106    super(tracks);
7107    this.changing_ = false;
7108
7109    /**
7110     * @member {number} VideoTrackList#selectedIndex
7111     *         The current index of the selected {@link VideoTrack`}.
7112     */
7113    Object.defineProperty(this, 'selectedIndex', {
7114      get() {
7115        for (let i = 0; i < this.length; i++) {
7116          if (this[i].selected) {
7117            return i;
7118          }
7119        }
7120        return -1;
7121      },
7122      set() {},
7123    });
7124  }
7125
7126  /**
7127   * Add a {@link VideoTrack} to the `VideoTrackList`.
7128   *
7129   * @param {VideoTrack} track
7130   *        The VideoTrack to add to the list
7131   *
7132   * @fires TrackList#addtrack
7133   */
7134  addTrack(track) {
7135    if (track.selected) {
7136      disableOthers(this, track);
7137    }
7138    super.addTrack(track);
7139    // native tracks don't have this
7140    if (!track.addEventListener) {
7141      return;
7142    }
7143    track.selectedChange_ = () => {
7144      if (this.changing_) {
7145        return;
7146      }
7147      this.changing_ = true;
7148      disableOthers(this, track);
7149      this.changing_ = false;
7150      this.trigger('change');
7151    };
7152
7153    /**
7154     * @listens VideoTrack#selectedchange
7155     * @fires TrackList#change
7156     */
7157    track.addEventListener('selectedchange', track.selectedChange_);
7158  }
7159  removeTrack(rtrack) {
7160    super.removeTrack(rtrack);
7161    if (rtrack.removeEventListener && rtrack.selectedChange_) {
7162      rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
7163      rtrack.selectedChange_ = null;
7164    }
7165  }
7166}
7167
7168/**
7169 * @file text-track-list.js
7170 */
7171
7172/** @import TextTrack from './text-track' */
7173
7174/**
7175 * The current list of {@link TextTrack} for a media file.
7176 *
7177 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
7178 * @extends TrackList
7179 */
7180class TextTrackList extends TrackList {
7181  /**
7182   * Add a {@link TextTrack} to the `TextTrackList`
7183   *
7184   * @param {TextTrack} track
7185   *        The text track to add to the list.
7186   *
7187   * @fires TrackList#addtrack
7188   */
7189  addTrack(track) {
7190    super.addTrack(track);
7191    if (!this.queueChange_) {
7192      this.queueChange_ = () => this.queueTrigger('change');
7193    }
7194    if (!this.triggerSelectedlanguagechange) {
7195      this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
7196    }
7197
7198    /**
7199     * @listens TextTrack#modechange
7200     * @fires TrackList#change
7201     */
7202    track.addEventListener('modechange', this.queueChange_);
7203    const nonLanguageTextTrackKind = ['metadata', 'chapters'];
7204    if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
7205      track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
7206    }
7207  }
7208  removeTrack(rtrack) {
7209    super.removeTrack(rtrack);
7210
7211    // manually remove the event handlers we added
7212    if (rtrack.removeEventListener) {
7213      if (this.queueChange_) {
7214        rtrack.removeEventListener('modechange', this.queueChange_);
7215      }
7216      if (this.selectedlanguagechange_) {
7217        rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
7218      }
7219    }
7220  }
7221}
7222
7223/**
7224 * @file html-track-element-list.js
7225 */
7226
7227/**
7228 * The current list of {@link HtmlTrackElement}s.
7229 */
7230class HtmlTrackElementList {
7231  /**
7232   * Create an instance of this class.
7233   *
7234   * @param {HtmlTrackElement[]} [tracks=[]]
7235   *        A list of `HtmlTrackElement` to instantiate the list with.
7236   */
7237  constructor(trackElements = []) {
7238    this.trackElements_ = [];
7239
7240    /**
7241     * @memberof HtmlTrackElementList
7242     * @member {number} length
7243     *         The current number of `Track`s in the this Trackist.
7244     * @instance
7245     */
7246    Object.defineProperty(this, 'length', {
7247      get() {
7248        return this.trackElements_.length;
7249      },
7250    });
7251    for (let i = 0, length = trackElements.length; i < length; i++) {
7252      this.addTrackElement_(trackElements[i]);
7253    }
7254  }
7255
7256  /**
7257   * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
7258   *
7259   * @param {HtmlTrackElement} trackElement
7260   *        The track element to add to the list.
7261   *
7262   * @private
7263   */
7264  addTrackElement_(trackElement) {
7265    const index = this.trackElements_.length;
7266    if (!('' + index in this)) {
7267      Object.defineProperty(this, index, {
7268        get() {
7269          return this.trackElements_[index];
7270        },
7271      });
7272    }
7273
7274    // Do not add duplicate elements
7275    if (this.trackElements_.indexOf(trackElement) === -1) {
7276      this.trackElements_.push(trackElement);
7277    }
7278  }
7279
7280  /**
7281   * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
7282   * {@link TextTrack}.
7283   *
7284   * @param {TextTrack} track
7285   *        The track associated with a track element.
7286   *
7287   * @return {HtmlTrackElement|undefined}
7288   *         The track element that was found or undefined.
7289   *
7290   * @private
7291   */
7292  getTrackElementByTrack_(track) {
7293    let trackElement_;
7294    for (let i = 0, length = this.trackElements_.length; i < length; i++) {
7295      if (track === this.trackElements_[i].track) {
7296        trackElement_ = this.trackElements_[i];
7297        break;
7298      }
7299    }
7300    return trackElement_;
7301  }
7302
7303  /**
7304   * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
7305   *
7306   * @param {HtmlTrackElement} trackElement
7307   *        The track element to remove from the list.
7308   *
7309   * @private
7310   */
7311  removeTrackElement_(trackElement) {
7312    for (let i = 0, length = this.trackElements_.length; i < length; i++) {
7313      if (trackElement === this.trackElements_[i]) {
7314        if (
7315          this.trackElements_[i].track &&
7316          typeof this.trackElements_[i].track.off === 'function'
7317        ) {
7318          this.trackElements_[i].track.off();
7319        }
7320        if (typeof this.trackElements_[i].off === 'function') {
7321          this.trackElements_[i].off();
7322        }
7323        this.trackElements_.splice(i, 1);
7324        break;
7325      }
7326    }
7327  }
7328}
7329
7330/**
7331 * @file text-track-cue-list.js
7332 */
7333
7334/**
7335 * @typedef {Object} TextTrackCueList~TextTrackCue
7336 *
7337 * @property {string} id
7338 *           The unique id for this text track cue
7339 *
7340 * @property {number} startTime
7341 *           The start time for this text track cue
7342 *
7343 * @property {number} endTime
7344 *           The end time for this text track cue
7345 *
7346 * @property {boolean} pauseOnExit
7347 *           Pause when the end time is reached if true.
7348 *
7349 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
7350 */
7351
7352/**
7353 * A List of TextTrackCues.
7354 *
7355 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
7356 */
7357class TextTrackCueList {
7358  /**
7359   * Create an instance of this class..
7360   *
7361   * @param {Array} cues
7362   *        A list of cues to be initialized with
7363   */
7364  constructor(cues) {
7365    TextTrackCueList.prototype.setCues_.call(this, cues);
7366
7367    /**
7368     * @memberof TextTrackCueList
7369     * @member {number} length
7370     *         The current number of `TextTrackCue`s in the TextTrackCueList.
7371     * @instance
7372     */
7373    Object.defineProperty(this, 'length', {
7374      get() {
7375        return this.length_;
7376      },
7377    });
7378  }
7379
7380  /**
7381   * A setter for cues in this list. Creates getters
7382   * an an index for the cues.
7383   *
7384   * @param {Array} cues
7385   *        An array of cues to set
7386   *
7387   * @private
7388   */
7389  setCues_(cues) {
7390    const oldLength = this.length || 0;
7391    let i = 0;
7392    const l = cues.length;
7393    this.cues_ = cues;
7394    this.length_ = cues.length;
7395    const defineProp = function (index) {
7396      if (!('' + index in this)) {
7397        Object.defineProperty(this, '' + index, {
7398          get() {
7399            return this.cues_[index];
7400          },
7401        });
7402      }
7403    };
7404    if (oldLength < l) {
7405      i = oldLength;
7406      for (; i < l; i++) {
7407        defineProp.call(this, i);
7408      }
7409    }
7410  }
7411
7412  /**
7413   * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
7414   *
7415   * @param {string} id
7416   *        The id of the cue that should be searched for.
7417   *
7418   * @return {TextTrackCueList~TextTrackCue|null}
7419   *         A single cue or null if none was found.
7420   */
7421  getCueById(id) {
7422    let result = null;
7423    for (let i = 0, l = this.length; i < l; i++) {
7424      const cue = this[i];
7425      if (cue.id === id) {
7426        result = cue;
7427        break;
7428      }
7429    }
7430    return result;
7431  }
7432}
7433
7434/**
7435 * @file track-kinds.js
7436 */
7437
7438/**
7439 * All possible `VideoTrackKind`s
7440 *
7441 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7442 * @typedef VideoTrack~Kind
7443 * @enum
7444 */
7445const VideoTrackKind = {
7446  alternative: 'alternative',
7447  captions: 'captions',
7448  main: 'main',
7449  sign: 'sign',
7450  subtitles: 'subtitles',
7451  commentary: 'commentary',
7452};
7453
7454/**
7455 * All possible `AudioTrackKind`s
7456 *
7457 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7458 * @typedef AudioTrack~Kind
7459 * @enum
7460 */
7461const AudioTrackKind = {
7462  alternative: 'alternative',
7463  descriptions: 'descriptions',
7464  main: 'main',
7465  'main-desc': 'main-desc',
7466  translation: 'translation',
7467  commentary: 'commentary',
7468};
7469
7470/**
7471 * All possible `TextTrackKind`s
7472 *
7473 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7474 * @typedef TextTrack~Kind
7475 * @enum
7476 */
7477const TextTrackKind = {
7478  subtitles: 'subtitles',
7479  captions: 'captions',
7480  descriptions: 'descriptions',
7481  chapters: 'chapters',
7482  metadata: 'metadata',
7483};
7484
7485/**
7486 * All possible `TextTrackMode`s
7487 *
7488 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7489 * @typedef TextTrack~Mode
7490 * @enum
7491 */
7492const TextTrackMode = {
7493  disabled: 'disabled',
7494  hidden: 'hidden',
7495  showing: 'showing',
7496};
7497
7498/**
7499 * @file track.js
7500 */
7501
7502/**
7503 * A Track class that contains all of the common functionality for {@link AudioTrack},
7504 * {@link VideoTrack}, and {@link TextTrack}.
7505 *
7506 * > Note: This class should not be used directly
7507 *
7508 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7509 * @extends EventTarget
7510 * @abstract
7511 */
7512class Track extends EventTarget$2 {
7513  /**
7514   * Create an instance of this class.
7515   *
7516   * @param {Object} [options={}]
7517   *        Object of option names and values
7518   *
7519   * @param {string} [options.kind='']
7520   *        A valid kind for the track type you are creating.
7521   *
7522   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7523   *        A unique id for this AudioTrack.
7524   *
7525   * @param {string} [options.label='']
7526   *        The menu label for this track.
7527   *
7528   * @param {string} [options.language='']
7529   *        A valid two character language code.
7530   *
7531   * @abstract
7532   */
7533  constructor(options = {}) {
7534    super();
7535    const trackProps = {
7536      id: options.id || 'vjs_track_' + newGUID(),
7537      kind: options.kind || '',
7538      language: options.language || '',
7539    };
7540    let label = options.label || '';
7541
7542    /**
7543     * @memberof Track
7544     * @member {string} id
7545     *         The id of this track. Cannot be changed after creation.
7546     * @instance
7547     *
7548     * @readonly
7549     */
7550
7551    /**
7552     * @memberof Track
7553     * @member {string} kind
7554     *         The kind of track that this is. Cannot be changed after creation.
7555     * @instance
7556     *
7557     * @readonly
7558     */
7559
7560    /**
7561     * @memberof Track
7562     * @member {string} language
7563     *         The two letter language code for this track. Cannot be changed after
7564     *         creation.
7565     * @instance
7566     *
7567     * @readonly
7568     */
7569
7570    for (const key in trackProps) {
7571      Object.defineProperty(this, key, {
7572        get() {
7573          return trackProps[key];
7574        },
7575        set() {},
7576      });
7577    }
7578
7579    /**
7580     * @memberof Track
7581     * @member {string} label
7582     *         The label of this track. Cannot be changed after creation.
7583     * @instance
7584     *
7585     * @fires Track#labelchange
7586     */
7587    Object.defineProperty(this, 'label', {
7588      get() {
7589        return label;
7590      },
7591      set(newLabel) {
7592        if (newLabel !== label) {
7593          label = newLabel;
7594
7595          /**
7596           * An event that fires when label changes on this track.
7597           *
7598           * > Note: This is not part of the spec!
7599           *
7600           * @event Track#labelchange
7601           * @type {Event}
7602           */
7603          this.trigger('labelchange');
7604        }
7605      },
7606    });
7607  }
7608}
7609
7610/**
7611 * @file url.js
7612 * @module url
7613 */
7614
7615/**
7616 * Resolve and parse the elements of a URL.
7617 *
7618 * @function
7619 * @param    {string} url
7620 *           The url to parse
7621 *
7622 * @return   {URL}
7623 *           An object of url details
7624 */
7625const parseUrl = function (url) {
7626  return new URL(url, document__default['default'].baseURI);
7627};
7628
7629/**
7630 * Get absolute version of relative URL.
7631 *
7632 * @function
7633 * @param    {string} url
7634 *           URL to make absolute
7635 *
7636 * @return   {string}
7637 *           Absolute URL
7638 */
7639const getAbsoluteURL = function (url) {
7640  return new URL(url, document__default['default'].baseURI).href;
7641};
7642
7643/**
7644 * Returns the extension of the passed file name. It will return an empty string
7645 * if passed an invalid path.
7646 *
7647 * @function
7648 * @param    {string} path
7649 *           The fileName path like '/path/to/file.mp4'
7650 *
7651 * @return  {string}
7652 *           The extension in lower case or an empty string if no
7653 *           extension could be found.
7654 */
7655const getFileExtension = function (path) {
7656  if (typeof path === 'string') {
7657    const splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7658    const pathParts = splitPathRe.exec(path);
7659    if (pathParts) {
7660      return pathParts.pop().toLowerCase();
7661    }
7662  }
7663  return '';
7664};
7665
7666/**
7667 * Returns whether the url passed is a cross domain request or not.
7668 *
7669 * @function
7670 * @param    {string} url
7671 *           The url to check.
7672 *
7673 * @param    {URL} [winLoc]
7674 *           the domain to check the url against, defaults to window.location
7675 *
7676 * @return   {boolean}
7677 *           Whether it is a cross domain request or not.
7678 */
7679const isCrossOrigin = function (url, winLoc = window__default['default'].location) {
7680  return parseUrl(url).origin !== winLoc.origin;
7681};
7682
7683var Url = /*#__PURE__*/ Object.freeze({
7684  __proto__: null,
7685  parseUrl: parseUrl,
7686  getAbsoluteURL: getAbsoluteURL,
7687  getFileExtension: getFileExtension,
7688  isCrossOrigin: isCrossOrigin,
7689});
7690
7691/**
7692 * @file text-track.js
7693 */
7694
7695/** @import Tech from '../tech/tech' */
7696
7697/**
7698 * Takes a webvtt file contents and parses it into cues
7699 *
7700 * @param {string} srcContent
7701 *        webVTT file contents
7702 *
7703 * @param {TextTrack} track
7704 *        TextTrack to add cues to. Cues come from the srcContent.
7705 *
7706 * @private
7707 */
7708const parseCues = function (srcContent, track) {
7709  const parser = new window__default['default'].WebVTT.Parser(
7710    window__default['default'],
7711    window__default['default'].vttjs,
7712    window__default['default'].WebVTT.StringDecoder()
7713  );
7714  const errors = [];
7715  parser.oncue = function (cue) {
7716    track.addCue(cue);
7717  };
7718  parser.onparsingerror = function (error) {
7719    errors.push(error);
7720  };
7721  parser.onflush = function () {
7722    track.trigger({
7723      type: 'loadeddata',
7724      target: track,
7725    });
7726  };
7727  parser.parse(srcContent);
7728  if (errors.length > 0) {
7729    if (window__default['default'].console && window__default['default'].console.groupCollapsed) {
7730      window__default['default'].console.groupCollapsed(
7731        `Text Track parsing errors for ${track.src}`
7732      );
7733    }
vendor: 4,270 bytes, lines 7734-7869
7734    errors.forEach((error) => log$1.error(error));
7735    if (window__default['default'].console && window__default['default'].console.groupEnd) {
7736      window__default['default'].console.groupEnd();
7737    }
7738  }
7739  parser.flush();
7740};
7741
7742/**
7743 * Load a `TextTrack` from a specified url.
7744 *
7745 * @param {string} src
7746 *        Url to load track from.
7747 *
7748 * @param {TextTrack} track
7749 *        Track to add cues to. Comes from the content at the end of `url`.
7750 *
7751 * @private
7752 */
7753const loadTrack = function (src, track) {
7754  const opts = {
7755    uri: src,
7756  };
7757  const crossOrigin = isCrossOrigin(src);
7758  if (crossOrigin) {
7759    opts.cors = crossOrigin;
7760  }
7761  const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
7762  if (withCredentials) {
7763    opts.withCredentials = withCredentials;
7764  }
7765  XHR__default['default'](
7766    opts,
7767    bind_(this, function (err, response, responseBody) {
7768      if (err) {
7769        return log$1.error(err, response);
7770      }
7771      track.loaded_ = true;
7772
7773      // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7774      // NOTE: this is only used for the alt/video.novtt.js build
7775      if (typeof window__default['default'].WebVTT !== 'function') {
7776        if (track.tech_) {
7777          // to prevent use before define eslint error, we define loadHandler
7778          // as a let here
7779          track.tech_.any(['vttjsloaded', 'vttjserror'], (event) => {
7780            if (event.type === 'vttjserror') {
7781              log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
7782              return;
7783            }
7784            return parseCues(responseBody, track);
7785          });
7786        }
7787      } else {
7788        parseCues(responseBody, track);
7789      }
7790    })
7791  );
7792};
7793
7794/**
7795 * A representation of a single `TextTrack`.
7796 *
7797 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7798 * @extends Track
7799 */
7800class TextTrack extends Track {
7801  /**
7802   * Create an instance of this class.
7803   *
7804   * @param {Object} options={}
7805   *        Object of option names and values
7806   *
7807   * @param {Tech} options.tech
7808   *        A reference to the tech that owns this TextTrack.
7809   *
7810   * @param {TextTrack~Kind} [options.kind='subtitles']
7811   *        A valid text track kind.
7812   *
7813   * @param {TextTrack~Mode} [options.mode='disabled']
7814   *        A valid text track mode.
7815   *
7816   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7817   *        A unique id for this TextTrack.
7818   *
7819   * @param {string} [options.label='']
7820   *        The menu label for this track.
7821   *
7822   * @param {string} [options.language='']
7823   *        A valid two character language code.
7824   *
7825   * @param {string} [options.srclang='']
7826   *        A valid two character language code. An alternative, but deprioritized
7827   *        version of `options.language`
7828   *
7829   * @param {string} [options.src]
7830   *        A url to TextTrack cues.
7831   *
7832   * @param {boolean} [options.default]
7833   *        If this track should default to on or off.
7834   */
7835  constructor(options = {}) {
7836    if (!options.tech) {
7837      throw new Error('A tech was not provided.');
7838    }
7839    const settings = merge$1(options, {
7840      kind: TextTrackKind[options.kind] || 'subtitles',
7841      language: options.language || options.srclang || '',
7842    });
7843    let mode = TextTrackMode[settings.mode] || 'disabled';
7844    const default_ = settings.default;
7845    if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7846      mode = 'hidden';
7847    }
7848    super(settings);
7849    this.tech_ = settings.tech;
7850    this.cues_ = [];
7851    this.activeCues_ = [];
7852    this.preload_ = this.tech_.preloadTextTracks !== false;
7853    const cues = new TextTrackCueList(this.cues_);
7854    const activeCues = new TextTrackCueList(this.activeCues_);
7855    let changed = false;
7856    this.timeupdateHandler = bind_(this, function (event = {}) {
7857      if (this.tech_.isDisposed()) {
7858        return;
7859      }
7860      if (!this.tech_.isReady_) {
7861        if (event.type !== 'timeupdate') {
7862          this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7863        }
7864        return;
7865      }
7866
7867      // Accessing this.activeCues for the side-effects of updating itself
7868      // due to its nature as a getter function. Do not remove or cues will
7869      // stop updating!
vendor: 4,823 bytes, lines 7870-8035
7870      // Use the setter to prevent deletion from uglify (pure_getters rule)
7871      this.activeCues = this.activeCues;
7872      if (changed) {
7873        this.trigger('cuechange');
7874        changed = false;
7875      }
7876      if (event.type !== 'timeupdate') {
7877        this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7878      }
7879    });
7880    const disposeHandler = () => {
7881      this.stopTracking();
7882    };
7883    this.tech_.one('dispose', disposeHandler);
7884    if (mode !== 'disabled') {
7885      this.startTracking();
7886    }
7887    Object.defineProperties(this, {
7888      /**
7889       * @memberof TextTrack
7890       * @member {boolean} default
7891       *         If this track was set to be on or off by default. Cannot be changed after
7892       *         creation.
7893       * @instance
7894       *
7895       * @readonly
7896       */
7897      default: {
7898        get() {
7899          return default_;
7900        },
7901        set() {},
7902      },
7903      /**
7904       * @memberof TextTrack
7905       * @member {string} mode
7906       *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7907       *         not be set if setting to an invalid mode.
7908       * @instance
7909       *
7910       * @fires TextTrack#modechange
7911       */
7912      mode: {
7913        get() {
7914          return mode;
7915        },
7916        set(newMode) {
7917          if (!TextTrackMode[newMode]) {
7918            return;
7919          }
7920          if (mode === newMode) {
7921            return;
7922          }
7923          mode = newMode;
7924          if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
7925            // On-demand load.
7926            loadTrack(this.src, this);
7927          }
7928          this.stopTracking();
7929          if (mode !== 'disabled') {
7930            this.startTracking();
7931          }
7932          /**
7933           * An event that fires when mode changes on this track. This allows
7934           * the TextTrackList that holds this track to act accordingly.
7935           *
7936           * > Note: This is not part of the spec!
7937           *
7938           * @event TextTrack#modechange
7939           * @type {Event}
7940           */
7941          this.trigger('modechange');
7942        },
7943      },
7944      /**
7945       * @memberof TextTrack
7946       * @member {TextTrackCueList} cues
7947       *         The text track cue list for this TextTrack.
7948       * @instance
7949       */
7950      cues: {
7951        get() {
7952          if (!this.loaded_) {
7953            return null;
7954          }
7955          return cues;
7956        },
7957        set() {},
7958      },
7959      /**
7960       * @memberof TextTrack
7961       * @member {TextTrackCueList} activeCues
7962       *         The list text track cues that are currently active for this TextTrack.
7963       * @instance
7964       */
7965      activeCues: {
7966        get() {
7967          if (!this.loaded_) {
7968            return null;
7969          }
7970
7971          // nothing to do
7972          if (this.cues.length === 0) {
7973            return activeCues;
7974          }
7975          const ct = this.tech_.currentTime();
7976          const active = [];
7977          for (let i = 0, l = this.cues.length; i < l; i++) {
7978            const cue = this.cues[i];
7979            if (cue.startTime <= ct && cue.endTime >= ct) {
7980              active.push(cue);
7981            }
7982          }
7983          changed = false;
7984          if (active.length !== this.activeCues_.length) {
7985            changed = true;
7986          } else {
7987            for (let i = 0; i < active.length; i++) {
7988              if (this.activeCues_.indexOf(active[i]) === -1) {
7989                changed = true;
7990              }
7991            }
7992          }
7993          this.activeCues_ = active;
7994          activeCues.setCues_(this.activeCues_);
7995          return activeCues;
7996        },
7997        // /!\ Keep this setter empty (see the timeupdate handler above)
7998        set() {},
7999      },
8000    });
8001    if (settings.src) {
8002      this.src = settings.src;
8003      if (!this.preload_) {
8004        // Tracks will load on-demand.
8005        // Act like we're loaded for other purposes.
8006        this.loaded_ = true;
8007      }
8008      if (this.preload_ || (settings.kind !== 'subtitles' && settings.kind !== 'captions')) {
8009        loadTrack(this.src, this);
8010      }
8011    } else {
8012      this.loaded_ = true;
8013    }
8014  }
8015  startTracking() {
8016    // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
8017    this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
8018    // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
8019    this.tech_.on('timeupdate', this.timeupdateHandler);
8020  }
8021  stopTracking() {
8022    if (this.rvf_) {
8023      this.tech_.cancelVideoFrameCallback(this.rvf_);
8024      this.rvf_ = undefined;
8025    }
8026    this.tech_.off('timeupdate', this.timeupdateHandler);
8027  }
8028
8029  /**
8030   * Add a cue to the internal list of cues.
8031   *
8032   * @param {TextTrack~Cue} cue
8033   *        The cue to add to our internal list
8034   */
8035  addCue(originalCue) {
8036    let cue = originalCue;
8037
8038    // Testing if the cue is a VTTCue in a way that survives minification
8039    if (!('getCueAsHTML' in cue)) {
8040      cue = new window__default['default'].vttjs.VTTCue(
8041        originalCue.startTime,
8042        originalCue.endTime,
8043        originalCue.text
8044      );
8045      for (const prop in originalCue) {
8046        if (!(prop in cue)) {
8047          cue[prop] = originalCue[prop];
8048        }
8049      }
8050
8051      // make sure that `id` is copied over
8052      cue.id = originalCue.id;
8053      cue.originalCue_ = originalCue;
8054    }
8055    const tracks = this.tech_.textTracks();
8056    for (let i = 0; i < tracks.length; i++) {
8057      if (tracks[i] !== this) {
8058        tracks[i].removeCue(cue);
8059      }
8060    }
8061    this.cues_.push(cue);
8062    this.cues.setCues_(this.cues_);
8063  }
8064
8065  /**
8066   * Remove a cue from our internal list
8067   *
8068   * @param {TextTrack~Cue} removeCue
8069   *        The cue to remove from our internal list
8070   */
8071  removeCue(removeCue) {
8072    let i = this.cues_.length;
8073    while (i--) {
8074      const cue = this.cues_[i];
8075      if (cue === removeCue || (cue.originalCue_ && cue.originalCue_ === removeCue)) {
8076        this.cues_.splice(i, 1);
8077        this.cues.setCues_(this.cues_);
8078        break;
8079      }
8080    }
8081  }
8082}
8083
8084/**
8085 * cuechange - One or more cues in the track have become active or stopped being active.
8086 *
8087 * @protected
8088 */
8089TextTrack.prototype.allowedEvents_ = {
8090  cuechange: 'cuechange',
8091};
8092
8093/**
8094 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8095 * only one `AudioTrack` in the list will be enabled at a time.
8096 *
8097 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8098 * @extends Track
8099 */
8100class AudioTrack extends Track {
8101  /**
8102   * Create an instance of this class.
8103   *
8104   * @param {Object} [options={}]
8105   *        Object of option names and values
8106   *
8107   * @param {AudioTrack~Kind} [options.kind='']
8108   *        A valid audio track kind
8109   *
8110   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8111   *        A unique id for this AudioTrack.
8112   *
8113   * @param {string} [options.label='']
8114   *        The menu label for this track.
8115   *
8116   * @param {string} [options.language='']
8117   *        A valid two character language code.
8118   *
8119   * @param {boolean} [options.enabled]
8120   *        If this track is the one that is currently playing. If this track is part of
8121   *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8122   */
8123  constructor(options = {}) {
8124    const settings = merge$1(options, {
8125      kind: AudioTrackKind[options.kind] || '',
8126    });
8127    super(settings);
8128    let enabled = false;
8129
8130    /**
8131     * @memberof AudioTrack
8132     * @member {boolean} enabled
8133     *         If this `AudioTrack` is enabled or not. When setting this will
8134     *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8135     * @instance
8136     *
8137     * @fires VideoTrack#selectedchange
8138     */
8139    Object.defineProperty(this, 'enabled', {
8140      get() {
8141        return enabled;
8142      },
8143      set(newEnabled) {
8144        // an invalid or unchanged value
8145        if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8146          return;
8147        }
8148        enabled = newEnabled;
8149
8150        /**
8151         * An event that fires when enabled changes on this track. This allows
8152         * the AudioTrackList that holds this track to act accordingly.
8153         *
8154         * > Note: This is not part of the spec! Native tracks will do
8155         *         this internally without an event.
8156         *
8157         * @event AudioTrack#enabledchange
8158         * @type {Event}
8159         */
8160        this.trigger('enabledchange');
8161      },
8162    });
8163
8164    // if the user sets this track to selected then
8165    // set selected to that true value otherwise
8166    // we keep it false
8167    if (settings.enabled) {
8168      this.enabled = settings.enabled;
8169    }
8170    this.loaded_ = true;
8171  }
8172}
8173
8174/**
8175 * A representation of a single `VideoTrack`.
8176 *
8177 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8178 * @extends Track
8179 */
8180class VideoTrack extends Track {
8181  /**
8182   * Create an instance of this class.
8183   *
8184   * @param {Object} [options={}]
8185   *        Object of option names and values
8186   *
8187   * @param {string} [options.kind='']
8188   *        A valid {@link VideoTrack~Kind}
8189   *
8190   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8191   *        A unique id for this AudioTrack.
8192   *
8193   * @param {string} [options.label='']
8194   *        The menu label for this track.
8195   *
8196   * @param {string} [options.language='']
8197   *        A valid two character language code.
8198   *
8199   * @param {boolean} [options.selected]
8200   *        If this track is the one that is currently playing.
8201   */
8202  constructor(options = {}) {
8203    const settings = merge$1(options, {
8204      kind: VideoTrackKind[options.kind] || '',
8205    });
8206    super(settings);
8207    let selected = false;
8208
8209    /**
8210     * @memberof VideoTrack
8211     * @member {boolean} selected
8212     *         If this `VideoTrack` is selected or not. When setting this will
8213     *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8214     * @instance
8215     *
8216     * @fires VideoTrack#selectedchange
8217     */
8218    Object.defineProperty(this, 'selected', {
8219      get() {
8220        return selected;
8221      },
8222      set(newSelected) {
8223        // an invalid or unchanged value
8224        if (typeof newSelected !== 'boolean' || newSelected === selected) {
8225          return;
8226        }
8227        selected = newSelected;
8228
8229        /**
8230         * An event that fires when selected changes on this track. This allows
8231         * the VideoTrackList that holds this track to act accordingly.
8232         *
8233         * > Note: This is not part of the spec! Native tracks will do
8234         *         this internally without an event.
8235         *
8236         * @event VideoTrack#selectedchange
8237         * @type {Event}
8238         */
8239        this.trigger('selectedchange');
8240      },
8241    });
8242
8243    // if the user sets this track to selected then
8244    // set selected to that true value otherwise
8245    // we keep it false
8246    if (settings.selected) {
8247      this.selected = settings.selected;
8248    }
8249  }
8250}
8251
8252/**
8253 * @file html-track-element.js
8254 */
8255
8256/** @import Tech from '../tech/tech' */
8257
8258/**
8259 * A single track represented in the DOM.
8260 *
8261 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8262 * @extends EventTarget
8263 */
8264class HTMLTrackElement extends EventTarget$2 {
8265  /**
8266   * Create an instance of this class.
8267   *
8268   * @param {Object} options={}
8269   *        Object of option names and values
8270   *
8271   * @param {Tech} options.tech
8272   *        A reference to the tech that owns this HTMLTrackElement.
8273   *
8274   * @param {TextTrack~Kind} [options.kind='subtitles']
8275   *        A valid text track kind.
8276   *
8277   * @param {TextTrack~Mode} [options.mode='disabled']
8278   *        A valid text track mode.
8279   *
8280   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8281   *        A unique id for this TextTrack.
8282   *
8283   * @param {string} [options.label='']
8284   *        The menu label for this track.
8285   *
8286   * @param {string} [options.language='']
8287   *        A valid two character language code.
8288   *
8289   * @param {string} [options.srclang='']
8290   *        A valid two character language code. An alternative, but deprioritized
8291   *        version of `options.language`
8292   *
8293   * @param {string} [options.src]
8294   *        A url to TextTrack cues.
8295   *
8296   * @param {boolean} [options.default]
8297   *        If this track should default to on or off.
8298   */
8299  constructor(options = {}) {
8300    super();
8301    let readyState;
8302    const track = new TextTrack(options);
8303    this.kind = track.kind;
8304    this.src = track.src;
8305    this.srclang = track.language;
8306    this.label = track.label;
8307    this.default = track.default;
8308    Object.defineProperties(this, {
8309      /**
8310       * @memberof HTMLTrackElement
8311       * @member {HTMLTrackElement~ReadyState} readyState
8312       *         The current ready state of the track element.
8313       * @instance
8314       */
8315      readyState: {
8316        get() {
8317          return readyState;
8318        },
8319      },
8320      /**
8321       * @memberof HTMLTrackElement
8322       * @member {TextTrack} track
8323       *         The underlying TextTrack object.
8324       * @instance
8325       *
8326       */
8327      track: {
8328        get() {
8329          return track;
8330        },
8331      },
8332    });
8333    readyState = HTMLTrackElement.NONE;
8334
8335    /**
8336     * @listens TextTrack#loadeddata
8337     * @fires HTMLTrackElement#load
8338     */
8339    track.addEventListener('loadeddata', () => {
8340      readyState = HTMLTrackElement.LOADED;
8341      this.trigger({
8342        type: 'load',
8343        target: this,
8344      });
8345    });
8346  }
8347}
8348
8349/**
8350 * @protected
8351 */
8352HTMLTrackElement.prototype.allowedEvents_ = {
8353  load: 'load',
8354};
8355
8356/**
8357 * The text track not loaded state.
8358 *
8359 * @type {number}
8360 * @static
8361 */
8362HTMLTrackElement.NONE = 0;
8363
8364/**
8365 * The text track loading state.
8366 *
8367 * @type {number}
8368 * @static
8369 */
8370HTMLTrackElement.LOADING = 1;
8371
8372/**
8373 * The text track loaded state.
8374 *
8375 * @type {number}
8376 * @static
8377 */
8378HTMLTrackElement.LOADED = 2;
8379
8380/**
8381 * The text track failed to load state.
8382 *
8383 * @type {number}
8384 * @static
8385 */
8386HTMLTrackElement.ERROR = 3;
8387
8388/*
8389 * This file contains all track properties that are used in
8390 * player.js, tech.js, html5.js and possibly other techs in the future.
8391 */
8392
8393const NORMAL = {
8394  audio: {
8395    ListClass: AudioTrackList,
8396    TrackClass: AudioTrack,
8397    capitalName: 'Audio',
8398  },
8399  video: {
8400    ListClass: VideoTrackList,
8401    TrackClass: VideoTrack,
8402    capitalName: 'Video',
8403  },
8404  text: {
8405    ListClass: TextTrackList,
8406    TrackClass: TextTrack,
8407    capitalName: 'Text',
8408  },
8409};
8410Object.keys(NORMAL).forEach(function (type) {
8411  NORMAL[type].getterName = `${type}Tracks`;
8412  NORMAL[type].privateName = `${type}Tracks_`;
8413});
8414const REMOTE = {
8415  remoteText: {
8416    ListClass: TextTrackList,
8417    TrackClass: TextTrack,
8418    capitalName: 'RemoteText',
8419    getterName: 'remoteTextTracks',
8420    privateName: 'remoteTextTracks_',
8421  },
8422  remoteTextEl: {
8423    ListClass: HtmlTrackElementList,
8424    TrackClass: HTMLTrackElement,
8425    capitalName: 'RemoteTextTrackEls',
8426    getterName: 'remoteTextTrackEls',
8427    privateName: 'remoteTextTrackEls_',
8428  },
8429};
8430const ALL = Object.assign({}, NORMAL, REMOTE);
8431REMOTE.names = Object.keys(REMOTE);
8432NORMAL.names = Object.keys(NORMAL);
8433ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8434
8435/**
8436 * @file tech.js
8437 */
8438
8439/** @import { TimeRange } from '../utils/time' */
8440
8441/**
8442 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
8443 * that just contains the src url alone.
8444 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
8445 * `var SourceString = 'http://example.com/some-video.mp4';`
8446 *
8447 * @typedef {Object|string} SourceObject
8448 *
8449 * @property {string} src
8450 *           The url to the source
8451 *
8452 * @property {string} type
8453 *           The mime type of the source
8454 */
8455
8456/**
8457 * A function used by {@link Tech} to create a new {@link TextTrack}.
8458 *
8459 * @private
8460 *
8461 * @param {Tech} self
8462 *        An instance of the Tech class.
8463 *
8464 * @param {string} kind
8465 *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
8466 *
8467 * @param {string} [label]
8468 *        Label to identify the text track
8469 *
8470 * @param {string} [language]
8471 *        Two letter language abbreviation
8472 *
8473 * @param {Object} [options={}]
8474 *        An object with additional text track options
8475 *
8476 * @return {TextTrack}
8477 *          The text track that was created.
8478 */
8479function createTrackHelper(self, kind, label, language, options = {}) {
8480  const tracks = self.textTracks();
8481  options.kind = kind;
8482  if (label) {
8483    options.label = label;
8484  }
8485  if (language) {
8486    options.language = language;
8487  }
8488  options.tech = self;
8489  const track = new ALL.text.TrackClass(options);
8490  tracks.addTrack(track);
8491  return track;
8492}
8493
8494/**
8495 * This is the base class for media playback technology controllers, such as
8496 * {@link HTML5}
8497 *
8498 * @extends Component
8499 */
8500class Tech extends Component$1 {
8501  /**
8502   * Create an instance of this Tech.
8503   *
8504   * @param {Object} [options]
8505   *        The key/value store of player options.
8506   *
8507   * @param {Function} [ready]
8508   *        Callback function to call when the `HTML5` Tech is ready.
8509   */
8510  constructor(options = {}, ready = function () {}) {
8511    // we don't want the tech to report user activity automatically.
8512    // This is done manually in addControlsListeners
8513    options.reportTouchActivity = false;
8514    super(null, options, ready);
8515    this.onDurationChange_ = (e) => this.onDurationChange(e);
8516    this.trackProgress_ = (e) => this.trackProgress(e);
8517    this.trackCurrentTime_ = (e) => this.trackCurrentTime(e);
8518    this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
8519    this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);
8520    this.queuedHanders_ = new Set();
8521
8522    // keep track of whether the current source has played at all to
8523    // implement a very limited played()
8524    this.hasStarted_ = false;
8525    this.on('playing', function () {
8526      this.hasStarted_ = true;
8527    });
8528    this.on('loadstart', function () {
8529      this.hasStarted_ = false;
8530    });
8531    ALL.names.forEach((name) => {
8532      const props = ALL[name];
8533      if (options && options[props.getterName]) {
8534        this[props.privateName] = options[props.getterName];
8535      }
8536    });
8537
8538    // Manually track progress in cases where the browser/tech doesn't report it.
8539    if (!this.featuresProgressEvents) {
8540      this.manualProgressOn();
8541    }
8542
8543    // Manually track timeupdates in cases where the browser/tech doesn't report it.
8544    if (!this.featuresTimeupdateEvents) {
8545      this.manualTimeUpdatesOn();
8546    }
8547    ['Text', 'Audio', 'Video'].forEach((track) => {
8548      if (options[`native${track}Tracks`] === false) {
8549        this[`featuresNative${track}Tracks`] = false;
vendor: 5,078 bytes, lines 8550-8726
8550      }
8551    });
8552    if (options.nativeCaptions === false || options.nativeTextTracks === false) {
8553      this.featuresNativeTextTracks = false;
8554    } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
8555      this.featuresNativeTextTracks = true;
8556    }
8557    if (!this.featuresNativeTextTracks) {
8558      this.emulateTextTracks();
8559    }
8560    this.preloadTextTracks = options.preloadTextTracks !== false;
8561    this.autoRemoteTextTracks_ = new ALL.text.ListClass();
8562    this.initTrackListeners();
8563
8564    // Turn on component tap events only if not using native controls
8565    if (!options.nativeControlsForTouch) {
8566      this.emitTapEvents();
8567    }
8568    if (this.constructor) {
8569      this.name_ = this.constructor.name || 'Unknown Tech';
8570    }
8571  }
8572
8573  /**
8574   * A special function to trigger source set in a way that will allow player
8575   * to re-trigger if the player or tech are not ready yet.
8576   *
8577   * @fires Tech#sourceset
8578   * @param {string} src The source string at the time of the source changing.
8579   */
8580  triggerSourceset(src) {
8581    if (!this.isReady_) {
8582      // on initial ready we have to trigger source set
8583      // 1ms after ready so that player can watch for it.
8584      this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
8585    }
8586
8587    /**
8588     * Fired when the source is set on the tech causing the media element
8589     * to reload.
8590     *
8591     * @see {@link Player#event:sourceset}
8592     * @event Tech#sourceset
8593     * @type {Event}
8594     */
8595    this.trigger({
8596      src,
8597      type: 'sourceset',
8598    });
8599  }
8600
8601  /* Fallbacks for unsupported event types
8602  ================================================================================ */
8603
8604  /**
8605   * Polyfill the `progress` event for browsers that don't support it natively.
8606   *
8607   * @see {@link Tech#trackProgress}
8608   */
8609  manualProgressOn() {
8610    this.on('durationchange', this.onDurationChange_);
8611    this.manualProgress = true;
8612
8613    // Trigger progress watching when a source begins loading
8614    this.one('ready', this.trackProgress_);
8615  }
8616
8617  /**
8618   * Turn off the polyfill for `progress` events that was created in
8619   * {@link Tech#manualProgressOn}
8620   */
8621  manualProgressOff() {
8622    this.manualProgress = false;
8623    this.stopTrackingProgress();
8624    this.off('durationchange', this.onDurationChange_);
8625  }
8626
8627  /**
8628   * This is used to trigger a `progress` event when the buffered percent changes. It
8629   * sets an interval function that will be called every 500 milliseconds to check if the
8630   * buffer end percent has changed.
8631   *
8632   * > This function is called by {@link Tech#manualProgressOn}
8633   *
8634   * @param {Event} event
8635   *        The `ready` event that caused this to run.
8636   *
8637   * @listens Tech#ready
8638   * @fires Tech#progress
8639   */
8640  trackProgress(event) {
8641    this.stopTrackingProgress();
8642    this.progressInterval = this.setInterval(
8643      bind_(this, function () {
8644        // Don't trigger unless buffered amount is greater than last time
8645
8646        const numBufferedPercent = this.bufferedPercent();
8647        if (this.bufferedPercent_ !== numBufferedPercent) {
8648          /**
8649           * See {@link Player#progress}
8650           *
8651           * @event Tech#progress
8652           * @type {Event}
8653           */
8654          this.trigger('progress');
8655        }
8656        this.bufferedPercent_ = numBufferedPercent;
8657        if (numBufferedPercent === 1) {
8658          this.stopTrackingProgress();
8659        }
8660      }),
8661      500
8662    );
8663  }
8664
8665  /**
8666   * Update our internal duration on a `durationchange` event by calling
8667   * {@link Tech#duration}.
8668   *
8669   * @param {Event} event
8670   *        The `durationchange` event that caused this to run.
8671   *
8672   * @listens Tech#durationchange
8673   */
8674  onDurationChange(event) {
8675    this.duration_ = this.duration();
8676  }
8677
8678  /**
8679   * Get and create a `TimeRange` object for buffering.
8680   *
8681   * @return {TimeRange}
8682   *         The time range object that was created.
8683   */
8684  buffered() {
8685    return createTimeRanges$1(0, 0);
8686  }
8687
8688  /**
8689   * Get the percentage of the current video that is currently buffered.
8690   *
8691   * @return {number}
8692   *         A number from 0 to 1 that represents the decimal percentage of the
8693   *         video that is buffered.
8694   *
8695   */
8696  bufferedPercent() {
8697    return bufferedPercent(this.buffered(), this.duration_);
8698  }
8699
8700  /**
8701   * Turn off the polyfill for `progress` events that was created in
8702   * {@link Tech#manualProgressOn}
8703   * Stop manually tracking progress events by clearing the interval that was set in
8704   * {@link Tech#trackProgress}.
8705   */
8706  stopTrackingProgress() {
8707    this.clearInterval(this.progressInterval);
8708  }
8709
8710  /**
8711   * Polyfill the `timeupdate` event for browsers that don't support it.
8712   *
8713   * @see {@link Tech#trackCurrentTime}
8714   */
8715  manualTimeUpdatesOn() {
8716    this.manualTimeUpdates = true;
8717    this.on('play', this.trackCurrentTime_);
8718    this.on('pause', this.stopTrackingCurrentTime_);
8719  }
8720
8721  /**
8722   * Turn off the polyfill for `timeupdate` events that was created in
8723   * {@link Tech#manualTimeUpdatesOn}
8724   */
8725  manualTimeUpdatesOff() {
8726    this.manualTimeUpdates = false;
vendor: 6,402 bytes, lines 8727-8982
8727    this.stopTrackingCurrentTime();
8728    this.off('play', this.trackCurrentTime_);
8729    this.off('pause', this.stopTrackingCurrentTime_);
8730  }
8731
8732  /**
8733   * Sets up an interval function to track current time and trigger `timeupdate` every
8734   * 250 milliseconds.
8735   *
8736   * @listens Tech#play
8737   * @triggers Tech#timeupdate
8738   */
8739  trackCurrentTime() {
8740    if (this.currentTimeInterval) {
8741      this.stopTrackingCurrentTime();
8742    }
8743    this.currentTimeInterval = this.setInterval(function () {
8744      /**
8745       * Triggered at an interval of 250ms to indicated that time is passing in the video.
8746       *
8747       * @event Tech#timeupdate
8748       * @type {Event}
8749       */
8750      this.trigger({
8751        type: 'timeupdate',
8752        target: this,
8753        manuallyTriggered: true,
8754      });
8755
8756      // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
8757    }, 250);
8758  }
8759
8760  /**
8761   * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
8762   * `timeupdate` event is no longer triggered.
8763   *
8764   * @listens {Tech#pause}
8765   */
8766  stopTrackingCurrentTime() {
8767    this.clearInterval(this.currentTimeInterval);
8768
8769    // #1002 - if the video ends right before the next timeupdate would happen,
8770    // the progress bar won't make it all the way to the end
8771    this.trigger({
8772      type: 'timeupdate',
8773      target: this,
8774      manuallyTriggered: true,
8775    });
8776  }
8777
8778  /**
8779   * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
8780   * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
8781   *
8782   * @fires Component#dispose
8783   */
8784  dispose() {
8785    // clear out all tracks because we can't reuse them between techs
8786    this.clearTracks(NORMAL.names);
8787
8788    // Turn off any manual progress or timeupdate tracking
8789    if (this.manualProgress) {
8790      this.manualProgressOff();
8791    }
8792    if (this.manualTimeUpdates) {
8793      this.manualTimeUpdatesOff();
8794    }
8795    super.dispose();
8796  }
8797
8798  /**
8799   * Clear out a single `TrackList` or an array of `TrackLists` given their names.
8800   *
8801   * > Note: Techs without source handlers should call this between sources for `video`
8802   *         & `audio` tracks. You don't want to use them between tracks!
8803   *
8804   * @param {string[]|string} types
8805   *        TrackList names to clear, valid names are `video`, `audio`, and
8806   *        `text`.
8807   */
8808  clearTracks(types) {
8809    types = [].concat(types);
8810    // clear out all tracks because we can't reuse them between techs
8811    types.forEach((type) => {
8812      const list = this[`${type}Tracks`]() || [];
8813      let i = list.length;
8814      while (i--) {
8815        const track = list[i];
8816        if (type === 'text') {
8817          this.removeRemoteTextTrack(track);
8818        }
8819        list.removeTrack(track);
8820      }
8821    });
8822  }
8823
8824  /**
8825   * Remove any TextTracks added via addRemoteTextTrack that are
8826   * flagged for automatic garbage collection
8827   */
8828  cleanupAutoTextTracks() {
8829    const list = this.autoRemoteTextTracks_ || [];
8830    let i = list.length;
8831    while (i--) {
8832      const track = list[i];
8833      this.removeRemoteTextTrack(track);
8834    }
8835  }
8836
8837  /**
8838   * Reset the tech, which will removes all sources and reset the internal readyState.
8839   *
8840   * @abstract
8841   */
8842  reset() {}
8843
8844  /**
8845   * Get the value of `crossOrigin` from the tech.
8846   *
8847   * @abstract
8848   *
8849   * @see {Html5#crossOrigin}
8850   */
8851  crossOrigin() {}
8852
8853  /**
8854   * Set the value of `crossOrigin` on the tech.
8855   *
8856   * @abstract
8857   *
8858   * @param {string} crossOrigin the crossOrigin value
8859   * @see {Html5#setCrossOrigin}
8860   */
8861  setCrossOrigin() {}
8862
8863  /**
8864   * Get or set an error on the Tech.
8865   *
8866   * @param {MediaError} [err]
8867   *        Error to set on the Tech
8868   *
8869   * @return {MediaError|null}
8870   *         The current error object on the tech, or null if there isn't one.
8871   */
8872  error(err) {
8873    if (err !== undefined) {
8874      this.error_ = new MediaError(err);
8875      this.trigger('error');
8876    }
8877    return this.error_;
8878  }
8879
8880  /**
8881   * Returns the `TimeRange`s that have been played through for the current source.
8882   *
8883   * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
8884   *         It only checks whether the source has played at all or not.
8885   *
8886   * @return {TimeRange}
8887   *         - A single time range if this video has played
8888   *         - An empty set of ranges if not.
8889   */
8890  played() {
8891    if (this.hasStarted_) {
8892      return createTimeRanges$1(0, 0);
8893    }
8894    return createTimeRanges$1();
8895  }
8896
8897  /**
8898   * Start playback
8899   *
8900   * @abstract
8901   *
8902   * @see {Html5#play}
8903   */
8904  play() {}
8905
8906  /**
8907   * Set whether we are scrubbing or not
8908   *
8909   * @abstract
8910   * @param {boolean} _isScrubbing
8911   *                  - true for we are currently scrubbing
8912   *                  - false for we are no longer scrubbing
8913   *
8914   * @see {Html5#setScrubbing}
8915   */
8916  setScrubbing(_isScrubbing) {}
8917
8918  /**
8919   * Get whether we are scrubbing or not
8920   *
8921   * @abstract
8922   *
8923   * @see {Html5#scrubbing}
8924   */
8925  scrubbing() {}
8926
8927  /**
8928   * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
8929   * previously called.
8930   *
8931   * @param {number} _seconds
8932   *        Set the current time of the media to this.
8933   * @fires Tech#timeupdate
8934   */
8935  setCurrentTime(_seconds) {
8936    // improve the accuracy of manual timeupdates
8937    if (this.manualTimeUpdates) {
8938      /**
8939       * A manual `timeupdate` event.
8940       *
8941       * @event Tech#timeupdate
8942       * @type {Event}
8943       */
8944      this.trigger({
8945        type: 'timeupdate',
8946        target: this,
8947        manuallyTriggered: true,
8948      });
8949    }
8950  }
8951
8952  /**
8953   * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
8954   * {@link TextTrackList} events.
8955   *
8956   * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
8957   *
8958   * @fires Tech#audiotrackchange
8959   * @fires Tech#videotrackchange
8960   * @fires Tech#texttrackchange
8961   */
8962  initTrackListeners() {
8963    /**
8964     * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
8965     *
8966     * @event Tech#audiotrackchange
8967     * @type {Event}
8968     */
8969
8970    /**
8971     * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
8972     *
8973     * @event Tech#videotrackchange
8974     * @type {Event}
8975     */
8976
8977    /**
8978     * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
8979     *
8980     * @event Tech#texttrackchange
8981     * @type {Event}
8982     */
vendor: 5,560 bytes, lines 8983-9145
8983    NORMAL.names.forEach((name) => {
8984      const props = NORMAL[name];
8985      const trackListChanges = () => {
8986        this.trigger(`${name}trackchange`);
8987      };
8988      const tracks = this[props.getterName]();
8989      tracks.addEventListener('removetrack', trackListChanges);
8990      tracks.addEventListener('addtrack', trackListChanges);
8991      this.on('dispose', () => {
8992        tracks.removeEventListener('removetrack', trackListChanges);
8993        tracks.removeEventListener('addtrack', trackListChanges);
8994      });
8995    });
8996  }
8997
8998  /**
8999   * Emulate TextTracks using vtt.js if necessary
9000   *
9001   * @fires Tech#vttjsloaded
9002   * @fires Tech#vttjserror
9003   */
9004  addWebVttScript_() {
9005    if (window__default['default'].WebVTT) {
9006      return;
9007    }
9008
9009    // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
9010    // signals that the Tech is ready at which point Tech.el_ is part of the DOM
9011    // before inserting the WebVTT script
9012    if (document__default['default'].body.contains(this.el())) {
9013      // load via require if available and vtt.js script location was not passed in
9014      // as an option. novtt builds will turn the above require call into an empty object
9015      // which will cause this if check to always fail.
9016      if (
9017        !this.options_['vtt.js'] &&
9018        isPlain(vtt__default['default']) &&
9019        Object.keys(vtt__default['default']).length > 0
9020      ) {
9021        this.trigger('vttjsloaded');
9022        return;
9023      }
9024
9025      // load vtt.js via the script location option or the cdn of no location was
9026      // passed in
9027      const script = document__default['default'].createElement('script');
9028      script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
9029      script.onload = () => {
9030        /**
9031         * Fired when vtt.js is loaded.
9032         *
9033         * @event Tech#vttjsloaded
9034         * @type {Event}
9035         */
9036        this.trigger('vttjsloaded');
9037      };
9038      script.onerror = () => {
9039        /**
9040         * Fired when vtt.js was not loaded due to an error
9041         *
9042         * @event Tech#vttjsloaded
9043         * @type {Event}
9044         */
9045        this.trigger('vttjserror');
9046      };
9047      this.on('dispose', () => {
9048        script.onload = null;
9049        script.onerror = null;
9050      });
9051      // but have not loaded yet and we set it to true before the inject so that
9052      // we don't overwrite the injected window.WebVTT if it loads right away
9053      window__default['default'].WebVTT = true;
9054      this.el().parentNode.appendChild(script);
9055    } else {
9056      this.ready(this.addWebVttScript_);
9057    }
9058  }
9059
9060  /**
9061   * Emulate texttracks
9062   *
9063   */
9064  emulateTextTracks() {
9065    const tracks = this.textTracks();
9066    const remoteTracks = this.remoteTextTracks();
9067    const handleAddTrack = (e) => tracks.addTrack(e.track);
9068    const handleRemoveTrack = (e) => tracks.removeTrack(e.track);
9069    remoteTracks.on('addtrack', handleAddTrack);
9070    remoteTracks.on('removetrack', handleRemoveTrack);
9071    this.addWebVttScript_();
9072    const updateDisplay = () => this.trigger('texttrackchange');
9073    const textTracksChanges = () => {
9074      updateDisplay();
9075      for (let i = 0; i < tracks.length; i++) {
9076        const track = tracks[i];
9077        track.removeEventListener('cuechange', updateDisplay);
9078        if (track.mode === 'showing') {
9079          track.addEventListener('cuechange', updateDisplay);
9080        }
9081      }
9082    };
9083    textTracksChanges();
9084    tracks.addEventListener('change', textTracksChanges);
9085    tracks.addEventListener('addtrack', textTracksChanges);
9086    tracks.addEventListener('removetrack', textTracksChanges);
9087    this.on('dispose', function () {
9088      remoteTracks.off('addtrack', handleAddTrack);
9089      remoteTracks.off('removetrack', handleRemoveTrack);
9090      tracks.removeEventListener('change', textTracksChanges);
9091      tracks.removeEventListener('addtrack', textTracksChanges);
9092      tracks.removeEventListener('removetrack', textTracksChanges);
9093      for (let i = 0; i < tracks.length; i++) {
9094        const track = tracks[i];
9095        track.removeEventListener('cuechange', updateDisplay);
9096      }
9097    });
9098  }
9099
9100  /**
9101   * Create and returns a remote {@link TextTrack} object.
9102   *
9103   * @param {string} kind
9104   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
9105   *
9106   * @param {string} [label]
9107   *        Label to identify the text track
9108   *
9109   * @param {string} [language]
9110   *        Two letter language abbreviation
9111   *
9112   * @return {TextTrack}
9113   *         The TextTrack that gets created.
9114   */
9115  addTextTrack(kind, label, language) {
9116    if (!kind) {
9117      throw new Error('TextTrack kind is required but was not provided');
9118    }
9119    return createTrackHelper(this, kind, label, language);
9120  }
9121
9122  /**
9123   * Create an emulated TextTrack for use by addRemoteTextTrack
9124   *
9125   * This is intended to be overridden by classes that inherit from
9126   * Tech in order to create native or custom TextTracks.
9127   *
9128   * @param {Object} options
9129   *        The object should contain the options to initialize the TextTrack with.
9130   *
9131   * @param {string} [options.kind]
9132   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
9133   *
9134   * @param {string} [options.label].
9135   *        Label to identify the text track
9136   *
9137   * @param {string} [options.language]
9138   *        Two letter language abbreviation.
9139   *
9140   * @return {HTMLTrackElement}
9141   *         The track element that gets created.
9142   */
9143  createRemoteTextTrack(options) {
9144    const track = merge$1(options, {
9145      tech: this,
vendor: 7,926 bytes, lines 9145-9436
9145
9146    });
9147    return new REMOTE.remoteTextEl.TrackClass(track);
9148  }
9149
9150  /**
9151   * Creates a remote text track object and returns an html track element.
9152   *
9153   * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
9154   *
9155   * @param {Object} options
9156   *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
9157   *
9158   * @param {boolean} [manualCleanup=false]
9159   *        - When false: the TextTrack will be automatically removed from the video
9160   *          element whenever the source changes
9161   *        - When True: The TextTrack will have to be cleaned up manually
9162   *
9163   * @return {HTMLTrackElement}
9164   *         An Html Track Element.
9165   *
9166   */
9167  addRemoteTextTrack(options = {}, manualCleanup) {
9168    const htmlTrackElement = this.createRemoteTextTrack(options);
9169    if (typeof manualCleanup !== 'boolean') {
9170      manualCleanup = false;
9171    }
9172
9173    // store HTMLTrackElement and TextTrack to remote list
9174    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
9175    this.remoteTextTracks().addTrack(htmlTrackElement.track);
9176    if (manualCleanup === false) {
9177      // create the TextTrackList if it doesn't exist
9178      this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
9179    }
9180    return htmlTrackElement;
9181  }
9182
9183  /**
9184   * Remove a remote text track from the remote `TextTrackList`.
9185   *
9186   * @param {TextTrack} track
9187   *        `TextTrack` to remove from the `TextTrackList`
9188   */
9189  removeRemoteTextTrack(track) {
9190    const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
9191
9192    // remove HTMLTrackElement and TextTrack from remote list
9193    this.remoteTextTrackEls().removeTrackElement_(trackElement);
9194    this.remoteTextTracks().removeTrack(track);
9195    this.autoRemoteTextTracks_.removeTrack(track);
9196  }
9197
9198  /**
9199   * Gets available media playback quality metrics as specified by the W3C's Media
9200   * Playback Quality API.
9201   *
9202   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
9203   *
9204   * @return {Object}
9205   *         An object with supported media playback quality metrics
9206   *
9207   * @abstract
9208   */
9209  getVideoPlaybackQuality() {
9210    return {};
9211  }
9212
9213  /**
9214   * Attempt to create a floating video window always on top of other windows
9215   * so that users may continue consuming media while they interact with other
9216   * content sites, or applications on their device.
9217   *
9218   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
9219   *
9220   * @return {Promise|undefined}
9221   *         A promise with a Picture-in-Picture window if the browser supports
9222   *         Promises (or one was passed in as an option). It returns undefined
9223   *         otherwise.
9224   *
9225   * @abstract
9226   */
9227  requestPictureInPicture() {
9228    return Promise.reject();
9229  }
9230
9231  /**
9232   * A method to check for the value of the 'disablePictureInPicture' <video> property.
9233   * Defaults to true, as it should be considered disabled if the tech does not support pip
9234   *
9235   * @abstract
9236   */
9237  disablePictureInPicture() {
9238    return true;
9239  }
9240
9241  /**
9242   * A method to set or unset the 'disablePictureInPicture' <video> property.
9243   *
9244   * @abstract
9245   */
9246  setDisablePictureInPicture() {}
9247
9248  /**
9249   * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
9250   *
9251   * @param {function} cb
9252   * @return {number} request id
9253   */
9254  requestVideoFrameCallback(cb) {
9255    const id = newGUID();
9256    if (!this.isReady_ || this.paused()) {
9257      this.queuedHanders_.add(id);
9258      this.one('playing', () => {
9259        if (this.queuedHanders_.has(id)) {
9260          this.queuedHanders_.delete(id);
9261          cb();
9262        }
9263      });
9264    } else {
9265      this.requestNamedAnimationFrame(id, cb);
9266    }
9267    return id;
9268  }
9269
9270  /**
9271   * A fallback implementation of cancelVideoFrameCallback
9272   *
9273   * @param {number} id id of callback to be cancelled
9274   */
9275  cancelVideoFrameCallback(id) {
9276    if (this.queuedHanders_.has(id)) {
9277      this.queuedHanders_.delete(id);
9278    } else {
9279      this.cancelNamedAnimationFrame(id);
9280    }
9281  }
9282
9283  /**
9284   * A method to set a poster from a `Tech`.
9285   *
9286   * @abstract
9287   */
9288  setPoster() {}
9289
9290  /**
9291   * A method to check for the presence of the 'playsinline' <video> attribute.
9292   *
9293   * @abstract
9294   */
9295  playsinline() {}
9296
9297  /**
9298   * A method to set or unset the 'playsinline' <video> attribute.
9299   *
9300   * @abstract
9301   */
9302  setPlaysinline() {}
9303
9304  /**
9305   * Attempt to force override of native audio tracks.
9306   *
9307   * @param {boolean} override - If set to true native audio will be overridden,
9308   * otherwise native audio will potentially be used.
9309   *
9310   * @abstract
9311   */
9312  overrideNativeAudioTracks(override) {}
9313
9314  /**
9315   * Attempt to force override of native video tracks.
9316   *
9317   * @param {boolean} override - If set to true native video will be overridden,
9318   * otherwise native video will potentially be used.
9319   *
9320   * @abstract
9321   */
9322  overrideNativeVideoTracks(override) {}
9323
9324  /**
9325   * Check if the tech can support the given mime-type.
9326   *
9327   * The base tech does not support any type, but source handlers might
9328   * overwrite this.
9329   *
9330   * @param  {string} _type
9331   *         The mimetype to check for support
9332   *
9333   * @return {string}
9334   *         'probably', 'maybe', or empty string
9335   *
9336   * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
9337   *
9338   * @abstract
9339   */
9340  canPlayType(_type) {
9341    return '';
9342  }
9343
9344  /**
9345   * Check if the type is supported by this tech.
9346   *
9347   * The base tech does not support any type, but source handlers might
9348   * overwrite this.
9349   *
9350   * @param {string} _type
9351   *        The media type to check
9352   * @return {string} Returns the native video element's response
9353   */
9354  static canPlayType(_type) {
9355    return '';
9356  }
9357
9358  /**
9359   * Check if the tech can support the given source
9360   *
9361   * @param {Object} srcObj
9362   *        The source object
9363   * @param {Object} options
9364   *        The options passed to the tech
9365   * @return {string} 'probably', 'maybe', or '' (empty string)
9366   */
9367  static canPlaySource(srcObj, options) {
9368    return Tech.canPlayType(srcObj.type);
9369  }
9370
9371  /*
9372   * Return whether the argument is a Tech or not.
9373   * Can be passed either a Class like `Html5` or a instance like `player.tech_`
9374   *
9375   * @param {Object} component
9376   *        The item to check
9377   *
9378   * @return {boolean}
9379   *         Whether it is a tech or not
9380   *         - True if it is a tech
9381   *         - False if it is not
9382   */
9383  static isTech(component) {
9384    return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
9385  }
9386
9387  /**
9388   * Registers a `Tech` into a shared list for videojs.
9389   *
9390   * @param {string} name
9391   *        Name of the `Tech` to register.
9392   *
9393   * @param {Object} tech
9394   *        The `Tech` class to register.
9395   */
9396  static registerTech(name, tech) {
9397    if (!Tech.techs_) {
9398      Tech.techs_ = {};
9399    }
9400    if (!Tech.isTech(tech)) {
9401      throw new Error(`Tech ${name} must be a Tech`);
9402    }
9403    if (!Tech.canPlayType) {
9404      throw new Error('Techs must have a static canPlayType method on them');
9405    }
9406    if (!Tech.canPlaySource) {
9407      throw new Error('Techs must have a static canPlaySource method on them');
9408    }
9409    name = toTitleCase$1(name);
9410    Tech.techs_[name] = tech;
9411    Tech.techs_[toLowerCase(name)] = tech;
9412    if (name !== 'Tech') {
9413      // camel case the techName for use in techOrder
9414      Tech.defaultTechOrder_.push(name);
9415    }
9416    return tech;
9417  }
9418
9419  /**
9420   * Get a `Tech` from the shared list by name.
9421   *
9422   * @param {string} name
9423   *        `camelCase` or `TitleCase` name of the Tech to get
9424   *
9425   * @return {Tech|undefined}
9426   *         The `Tech` or undefined if there was no tech with the name requested.
9427   */
9428  static getTech(name) {
9429    if (!name) {
9430      return;
9431    }
9432    if (Tech.techs_ && Tech.techs_[name]) {
9433      return Tech.techs_[name];
9434    }
9435    name = toTitleCase$1(name);
9436    if (
9436
9437      window__default['default'] &&
9438      window__default['default'].videojs &&
9439      window__default['default'].videojs[name]
9440    ) {
9441      log$1.warn(
9442        `The ${name} tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)`
9443      );
9444      return window__default['default']
vendor: 11,353 bytes, lines 9444-9880
9444.videojs[name];
9445    }
9446  }
9447}
9448
9449/**
9450 * Get the {@link VideoTrackList}
9451 *
9452 * @returns {VideoTrackList}
9453 * @method Tech.prototype.videoTracks
9454 */
9455
9456/**
9457 * Get the {@link AudioTrackList}
9458 *
9459 * @returns {AudioTrackList}
9460 * @method Tech.prototype.audioTracks
9461 */
9462
9463/**
9464 * Get the {@link TextTrackList}
9465 *
9466 * @returns {TextTrackList}
9467 * @method Tech.prototype.textTracks
9468 */
9469
9470/**
9471 * Get the remote element {@link TextTrackList}
9472 *
9473 * @returns {TextTrackList}
9474 * @method Tech.prototype.remoteTextTracks
9475 */
9476
9477/**
9478 * Get the remote element {@link HtmlTrackElementList}
9479 *
9480 * @returns {HtmlTrackElementList}
9481 * @method Tech.prototype.remoteTextTrackEls
9482 */
9483
9484ALL.names.forEach(function (name) {
9485  const props = ALL[name];
9486  Tech.prototype[props.getterName] = function () {
9487    this[props.privateName] = this[props.privateName] || new props.ListClass();
9488    return this[props.privateName];
9489  };
9490});
9491
9492/**
9493 * List of associated text tracks
9494 *
9495 * @type {TextTrackList}
9496 * @private
9497 * @property Tech#textTracks_
9498 */
9499
9500/**
9501 * List of associated audio tracks.
9502 *
9503 * @type {AudioTrackList}
9504 * @private
9505 * @property Tech#audioTracks_
9506 */
9507
9508/**
9509 * List of associated video tracks.
9510 *
9511 * @type {VideoTrackList}
9512 * @private
9513 * @property Tech#videoTracks_
9514 */
9515
9516/**
9517 * Boolean indicating whether the `Tech` supports volume control.
9518 *
9519 * @type {boolean}
9520 * @default
9521 */
9522Tech.prototype.featuresVolumeControl = true;
9523
9524/**
9525 * Boolean indicating whether the `Tech` supports muting volume.
9526 *
9527 * @type {boolean}
9528 * @default
9529 */
9530Tech.prototype.featuresMuteControl = true;
9531
9532/**
9533 * Boolean indicating whether the `Tech` supports fullscreen resize control.
9534 * Resizing plugins using request fullscreen reloads the plugin
9535 *
9536 * @type {boolean}
9537 * @default
9538 */
9539Tech.prototype.featuresFullscreenResize = false;
9540
9541/**
9542 * Boolean indicating whether the `Tech` supports changing the speed at which the video
9543 * plays. Examples:
9544 *   - Set player to play 2x (twice) as fast
9545 *   - Set player to play 0.5x (half) as fast
9546 *
9547 * @type {boolean}
9548 * @default
9549 */
9550Tech.prototype.featuresPlaybackRate = false;
9551
9552/**
9553 * Boolean indicating whether the `Tech` supports the `progress` event.
9554 * This will be used to determine if {@link Tech#manualProgressOn} should be called.
9555 *
9556 * @type {boolean}
9557 * @default
9558 */
9559Tech.prototype.featuresProgressEvents = false;
9560
9561/**
9562 * Boolean indicating whether the `Tech` supports the `sourceset` event.
9563 *
9564 * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
9565 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
9566 * a new source.
9567 *
9568 * @type {boolean}
9569 * @default
9570 */
9571Tech.prototype.featuresSourceset = false;
9572
9573/**
9574 * Boolean indicating whether the `Tech` supports the `timeupdate` event.
9575 * This will be used to determine if {@link Tech#manualTimeUpdates} should be called.
9576 *
9577 * @type {boolean}
9578 * @default
9579 */
9580Tech.prototype.featuresTimeupdateEvents = false;
9581
9582/**
9583 * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
9584 * This will help us integrate with native `TextTrack`s if the browser supports them.
9585 *
9586 * @type {boolean}
9587 * @default
9588 */
9589Tech.prototype.featuresNativeTextTracks = false;
9590
9591/**
9592 * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
9593 *
9594 * @type {boolean}
9595 * @default
9596 */
9597Tech.prototype.featuresVideoFrameCallback = false;
9598
9599/**
9600 * A functional mixin for techs that want to use the Source Handler pattern.
9601 * Source handlers are scripts for handling specific formats.
9602 * The source handler pattern is used for adaptive formats (HLS, DASH) that
9603 * manually load video data and feed it into a Source Buffer (Media Source Extensions)
9604 * Example: `Tech.withSourceHandlers.call(MyTech);`
9605 *
9606 * @param {Tech} _Tech
9607 *        The tech to add source handler functions to.
9608 *
9609 * @mixes Tech~SourceHandlerAdditions
9610 */
9611Tech.withSourceHandlers = function (_Tech) {
9612  /**
9613   * Register a source handler
9614   *
9615   * @param {Function} handler
9616   *        The source handler class
9617   *
9618   * @param {number} [index]
9619   *        Register it at the following index
9620   */
9621  _Tech.registerSourceHandler = function (handler, index) {
9622    let handlers = _Tech.sourceHandlers;
9623    if (!handlers) {
9624      handlers = _Tech.sourceHandlers = [];
9625    }
9626    if (index === undefined) {
9627      // add to the end of the list
9628      index = handlers.length;
9629    }
9630    handlers.splice(index, 0, handler);
9631  };
9632
9633  /**
9634   * Check if the tech can support the given type. Also checks the
9635   * Techs sourceHandlers.
9636   *
9637   * @param {string} type
9638   *         The mimetype to check.
9639   *
9640   * @return {string}
9641   *         'probably', 'maybe', or '' (empty string)
9642   */
9643  _Tech.canPlayType = function (type) {
9644    const handlers = _Tech.sourceHandlers || [];
9645    let can;
9646    for (let i = 0; i < handlers.length; i++) {
9647      can = handlers[i].canPlayType(type);
9648      if (can) {
9649        return can;
9650      }
9651    }
9652    return '';
9653  };
9654
9655  /**
9656   * Returns the first source handler that supports the source.
9657   *
9658   * TODO: Answer question: should 'probably' be prioritized over 'maybe'
9659   *
9660   * @param {SourceObject} source
9661   *        The source object
9662   *
9663   * @param {Object} options
9664   *        The options passed to the tech
9665   *
9666   * @return {SourceHandler|null}
9667   *          The first source handler that supports the source or null if
9668   *          no SourceHandler supports the source
9669   */
9670  _Tech.selectSourceHandler = function (source, options) {
9671    const handlers = _Tech.sourceHandlers || [];
9672    let can;
9673    for (let i = 0; i < handlers.length; i++) {
9674      can = handlers[i].canHandleSource(source, options);
9675      if (can) {
9676        return handlers[i];
9677      }
9678    }
9679    return null;
9680  };
9681
9682  /**
9683   * Check if the tech can support the given source.
9684   *
9685   * @param {SourceObject} srcObj
9686   *        The source object
9687   *
9688   * @param {Object} options
9689   *        The options passed to the tech
9690   *
9691   * @return {string}
9692   *         'probably', 'maybe', or '' (empty string)
9693   */
9694  _Tech.canPlaySource = function (srcObj, options) {
9695    const sh = _Tech.selectSourceHandler(srcObj, options);
9696    if (sh) {
9697      return sh.canHandleSource(srcObj, options);
9698    }
9699    return '';
9700  };
9701
9702  /**
9703   * When using a source handler, prefer its implementation of
9704   * any function normally provided by the tech.
9705   */
9706  const deferrable = ['seekable', 'seeking', 'duration'];
9707
9708  /**
9709   * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
9710   * function if it exists, with a fallback to the Techs seekable function.
9711   *
9712   * @method _Tech.seekable
9713   */
9714
9715  /**
9716   * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
9717   * function if it exists, otherwise it will fallback to the techs duration function.
9718   *
9719   * @method _Tech.duration
9720   */
9721
9722  deferrable.forEach(function (fnName) {
9723    const originalFn = this[fnName];
9724    if (typeof originalFn !== 'function') {
9725      return;
9726    }
9727    this[fnName] = function () {
9728      if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
9729        return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
9730      }
9731      return originalFn.apply(this, arguments);
9732    };
9733  }, _Tech.prototype);
9734
9735  /**
9736   * Create a function for setting the source using a source object
9737   * and source handlers.
9738   * Should never be called unless a source handler was found.
9739   *
9740   * @param {SourceObject} source
9741   *        A source object with src and type keys
9742   */
9743  _Tech.prototype.setSource = function (source) {
9744    let sh = _Tech.selectSourceHandler(source, this.options_);
9745    if (!sh) {
9746      // Fall back to a native source handler when unsupported sources are
9747      // deliberately set
9748      if (_Tech.nativeSourceHandler) {
9749        sh = _Tech.nativeSourceHandler;
9750      } else {
9751        log$1.error('No source handler found for the current source.');
9752      }
9753    }
9754
9755    // Dispose any existing source handler
9756    this.disposeSourceHandler();
9757    this.off('dispose', this.disposeSourceHandler_);
9758    if (sh !== _Tech.nativeSourceHandler) {
9759      this.currentSource_ = source;
9760    }
9761    this.sourceHandler_ = sh.handleSource(source, this, this.options_);
9762    this.one('dispose', this.disposeSourceHandler_);
9763  };
9764
9765  /**
9766   * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
9767   *
9768   * @listens Tech#dispose
9769   */
9770  _Tech.prototype.disposeSourceHandler = function () {
9771    // if we have a source and get another one
9772    // then we are loading something new
9773    // than clear all of our current tracks
9774    if (this.currentSource_) {
9775      this.clearTracks(['audio', 'video']);
9776      this.currentSource_ = null;
9777    }
9778
9779    // always clean up auto-text tracks
9780    this.cleanupAutoTextTracks();
9781    if (this.sourceHandler_) {
9782      if (this.sourceHandler_.dispose) {
9783        this.sourceHandler_.dispose();
9784      }
9785      this.sourceHandler_ = null;
9786    }
9787  };
9788};
9789
9790// The base Tech class needs to be registered as a Component. It is the only
9791// Tech that can be registered as a Component.
9792Component$1.registerComponent('Tech', Tech);
9793Tech.registerTech('Tech', Tech);
9794
9795/**
9796 * A list of techs that should be added to techOrder on Players
9797 *
9798 * @private
9799 */
9800Tech.defaultTechOrder_ = [];
9801
9802/**
9803 * @file middleware.js
9804 * @module middleware
9805 */
9806
9807/** @import Player from '../player' */
9808/** @import Tech from '../tech/tech' */
9809
9810const middlewares = {};
9811const middlewareInstances = {};
9812const TERMINATOR = {};
9813
9814/**
9815 * A middleware object is a plain JavaScript object that has methods that
9816 * match the {@link Tech} methods found in the lists of allowed
9817 * {@link module:middleware.allowedGetters|getters},
9818 * {@link module:middleware.allowedSetters|setters}, and
9819 * {@link module:middleware.allowedMediators|mediators}.
9820 *
9821 * @typedef {Object} MiddlewareObject
9822 */
9823
9824/**
9825 * A middleware factory function that should return a
9826 * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
9827 *
9828 * This factory will be called for each player when needed, with the player
9829 * passed in as an argument.
9830 *
9831 * @callback MiddlewareFactory
9832 * @param {Player} player
9833 *        A Video.js player.
9834 */
9835
9836/**
9837 * Define a middleware that the player should use by way of a factory function
9838 * that returns a middleware object.
9839 *
9840 * @param  {string} type
9841 *         The MIME type to match or `"*"` for all MIME types.
9842 *
9843 * @param  {MiddlewareFactory} middleware
9844 *         A middleware factory function that will be executed for
9845 *         matching types.
9846 */
9847function use(type, middleware) {
9848  middlewares[type] = middlewares[type] || [];
9849  middlewares[type].push(middleware);
9850}
9851
9852/**
9853 * Asynchronously sets a source using middleware by recursing through any
9854 * matching middlewares and calling `setSource` on each, passing along the
9855 * previous returned value each time.
9856 *
9857 * @param  {Player} player
9858 *         A {@link Player} instance.
9859 *
9860 * @param  {Tech~SourceObject} src
9861 *         A source object.
9862 *
9863 * @param  {Function}
9864 *         The next middleware to run.
9865 */
9866function setSource(player, src, next) {
9867  player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
9868}
9869
9870/**
9871 * When the tech is set, passes the tech to each middleware's `setTech` method.
9872 *
9873 * @param {Object[]} middleware
9874 *        An array of middleware instances.
9875 *
9876 * @param {Tech} tech
9877 *        A Video.js tech.
9878 */
9879function setTech(middleware, tech) {
9880  middleware.forEach(
9880(mw) => mw.setTech && mw.setTech(tech));
9881}
9882
9883/**
9884 * Calls a getter on the tech first, through each middleware
9885 * from right to left to the player.
9886 *
9887 * @param  {Object[]} middleware
9888 *         An array of middleware instances.
9889 *
9890 * @param  {Tech} tech
9891 *         The current tech.
9892 *
9893 * @param  {string} method
9894 *         A method name.
9895 *
9896 * @return {*}
9897 *         The final value from the tech after middleware has intercepted it.
9898 */
9899function get(middleware, tech, method) {
9900  return middleware.reduceRight(middlewareIterator(method), tech[method]());
9901}
9902
9903/**
9904 * Takes the argument given to the player and calls the setter method on each
9905 * middleware from left to right to the tech.
9906 *
9907 * @param  {Object[]} middleware
9908 *         An array of middleware instances.
9909 *
9910 * @param  {Tech} tech
9911 *         The current tech.
9912 *
9913 * @param  {string} method
9914 *         A method name.
9915 *
9916 * @param  {*} arg
9917 *         The value to set on the tech.
9918 *
9919 * @return {*}
9920 *         The return value of the `method` of the `tech`.
9921 */
9922function set(middleware, tech, method, arg) {
9923  return tech[method](middleware.reduce(middlewareIterator(method), arg));
9924}
9925
9926/**
9927 * Takes the argument given to the player and calls the `call` version of the
9928 * method on each middleware from left to right.
9929 *
9930 * Then, call the passed in method on the tech and return the result unchanged
9931 * back to the player, through middleware, this time from right to left.
9932 *
9933 * @param  {Object[]} middleware
9934 *         An array of middleware instances.
9935 *
9936 * @param  {Tech} tech
9937 *         The current tech.
9938 *
9939 * @param  {string} method
vendor: 7,929 bytes, lines 9940-10261
9940 *         A method name.
9941 *
9942 * @param  {*} arg
9943 *         The value to set on the tech.
9944 *
9945 * @return {*}
9946 *         The return value of the `method` of the `tech`, regardless of the
9947 *         return values of middlewares.
9948 */
9949function mediate(middleware, tech, method, arg = null) {
9950  const callMethod = 'call' + toTitleCase$1(method);
9951  const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
9952  const terminated = middlewareValue === TERMINATOR;
9953  // deprecated. The `null` return value should instead return TERMINATOR to
9954  // prevent confusion if a techs method actually returns null.
9955  const returnValue = terminated ? null : tech[method](middlewareValue);
9956  executeRight(middleware, method, returnValue, terminated);
9957  return returnValue;
9958}
9959
9960/**
9961 * Enumeration of allowed getters where the keys are method names.
9962 *
9963 * @type {Object}
9964 */
9965const allowedGetters = {
9966  buffered: 1,
9967  currentTime: 1,
9968  duration: 1,
9969  muted: 1,
9970  played: 1,
9971  paused: 1,
9972  seekable: 1,
9973  volume: 1,
9974  ended: 1,
9975};
9976
9977/**
9978 * Enumeration of allowed setters where the keys are method names.
9979 *
9980 * @type {Object}
9981 */
9982const allowedSetters = {
9983  setCurrentTime: 1,
9984  setMuted: 1,
9985  setVolume: 1,
9986};
9987
9988/**
9989 * Enumeration of allowed mediators where the keys are method names.
9990 *
9991 * @type {Object}
9992 */
9993const allowedMediators = {
9994  play: 1,
9995  pause: 1,
9996};
9997function middlewareIterator(method) {
9998  return (value, mw) => {
9999    // if the previous middleware terminated, pass along the termination
10000    if (value === TERMINATOR) {
10001      return TERMINATOR;
10002    }
10003    if (mw[method]) {
10004      return mw[method](value);
10005    }
10006    return value;
10007  };
10008}
10009function executeRight(mws, method, value, terminated) {
10010  for (let i = mws.length - 1; i >= 0; i--) {
10011    const mw = mws[i];
10012    if (mw[method]) {
10013      mw[method](terminated, value);
10014    }
10015  }
10016}
10017
10018/**
10019 * Clear the middleware cache for a player.
10020 *
10021 * @param  {Player} player
10022 *         A {@link Player} instance.
10023 */
10024function clearCacheForPlayer(player) {
10025  if (middlewareInstances.hasOwnProperty(player.id())) {
10026    delete middlewareInstances[player.id()];
10027  }
10028}
10029
10030/**
10031 * {
10032 *  [playerId]: [[mwFactory, mwInstance], ...]
10033 * }
10034 *
10035 * @private
10036 */
10037function getOrCreateFactory(player, mwFactory) {
10038  const mws = middlewareInstances[player.id()];
10039  let mw = null;
10040  if (mws === undefined || mws === null) {
10041    mw = mwFactory(player);
10042    middlewareInstances[player.id()] = [[mwFactory, mw]];
10043    return mw;
10044  }
10045  for (let i = 0; i < mws.length; i++) {
10046    const [mwf, mwi] = mws[i];
10047    if (mwf !== mwFactory) {
10048      continue;
10049    }
10050    mw = mwi;
10051  }
10052  if (mw === null) {
10053    mw = mwFactory(player);
10054    mws.push([mwFactory, mw]);
10055  }
10056  return mw;
10057}
10058function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
10059  const [mwFactory, ...mwrest] = middleware;
10060
10061  // if mwFactory is a string, then we're at a fork in the road
10062  if (typeof mwFactory === 'string') {
10063    setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
10064
10065    // if we have an mwFactory, call it with the player to get the mw,
10066    // then call the mw's setSource method
10067  } else if (mwFactory) {
10068    const mw = getOrCreateFactory(player, mwFactory);
10069
10070    // if setSource isn't present, implicitly select this middleware
10071    if (!mw.setSource) {
10072      acc.push(mw);
10073      return setSourceHelper(src, mwrest, next, player, acc, lastRun);
10074    }
10075    mw.setSource(Object.assign({}, src), function (err, _src) {
10076      // something happened, try the next middleware on the current level
10077      // make sure to use the old src
10078      if (err) {
10079        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
10080      }
10081
10082      // we've succeeded, now we need to go deeper
10083      acc.push(mw);
10084
10085      // if it's the same type, continue down the current chain
10086      // otherwise, we want to go down the new chain
10087      setSourceHelper(
10088        _src,
10089        src.type === _src.type ? mwrest : middlewares[_src.type],
10090        next,
10091        player,
10092        acc,
10093        lastRun
10094      );
10095    });
10096  } else if (mwrest.length) {
10097    setSourceHelper(src, mwrest, next, player, acc, lastRun);
10098  } else if (lastRun) {
10099    next(src, acc);
10100  } else {
10101    setSourceHelper(src, middlewares['*'], next, player, acc, true);
10102  }
10103}
10104
10105/** @import Player from '../player' */
10106
10107/**
10108 * Mimetypes
10109 *
10110 * @see https://www.iana.org/assignments/media-types/media-types.xhtml
10111 * @typedef Mimetypes~Kind
10112 * @enum
10113 */
10114const MimetypesKind = {
10115  opus: 'video/ogg',
10116  ogv: 'video/ogg',
10117  mp4: 'video/mp4',
10118  mov: 'video/mp4',
10119  m4v: 'video/mp4',
10120  mkv: 'video/x-matroska',
10121  m4a: 'audio/mp4',
10122  mp3: 'audio/mpeg',
10123  aac: 'audio/aac',
10124  caf: 'audio/x-caf',
10125  flac: 'audio/flac',
10126  oga: 'audio/ogg',
10127  wav: 'audio/wav',
10128  m3u8: 'application/x-mpegURL',
10129  mpd: 'application/dash+xml',
10130  jpg: 'image/jpeg',
10131  jpeg: 'image/jpeg',
10132  gif: 'image/gif',
10133  png: 'image/png',
10134  svg: 'image/svg+xml',
10135  webp: 'image/webp',
10136};
10137
10138/**
10139 * Get the mimetype of a given src url if possible
10140 *
10141 * @param {string} src
10142 *        The url to the src
10143 *
10144 * @return {string}
10145 *         return the mimetype if it was known or empty string otherwise
10146 */
10147const getMimetype = function (src = '') {
10148  const ext = getFileExtension(src);
10149  const mimetype = MimetypesKind[ext.toLowerCase()];
10150  return mimetype || '';
10151};
10152
10153/**
10154 * Find the mime type of a given source string if possible. Uses the player
10155 * source cache.
10156 *
10157 * @param {Player} player
10158 *        The player object
10159 *
10160 * @param {string} src
10161 *        The source string
10162 *
10163 * @return {string}
10164 *         The type that was found
10165 */
10166const findMimetype = (player, src) => {
10167  if (!src) {
10168    return '';
10169  }
10170
10171  // 1. check for the type in the `source` cache
10172  if (player.cache_.source.src === src && player.cache_.source.type) {
10173    return player.cache_.source.type;
10174  }
10175
10176  // 2. see if we have this source in our `currentSources` cache
10177  const matchingSources = player.cache_.sources.filter((s) => s.src === src);
10178  if (matchingSources.length) {
10179    return matchingSources[0].type;
10180  }
10181
10182  // 3. look for the src url in source elements and use the type there
10183  const sources = player.$$('source');
10184  for (let i = 0; i < sources.length; i++) {
10185    const s = sources[i];
10186    if (s.type && s.src && s.src === src) {
10187      return s.type;
10188    }
10189  }
10190
10191  // 4. finally fallback to our list of mime types based on src url extension
10192  return getMimetype(src);
10193};
10194
10195/**
10196 * @module filter-source
10197 */
10198
10199/**
10200 * Filter out single bad source objects or multiple source objects in an
10201 * array. Also flattens nested source object arrays into a 1 dimensional
10202 * array of source objects.
10203 *
10204 * @param {Tech~SourceObject|Tech~SourceObject[]} src
10205 *        The src object to filter
10206 *
10207 * @return {Tech~SourceObject[]}
10208 *         An array of sourceobjects containing only valid sources
10209 *
10210 * @private
10211 */
10212const filterSource = function (src) {
10213  // traverse array
10214  if (Array.isArray(src)) {
10215    let newsrc = [];
10216    src.forEach(function (srcobj) {
10217      srcobj = filterSource(srcobj);
10218      if (Array.isArray(srcobj)) {
10219        newsrc = newsrc.concat(srcobj);
10220      } else if (isObject(srcobj)) {
10221        newsrc.push(srcobj);
10222      }
10223    });
10224    src = newsrc;
10225  } else if (typeof src === 'string' && src.trim()) {
10226    // convert string into object
10227    src = [
10228      fixSource({
10229        src,
10230      }),
10231    ];
10232  } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
10233    // src is already valid
10234    src = [fixSource(src)];
10235  } else {
10236    // invalid source, turn it into an empty array
10237    src = [];
10238  }
10239  return src;
10240};
10241
10242/**
10243 * Checks src mimetype, adding it when possible
10244 *
10245 * @param {Tech~SourceObject} src
10246 *        The src object to check
10247 * @return {Tech~SourceObject}
10248 *        src Object with known type
10249 */
10250function fixSource(src) {
10251  if (!src.type) {
10252    const mimetype = getMimetype(src.src);
10253    if (mimetype) {
10254      src.type = mimetype;
10255    }
10256  }
10257  return src;
10258}
10259
10260var icons =
10261  
vendor: 17,747 bytes, line 10261
10261'<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 1200 1227" id="vjs-icon-twitter">\n      <path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"/>\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>'
10261;
10262
10263// /**
10264
10265// Determine the keycode for the 'back' key based on the platform
10266const backKeyCode = IS_TIZEN ? 10009 : IS_WEBOS ? 461 : 8;
10267const SpatialNavKeyCodes = {
10268  codes: {
10269    play: 415,
10270    pause: 19,
10271    ff: 417,
10272    rw: 412,
10273    back: backKeyCode,
10274  },
10275  names: {
10276    415: 'play',
10277    19: 'pause',
10278    417: 'ff',
10279    412: 'rw',
10280    [backKeyCode]: 'back',
10281  },
10282  isEventKey(event, keyName) {
10283    keyName = keyName.toLowerCase();
10284    if (this.names[event.keyCode] && this.names[event.keyCode] === keyName) {
10285      return true;
10286    }
10287    return false;
10288  },
10289  getEventName(event) {
10290    if (this.names[event.keyCode]) {
10291      return this.names[event.keyCode];
10292    } else if (this.codes[event.code]) {
10293      const code = this.codes[event.code];
10294      return this.names[code];
10295    }
10296    return null;
10297  },
10298};
10299
10300/**
10301 * @file spatial-navigation.js
10302 */
10303
10304/** @import Component from './component' */
10305/** @import Player from './player' */
10306
10307// The number of seconds the `step*` functions move the timeline.
10308const STEP_SECONDS$1 = 5;
10309
10310/**
10311 * Spatial Navigation in Video.js enhances user experience and accessibility on smartTV devices,
10312 * enabling seamless navigation through interactive elements within the player using remote control arrow keys.
10313 * This functionality allows users to effortlessly navigate through focusable components.
10314 *
vendor: 11,167 bytes, lines 10315-10639
10315 * @extends EventTarget
10316 */
10317class SpatialNavigation extends EventTarget$2 {
10318  /**
10319   * Constructs a SpatialNavigation instance with initial settings.
10320   * Sets up the player instance, and prepares the spatial navigation system.
10321   *
10322   * @class
10323   * @param {Player} player - The Video.js player instance to which the spatial navigation is attached.
10324   */
10325  constructor(player) {
10326    super();
10327    this.player_ = player;
10328    this.focusableComponents = [];
10329    this.isListening_ = false;
10330    this.isPaused_ = false;
10331    this.onKeyDown_ = this.onKeyDown_.bind(this);
10332    this.lastFocusedComponent_ = null;
10333  }
10334
10335  /**
10336   * Starts the spatial navigation by adding a keydown event listener to the video container.
10337   * This method ensures that the event listener is added only once.
10338   */
10339  start() {
10340    // If the listener is already active, exit early.
10341    if (this.isListening_) {
10342      return;
10343    }
10344
10345    // Add the event listener since the listener is not yet active.
10346    this.player_.on('keydown', this.onKeyDown_);
10347    this.player_.on('modalKeydown', this.onKeyDown_);
10348    // Listen for source change events
10349    this.player_.on('loadedmetadata', () => {
10350      this.focus(this.updateFocusableComponents()[0]);
10351    });
10352    this.player_.on('modalclose', () => {
10353      this.refocusComponent();
10354    });
10355    this.player_.on('focusin', this.handlePlayerFocus_.bind(this));
10356    this.player_.on('focusout', this.handlePlayerBlur_.bind(this));
10357    this.isListening_ = true;
10358    if (this.player_.errorDisplay) {
10359      this.player_.errorDisplay.on('aftermodalfill', () => {
10360        this.updateFocusableComponents();
10361        if (this.focusableComponents.length) {
10362          // The modal has focusable components:
10363
10364          if (this.focusableComponents.length > 1) {
10365            // The modal has close button + some additional buttons.
10366            // Focusing first additional button:
10367
10368            this.focusableComponents[1].focus();
10369          } else {
10370            // The modal has only close button,
10371            // Focusing it:
10372
10373            this.focusableComponents[0].focus();
10374          }
10375        }
10376      });
10377    }
10378  }
10379
10380  /**
10381   * Stops the spatial navigation by removing the keydown event listener from the video container.
10382   * Also sets the `isListening_` flag to false.
10383   */
10384  stop() {
10385    this.player_.off('keydown', this.onKeyDown_);
10386    this.isListening_ = false;
10387  }
10388
10389  /**
10390   * Responds to keydown events for spatial navigation and media control.
10391   *
10392   * Determines if spatial navigation or media control is active and handles key inputs accordingly.
10393   *
10394   * @param {KeyboardEvent} event - The keydown event to be handled.
10395   */
10396  onKeyDown_(event) {
10397    // Determine if the event is a custom modalKeydown event
10398    const actualEvent = event.originalEvent ? event.originalEvent : event;
10399    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(actualEvent.key)) {
10400      // Handle directional navigation
10401      if (this.isPaused_) {
10402        return;
10403      }
10404      actualEvent.preventDefault();
10405
10406      // "ArrowLeft" => "left" etc
10407      const direction = actualEvent.key.substring(5).toLowerCase();
10408      this.move(direction);
10409    } else if (
10410      SpatialNavKeyCodes.isEventKey(actualEvent, 'play') ||
10411      SpatialNavKeyCodes.isEventKey(actualEvent, 'pause') ||
10412      SpatialNavKeyCodes.isEventKey(actualEvent, 'ff') ||
10413      SpatialNavKeyCodes.isEventKey(actualEvent, 'rw')
10414    ) {
10415      // Handle media actions
10416      actualEvent.preventDefault();
10417      const action = SpatialNavKeyCodes.getEventName(actualEvent);
10418      this.performMediaAction_(action);
10419    } else if (
10420      SpatialNavKeyCodes.isEventKey(actualEvent, 'Back') &&
10421      event.target &&
10422      typeof event.target.closeable === 'function' &&
10423      event.target.closeable()
10424    ) {
10425      actualEvent.preventDefault();
10426      event.target.close();
10427    }
10428  }
10429
10430  /**
10431   * Performs media control actions based on the given key input.
10432   *
10433   * Controls the playback and seeking functionalities of the media player.
10434   *
10435   * @param {string} key - The key representing the media action to be performed.
10436   *   Accepted keys: 'play', 'pause', 'ff' (fast-forward), 'rw' (rewind).
10437   */
10438  performMediaAction_(key) {
10439    if (this.player_) {
10440      switch (key) {
10441        case 'play':
10442          if (this.player_.paused()) {
10443            this.player_.play();
10444          }
10445          break;
10446        case 'pause':
10447          if (!this.player_.paused()) {
10448            this.player_.pause();
10449          }
10450          break;
10451        case 'ff':
10452          this.userSeek_(this.player_.currentTime() + STEP_SECONDS$1);
10453          break;
10454        case 'rw':
10455          this.userSeek_(this.player_.currentTime() - STEP_SECONDS$1);
10456          break;
10457      }
10458    }
10459  }
10460
10461  /**
10462   * Prevent liveThreshold from causing seeks to seem like they
10463   * are not happening from a user perspective.
10464   *
10465   * @param {number} ct
10466   *        current time to seek to
10467   */
10468  userSeek_(ct) {
10469    if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
10470      this.player_.liveTracker.nextSeekedFromUser();
10471    }
10472    this.player_.currentTime(ct);
10473  }
10474
10475  /**
10476   * Pauses the spatial navigation functionality.
10477   * This method sets a flag that can be used to temporarily disable the navigation logic.
10478   */
10479  pause() {
10480    this.isPaused_ = true;
10481  }
10482
10483  /**
10484   * Resumes the spatial navigation functionality if it has been paused.
10485   * This method resets the pause flag, re-enabling the navigation logic.
10486   */
10487  resume() {
10488    this.isPaused_ = false;
10489  }
10490
10491  /**
10492   * Handles Player Blur.
10493   *
10494   * @param {string|Event|Object} event
10495   *        The name of the event, an `Event`, or an object with a key of type set to
10496   *        an event name.
10497   *
10498   * Calls for handling of the Player Blur if:
10499   * *The next focused element is not a child of current focused element &
10500   * The next focused element is not a child of the Player.
10501   * *There is no next focused element
10502   */
10503  handlePlayerBlur_(event) {
10504    const nextFocusedElement = event.relatedTarget;
10505    let isChildrenOfPlayer = null;
10506    const currentComponent = this.getCurrentComponent(event.target);
10507    if (nextFocusedElement) {
10508      isChildrenOfPlayer = Boolean(nextFocusedElement.closest('.video-js'));
10509
10510      // If nextFocusedElement is the 'TextTrackSettings' component
10511      if (nextFocusedElement.classList.contains('vjs-text-track-settings') && !this.isPaused_) {
10512        this.searchForTrackSelect_();
10513      }
10514    }
10515    if (
10516      (!event.currentTarget.contains(event.relatedTarget) && !isChildrenOfPlayer) ||
10517      !nextFocusedElement
10518    ) {
10519      if (currentComponent && currentComponent.name() === 'CloseButton') {
10520        this.refocusComponent();
10521      } else {
10522        this.pause();
10523        if (currentComponent && currentComponent.el()) {
10524          // Store last focused component
10525          this.lastFocusedComponent_ = currentComponent;
10526        }
10527      }
10528    }
10529  }
10530
10531  /**
10532   * Handles the Player focus event.
10533   *
10534   * Calls for handling of the Player Focus if current element is focusable.
10535   */
10536  handlePlayerFocus_() {
10537    if (this.getCurrentComponent() && this.getCurrentComponent().getIsFocusable()) {
10538      this.resume();
10539    }
10540  }
10541
10542  /**
10543   * Gets a set of focusable components.
10544   *
10545   * @return {Array}
10546   *         Returns an array of focusable components.
10547   */
10548  updateFocusableComponents() {
10549    const player = this.player_;
10550    const focusableComponents = [];
10551
10552    /**
10553     * Searches for children candidates.
10554     *
10555     * Pushes Components to array of 'focusableComponents'.
10556     * Calls itself if there is children elements inside iterated component.
10557     *
10558     * @param {Array} componentsArray - The array of components to search for focusable children.
10559     */
10560    function searchForChildrenCandidates(componentsArray) {
10561      for (const i of componentsArray) {
10562        if (i.hasOwnProperty('el_') && i.getIsFocusable() && i.getIsAvailableToBeFocused(i.el())) {
10563          focusableComponents.push(i);
10564        }
10565        if (i.hasOwnProperty('children_') && i.children_.length > 0) {
10566          searchForChildrenCandidates(i.children_);
10567        }
10568      }
10569    }
10570
10571    // Iterate inside all children components of the player.
10572    player.children_.forEach((value) => {
10573      if (value.hasOwnProperty('el_')) {
10574        // If component has required functions 'getIsFocusable' & 'getIsAvailableToBeFocused', is focusable & available to be focused.
10575        if (
10576          value.getIsFocusable &&
10577          value.getIsAvailableToBeFocused &&
10578          value.getIsFocusable() &&
10579          value.getIsAvailableToBeFocused(value.el())
10580        ) {
10581          focusableComponents.push(value);
10582          return;
10583          // If component has posible children components as candidates.
10584        } else if (value.hasOwnProperty('children_') && value.children_.length > 0) {
10585          searchForChildrenCandidates(value.children_);
10586          // If component has posible item components as candidates.
10587        } else if (value.hasOwnProperty('items') && value.items.length > 0) {
10588          searchForChildrenCandidates(value.items);
10589          // If there is a suitable child element within the component's DOM element.
10590        } else if (this.findSuitableDOMChild(value)) {
10591          focusableComponents.push(value);
10592        }
10593      }
10594
10595      // TODO - Refactor the following logic after refactor of videojs-errors elements to be components is done.
10596      if (value.name_ === 'ErrorDisplay' && value.opened_) {
10597        const buttonContainer = value.el_.querySelector('.vjs-errors-ok-button-container');
10598        if (buttonContainer) {
10599          const modalButtons = buttonContainer.querySelectorAll('button');
10600          modalButtons.forEach((element, index) => {
10601            // Add elements as objects to be handled by the spatial navigation
10602            focusableComponents.push({
10603              name: () => {
10604                return 'ModalButton' + (index + 1);
10605              },
10606              el: () => element,
10607              getPositions: () => {
10608                const rect = element.getBoundingClientRect();
10609
10610                // Creating objects that mirror DOMRectReadOnly for boundingClientRect and center
10611                const boundingClientRect = {
10612                  x: rect.x,
10613                  y: rect.y,
10614                  width: rect.width,
10615                  height: rect.height,
10616                  top: rect.top,
10617                  right: rect.right,
10618                  bottom: rect.bottom,
10619                  left: rect.left,
10620                };
10621
10622                // Calculating the center position
10623                const center = {
10624                  x: rect.left + rect.width / 2,
10625                  y: rect.top + rect.height / 2,
10626                  width: 0,
10627                  height: 0,
10628                  top: rect.top + rect.height / 2,
10629                  right: rect.left + rect.width / 2,
10630                  bottom: rect.top + rect.height / 2,
10631                  left: rect.left + rect.width / 2,
10632                };
10633                return {
10634                  boundingClientRect,
10635                  center,
10636                };
10637              },
10638              // Asume that the following are always focusable
10639              getIsAvailableToBeFocused: () => true,
vendor: 6,854 bytes, lines 10640-10833
10640              getIsFocusable: (el) => true,
10641              focus: () => element.focus(),
10642            });
10643          });
10644        }
10645      }
10646    });
10647    this.focusableComponents = focusableComponents;
10648    return this.focusableComponents;
10649  }
10650
10651  /**
10652   * Finds a suitable child element within the provided component's DOM element.
10653   *
10654   * @param {Object} component - The component containing the DOM element to search within.
10655   * @return {HTMLElement|null} Returns the suitable child element if found, or null if not found.
10656   */
10657  findSuitableDOMChild(component) {
10658    /**
10659     * Recursively searches for a suitable child node that can be focused within a given component.
10660     * It first checks if the provided node itself can be focused according to the component's
10661     * `getIsFocusable` and `getIsAvailableToBeFocused` methods. If not, it recursively searches
10662     * through the node's children to find a suitable child node that meets the focusability criteria.
10663     *
10664     * @param {HTMLElement} node - The DOM node to start the search from.
10665     * @return {HTMLElement|null} The first child node that is focusable and available to be focused,
10666     * or `null` if no suitable child is found.
10667     */
10668    function searchForSuitableChild(node) {
10669      if (component.getIsFocusable(node) && component.getIsAvailableToBeFocused(node)) {
10670        return node;
10671      }
10672      for (let i = 0; i < node.children.length; i++) {
10673        const child = node.children[i];
10674        const suitableChild = searchForSuitableChild(child);
10675        if (suitableChild) {
10676          return suitableChild;
10677        }
10678      }
10679      return null;
10680    }
10681    if (component.el()) {
10682      return searchForSuitableChild(component.el());
10683    }
10684    return null;
10685  }
10686
10687  /**
10688   * Gets the currently focused component from the list of focusable components.
10689   * If a target element is provided, it uses that element to find the corresponding
10690   * component. If no target is provided, it defaults to using the document's currently
10691   * active element.
10692   *
10693   * @param {HTMLElement} [target] - The DOM element to check against the focusable components.
10694   *                                 If not provided, `document.activeElement` is used.
10695   * @return {Component|null} - Returns the focused component if found among the focusable components,
10696   *                            otherwise returns null if no matching component is found.
10697   */
10698  getCurrentComponent(target) {
10699    this.updateFocusableComponents();
10700    // eslint-disable-next-line
10701    const curComp = target || document.activeElement;
10702    if (this.focusableComponents.length) {
10703      for (const i of this.focusableComponents) {
10704        // If component Node is equal to the current active element.
10705        if (i.el() === curComp) {
10706          return i;
10707        }
10708      }
10709    }
10710  }
10711
10712  /**
10713   * Adds a component to the array of focusable components.
10714   *
10715   * @param {Component} component
10716   *        The `Component` to be added.
10717   */
10718  add(component) {
10719    const focusableComponents = [...this.focusableComponents];
10720    if (
10721      component.hasOwnProperty('el_') &&
10722      component.getIsFocusable() &&
10723      component.getIsAvailableToBeFocused(component.el())
10724    ) {
10725      focusableComponents.push(component);
10726    }
10727    this.focusableComponents = focusableComponents;
10728    // Trigger the notification manually
10729    this.trigger({
10730      type: 'focusableComponentsChanged',
10731      focusableComponents: this.focusableComponents,
10732    });
10733  }
10734
10735  /**
10736   * Removes component from the array of focusable components.
10737   *
10738   * @param {Component} component - The component to be removed from the focusable components array.
10739   */
10740  remove(component) {
10741    for (let i = 0; i < this.focusableComponents.length; i++) {
10742      if (this.focusableComponents[i].name() === component.name()) {
10743        this.focusableComponents.splice(i, 1);
10744        // Trigger the notification manually
10745        this.trigger({
10746          type: 'focusableComponentsChanged',
10747          focusableComponents: this.focusableComponents,
10748        });
10749        return;
10750      }
10751    }
10752  }
10753
10754  /**
10755   * Clears array of focusable components.
10756   */
10757  clear() {
10758    // Check if the array is already empty to avoid unnecessary event triggering
10759    if (this.focusableComponents.length > 0) {
10760      // Clear the array
10761      this.focusableComponents = [];
10762
10763      // Trigger the notification manually
10764      this.trigger({
10765        type: 'focusableComponentsChanged',
10766        focusableComponents: this.focusableComponents,
10767      });
10768    }
10769  }
10770
10771  /**
10772   * Navigates to the next focusable component based on the specified direction.
10773   *
10774   * @param {string} direction 'up', 'down', 'left', 'right'
10775   */
10776  move(direction) {
10777    const currentFocusedComponent = this.getCurrentComponent();
10778    if (!currentFocusedComponent) {
10779      return;
10780    }
10781    const currentPositions = currentFocusedComponent.getPositions();
10782    const candidates = this.focusableComponents.filter(
10783      (component) =>
10784        component !== currentFocusedComponent &&
10785        this.isInDirection_(
10786          currentPositions.boundingClientRect,
10787          component.getPositions().boundingClientRect,
10788          direction
10789        )
10790    );
10791    const bestCandidate = this.findBestCandidate_(currentPositions.center, candidates, direction);
10792    if (bestCandidate) {
10793      this.focus(bestCandidate);
10794    } else {
10795      this.trigger({
10796        type: 'endOfFocusableComponents',
10797        direction,
10798        focusedComponent: currentFocusedComponent,
10799      });
10800    }
10801  }
10802
10803  /**
10804   * Finds the best candidate on the current center position,
10805   * the list of candidates, and the specified navigation direction.
10806   *
10807   * @param {Object} currentCenter The center position of the current focused component element.
10808   * @param {Array} candidates An array of candidate components to receive focus.
10809   * @param {string} direction The direction of navigation ('up', 'down', 'left', 'right').
10810   * @return {Object|null} The component that is the best candidate for receiving focus.
10811   */
10812  findBestCandidate_(currentCenter, candidates, direction) {
10813    let minDistance = Infinity;
10814    let bestCandidate = null;
10815    for (const candidate of candidates) {
10816      const candidateCenter = candidate.getPositions().center;
10817      const distance = this.calculateDistance_(currentCenter, candidateCenter, direction);
10818      if (distance < minDistance) {
10819        minDistance = distance;
10820        bestCandidate = candidate;
10821      }
10822    }
10823    return bestCandidate;
10824  }
10825
10826  /**
10827   * Determines if a target rectangle is in the specified navigation direction
10828   * relative to a source rectangle.
10829   *
10830   * @param {Object} srcRect The bounding rectangle of the source element.
10831   * @param {Object} targetRect The bounding rectangle of the target element.
10832   * @param {string} direction The navigation direction ('up', 'down', 'left', 'right').
10833   * @return {boolean}
vendor: 10,417 bytes, lines 10833-11199
10833 True if the target is in the specified direction relative to the source.
10834   */
10835  isInDirection_(srcRect, targetRect, direction) {
10836    switch (direction) {
10837      case 'right':
10838        return targetRect.left >= srcRect.right;
10839      case 'left':
10840        return targetRect.right <= srcRect.left;
10841      case 'down':
10842        return targetRect.top >= srcRect.bottom;
10843      case 'up':
10844        return targetRect.bottom <= srcRect.top;
10845      default:
10846        return false;
10847    }
10848  }
10849
10850  /**
10851   * Focus the last focused component saved before blur on player.
10852   */
10853  refocusComponent() {
10854    if (this.lastFocusedComponent_) {
10855      // If user is not active, set it to active.
10856      if (!this.player_.userActive()) {
10857        this.player_.userActive(true);
10858      }
10859      this.updateFocusableComponents();
10860
10861      // Search inside array of 'focusableComponents' for a match of name of
10862      // the last focused component.
10863      for (let i = 0; i < this.focusableComponents.length; i++) {
10864        if (this.focusableComponents[i].name() === this.lastFocusedComponent_.name()) {
10865          this.focus(this.focusableComponents[i]);
10866          return;
10867        }
10868      }
10869    } else {
10870      this.focus(this.updateFocusableComponents()[0]);
10871    }
10872  }
10873
10874  /**
10875   * Focuses on a given component.
10876   * If the component is available to be focused, it focuses on the component.
10877   * If not, it attempts to find a suitable DOM child within the component and focuses on it.
10878   *
10879   * @param {Component} component - The component to be focused.
10880   */
10881  focus(component) {
10882    if (typeof component !== 'object') {
10883      return;
10884    }
10885    if (component.getIsAvailableToBeFocused(component.el())) {
10886      component.focus();
10887    } else if (this.findSuitableDOMChild(component)) {
10888      this.findSuitableDOMChild(component).focus();
10889    }
10890  }
10891
10892  /**
10893   * Calculates the distance between two points, adjusting the calculation based on
10894   * the specified navigation direction.
10895   *
10896   * @param {Object} center1 The center point of the first element.
10897   * @param {Object} center2 The center point of the second element.
10898   * @param {string} direction The direction of navigation ('up', 'down', 'left', 'right').
10899   * @return {number} The calculated distance between the two centers.
10900   */
10901  calculateDistance_(center1, center2, direction) {
10902    const dx = Math.abs(center1.x - center2.x);
10903    const dy = Math.abs(center1.y - center2.y);
10904    let distance;
10905    switch (direction) {
10906      case 'right':
10907      case 'left':
10908        // Higher weight for vertical distance in horizontal navigation.
10909        distance = dx + dy * 100;
10910        break;
10911      case 'up':
10912        // Strongly prioritize vertical proximity for UP navigation.
10913        // Adjust the weight to ensure that elements directly above are favored.
10914        distance = dy * 2 + dx * 0.5;
10915        break;
10916      case 'down':
10917        // More balanced weight for vertical and horizontal distances.
10918        // Adjust the weights here to find the best balance.
10919        distance = dy * 5 + dx;
10920        break;
10921      default:
10922        distance = dx + dy;
10923    }
10924    return distance;
10925  }
10926
10927  /**
10928   * This gets called by 'handlePlayerBlur_' if 'spatialNavigation' is enabled.
10929   * Searches for the first 'TextTrackSelect' inside of modal to focus.
10930   *
10931   * @private
10932   */
10933  searchForTrackSelect_() {
10934    const spatialNavigation = this;
10935    for (const component of spatialNavigation.updateFocusableComponents()) {
10936      if (component.constructor.name === 'TextTrackSelect') {
10937        spatialNavigation.focus(component);
10938        break;
10939      }
10940    }
10941  }
10942}
10943
10944/**
10945 * @file loader.js
10946 */
10947
10948/** @import Player from '../player' */
10949
10950/**
10951 * The `MediaLoader` is the `Component` that decides which playback technology to load
10952 * when a player is initialized.
10953 *
10954 * @extends Component
10955 */
10956class MediaLoader extends Component$1 {
10957  /**
10958   * Create an instance of this class.
10959   *
10960   * @param {Player} player
10961   *        The `Player` that this class should attach to.
10962   *
10963   * @param {Object} [options]
10964   *        The key/value store of player options.
10965   *
10966   * @param {Function} [ready]
10967   *        The function that is run when this component is ready.
10968   */
10969  constructor(player, options, ready) {
10970    // MediaLoader has no element
10971    const options_ = merge$1(
10972      {
10973        createEl: false,
10974      },
10975      options
10976    );
10977    super(player, options_, ready);
10978
10979    // If there are no sources when the player is initialized,
10980    // load the first supported playback technology.
10981
10982    if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
10983      for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
10984        const techName = toTitleCase$1(j[i]);
10985        let tech = Tech.getTech(techName);
10986
10987        // Support old behavior of techs being registered as components.
10988        // Remove once that deprecated behavior is removed.
10989        if (!techName) {
10990          tech = Component$1.getComponent(techName);
10991        }
10992
10993        // Check if the browser supports this technology
10994        if (tech && tech.isSupported()) {
10995          player.loadTech_(techName);
10996          break;
10997        }
10998      }
10999    } else {
11000      // Loop through playback technologies (e.g. HTML5) and check for support.
11001      // Then load the best source.
11002      // A few assumptions here:
11003      //   All playback technologies respect preload false.
11004      player.src(options.playerOptions.sources);
11005    }
11006  }
11007}
11008Component$1.registerComponent('MediaLoader', MediaLoader);
11009
11010/**
11011 * @file clickable-component.js
11012 */
11013
11014/** @import Player from './player' */
11015
11016/**
11017 * Component which is clickable or keyboard actionable, but is not a
11018 * native HTML button.
11019 *
11020 * @extends Component
11021 */
11022class ClickableComponent extends Component$1 {
11023  /**
11024   * Creates an instance of this class.
11025   *
11026   * @param  {Player} player
11027   *         The `Player` that this class should be attached to.
11028   *
11029   * @param  {Object} [options]
11030   *         The key/value store of component options.
11031   *
11032   * @param  {function} [options.clickHandler]
11033   *         The function to call when the button is clicked / activated
11034   *
11035   * @param  {string} [options.controlText]
11036   *         The text to set on the button
11037   *
11038   * @param  {string} [options.className]
11039   *         A class or space separated list of classes to add the component
11040   *
11041   */
11042  constructor(player, options) {
11043    super(player, options);
11044    if (this.options_.controlText) {
11045      this.controlText(this.options_.controlText);
11046    }
11047    this.handleMouseOver_ = (e) => this.handleMouseOver(e);
11048    this.handleMouseOut_ = (e) => this.handleMouseOut(e);
11049    this.handleClick_ = (e) => this.handleClick(e);
11050    this.handleKeyDown_ = (e) => this.handleKeyDown(e);
11051    this.emitTapEvents();
11052    this.enable();
11053  }
11054
11055  /**
11056   * Create the `ClickableComponent`s DOM element.
11057   *
11058   * @param {string} [tag=div]
11059   *        The element's node type.
11060   *
11061   * @param {Object} [props={}]
11062   *        An object of properties that should be set on the element.
11063   *
11064   * @param {Object} [attributes={}]
11065   *        An object of attributes that should be set on the element.
11066   *
11067   * @return {Element}
11068   *         The element that gets created.
11069   */
11070  createEl(tag = 'div', props = {}, attributes = {}) {
11071    props = Object.assign(
11072      {
11073        className: this.buildCSSClass(),
11074        tabIndex: 0,
11075      },
11076      props
11077    );
11078    if (tag === 'button') {
11079      log$1.error(
11080        `Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`
11081      );
11082    }
11083
11084    // Add ARIA attributes for clickable element which is not a native HTML button
11085    attributes = Object.assign(
11086      {
11087        role: 'button',
11088      },
11089      attributes
11090    );
11091    this.tabIndex_ = props.tabIndex;
11092    const el = createEl(tag, props, attributes);
11093    if (!this.player_.options_.experimentalSvgIcons) {
11094      el.appendChild(
11095        createEl(
11096          'span',
11097          {
11098            className: 'vjs-icon-placeholder',
11099          },
11100          {
11101            'aria-hidden': true,
11102          }
11103        )
11104      );
11105    }
11106    this.createControlTextEl(el);
11107    return el;
11108  }
11109  dispose() {
11110    // remove controlTextEl_ on dispose
11111    this.controlTextEl_ = null;
11112    super.dispose();
11113  }
11114
11115  /**
11116   * Create a control text element on this `ClickableComponent`
11117   *
11118   * @param {Element} [el]
11119   *        Parent element for the control text.
11120   *
11121   * @return {Element}
11122   *         The control text element that gets created.
11123   */
11124  createControlTextEl(el) {
11125    this.controlTextEl_ = createEl(
11126      'span',
11127      {
11128        className: 'vjs-control-text',
11129      },
11130      {
11131        // let the screen reader user know that the text of the element may change
11132        'aria-live': 'polite',
11133      }
11134    );
11135    if (el) {
11136      el.appendChild(this.controlTextEl_);
11137    }
11138    this.controlText(this.controlText_, el);
11139    return this.controlTextEl_;
11140  }
11141
11142  /**
11143   * Get or set the localize text to use for the controls on the `ClickableComponent`.
11144   *
11145   * @param {string} [text]
11146   *        Control text for element.
11147   *
11148   * @param {Element} [el=this.el()]
11149   *        Element to set the title on.
11150   *
11151   * @return {string}
11152   *         - The control text when getting
11153   */
11154  controlText(text, el = this.el()) {
11155    if (text === undefined) {
11156      return this.controlText_ || 'Need Text';
11157    }
11158    const localizedText = this.localize(text);
11159
11160    /** @protected */
11161    this.controlText_ = text;
11162    textContent(this.controlTextEl_, localizedText);
11163    if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
11164      // Set title attribute if only an icon is shown
11165      el.setAttribute('title', localizedText);
11166    }
11167  }
11168
11169  /**
11170   * Builds the default DOM `className`.
11171   *
11172   * @return {string}
11173   *         The DOM `className` for this object.
11174   */
11175  buildCSSClass() {
11176    return `vjs-control vjs-button ${super.buildCSSClass()}`;
11177  }
11178
11179  /**
11180   * Enable this `ClickableComponent`
11181   */
11182  enable() {
11183    if (!this.enabled_) {
11184      this.enabled_ = true;
11185      this.removeClass('vjs-disabled');
11186      this.el_.setAttribute('aria-disabled', 'false');
11187      if (typeof this.tabIndex_ !== 'undefined') {
11188        this.el_.setAttribute('tabIndex', this.tabIndex_);
11189      }
11190      this.on(['tap', 'click'], this.handleClick_);
11191      this.on('keydown', this.handleKeyDown_);
11192    }
11193  }
11194
11195  /**
11196   * Disable this `ClickableComponent`
11197   */
11198  disable() {
11199    this.enabled_ = false;
vendor: 9,188 bytes, lines 11200-11537
11200    this.addClass('vjs-disabled');
11201    this.el_.setAttribute('aria-disabled', 'true');
11202    if (typeof this.tabIndex_ !== 'undefined') {
11203      this.el_.removeAttribute('tabIndex');
11204    }
11205    this.off('mouseover', this.handleMouseOver_);
11206    this.off('mouseout', this.handleMouseOut_);
11207    this.off(['tap', 'click'], this.handleClick_);
11208    this.off('keydown', this.handleKeyDown_);
11209  }
11210
11211  /**
11212   * Handles language change in ClickableComponent for the player in components
11213   *
11214   *
11215   */
11216  handleLanguagechange() {
11217    this.controlText(this.controlText_);
11218  }
11219
11220  /**
11221   * Event handler that is called when a `ClickableComponent` receives a
11222   * `click` or `tap` event.
11223   *
11224   * @param {Event} event
11225   *        The `tap` or `click` event that caused this function to be called.
11226   *
11227   * @listens tap
11228   * @listens click
11229   * @abstract
11230   */
11231  handleClick(event) {
11232    if (this.options_.clickHandler) {
11233      this.options_.clickHandler.call(this, arguments);
11234    }
11235  }
11236
11237  /**
11238   * Event handler that is called when a `ClickableComponent` receives a
11239   * `keydown` event.
11240   *
11241   * By default, if the key is Space or Enter, it will trigger a `click` event.
11242   *
11243   * @param {KeyboardEvent} event
11244   *        The `keydown` event that caused this function to be called.
11245   *
11246   * @listens keydown
11247   */
11248  handleKeyDown(event) {
11249    // Support Space or Enter key operation to fire a click event. Also,
11250    // prevent the event from propagating through the DOM and triggering
11251    // Player hotkeys.
11252    if (event.key === ' ' || event.key === 'Enter') {
11253      event.preventDefault();
11254      event.stopPropagation();
11255      this.trigger('click');
11256    } else {
11257      // Pass keypress handling up for unsupported keys
11258      super.handleKeyDown(event);
11259    }
11260  }
11261}
11262Component$1.registerComponent('ClickableComponent', ClickableComponent);
11263
11264/**
11265 * @file poster-image.js
11266 */
11267
11268/** @import Player from './player' */
11269
11270/**
11271 * A `ClickableComponent` that handles showing the poster image for the player.
11272 *
11273 * @extends ClickableComponent
11274 */
11275class PosterImage extends ClickableComponent {
11276  /**
11277   * Create an instance of this class.
11278   *
11279   * @param {Player} player
11280   *        The `Player` that this class should attach to.
11281   *
11282   * @param {Object} [options]
11283   *        The key/value store of player options.
11284   */
11285  constructor(player, options) {
11286    super(player, options);
11287    this.update();
11288    this.update_ = (e) => this.update(e);
11289    player.on('posterchange', this.update_);
11290  }
11291
11292  /**
11293   * Clean up and dispose of the `PosterImage`.
11294   */
11295  dispose() {
11296    this.player().off('posterchange', this.update_);
11297    super.dispose();
11298  }
11299
11300  /**
11301   * Create the `PosterImage`s DOM element.
11302   *
11303   * @return {Element}
11304   *         The element that gets created.
11305   */
11306  createEl() {
11307    // The el is an empty div to keep position in the DOM
11308    // A picture and img el will be inserted when a source is set
11309    return createEl('div', {
11310      className: 'vjs-poster',
11311    });
11312  }
11313
11314  /**
11315   * Get or set the `PosterImage`'s crossOrigin option.
11316   *
11317   * @param {string|null} [value]
11318   *        The value to set the crossOrigin to. If an argument is
11319   *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
11320   *
11321   * @return {string|null}
11322   *         - The current crossOrigin value of the `Player` when getting.
11323   *         - undefined when setting
11324   */
11325  crossOrigin(value) {
11326    // `null` can be set to unset a value
11327    if (typeof value === 'undefined') {
11328      if (this.$('img')) {
11329        // If the poster's element exists, give its value
11330        return this.$('img').crossOrigin;
11331      } else if (this.player_.tech_ && this.player_.tech_.isReady_) {
11332        // If not but the tech is ready, query the tech
11333        return this.player_.crossOrigin();
11334      }
11335      // Otherwise check options as the  poster is usually set before the state of crossorigin
11336      // can be retrieved by the getter
11337      return this.player_.options_.crossOrigin || this.player_.options_.crossorigin || null;
11338    }
11339    if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
11340      this.player_.log.warn(
11341        `crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`
11342      );
11343      return;
11344    }
11345    if (this.$('img')) {
11346      this.$('img').crossOrigin = value;
11347    }
11348    return;
11349  }
11350
11351  /**
11352   * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
11353   *
11354   * @listens Player#posterchange
11355   *
11356   * @param {Event} [event]
11357   *        The `Player#posterchange` event that triggered this function.
11358   */
11359  update(event) {
11360    const url = this.player().poster();
11361    this.setSrc(url);
11362
11363    // If there's no poster source we should display:none on this component
11364    // so it's not still clickable or right-clickable
11365    if (url) {
11366      this.show();
11367    } else {
11368      this.hide();
11369    }
11370  }
11371
11372  /**
11373   * Set the source of the `PosterImage` depending on the display method. (Re)creates
11374   * the inner picture and img elementss when needed.
11375   *
11376   * @param {string} [url]
11377   *        The URL to the source for the `PosterImage`. If not specified or falsy,
11378   *        any source and ant inner picture/img are removed.
11379   */
11380  setSrc(url) {
11381    if (!url) {
11382      this.el_.textContent = '';
11383      return;
11384    }
11385    if (!this.$('img')) {
11386      this.el_.appendChild(
11387        createEl(
11388          'picture',
11389          {
11390            className: 'vjs-poster',
11391            // Don't want poster to be tabbable.
11392            tabIndex: -1,
11393          },
11394          {},
11395          createEl(
11396            'img',
11397            {
11398              loading: 'lazy',
11399              crossOrigin: this.crossOrigin(),
11400            },
11401            {
11402              alt: '',
11403            }
11404          )
11405        )
11406      );
11407    }
11408    this.$('img').src = url;
11409  }
11410
11411  /**
11412   * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
11413   * {@link ClickableComponent#handleClick} for instances where this will be triggered.
11414   *
11415   * @listens tap
11416   * @listens click
11417   * @listens keydown
11418   *
11419   * @param {Event} event
11420   +        The `click`, `tap` or `keydown` event that caused this function to be called.
11421   */
11422  handleClick(event) {
11423    // We don't want a click to trigger playback when controls are disabled
11424    if (!this.player_.controls()) {
11425      return;
11426    }
11427    if (this.player_.tech(true)) {
11428      this.player_.tech(true).focus();
11429    }
11430    if (this.player_.paused()) {
11431      silencePromise(this.player_.play());
11432    } else {
11433      this.player_.pause();
11434    }
11435  }
11436}
11437
11438/**
11439 * Get or set the `PosterImage`'s crossorigin option. For the HTML5 player, this
11440 * sets the `crossOrigin` property on the `<img>` tag to control the CORS
11441 * behavior.
11442 *
11443 * @param {string|null} [value]
11444 *        The value to set the `PosterImages`'s crossorigin to. If an argument is
11445 *        given, must be one of `anonymous` or `use-credentials`.
11446 *
11447 * @return {string|null|undefined}
11448 *         - The current crossorigin value of the `Player` when getting.
11449 *         - undefined when setting
11450 */
11451PosterImage.prototype.crossorigin = PosterImage.prototype.crossOrigin;
11452Component$1.registerComponent('PosterImage', PosterImage);
11453
11454/**
11455 * @file text-track-display.js
11456 */
11457
11458/** @import Player from '../player' */
11459
11460const darkGray = '#222';
11461const lightGray = '#ccc';
11462const fontMap = {
11463  monospace: 'monospace',
11464  sansSerif: 'sans-serif',
11465  serif: 'serif',
11466  monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
11467  monospaceSerif: '"Courier New", monospace',
11468  proportionalSansSerif: 'sans-serif',
11469  proportionalSerif: 'serif',
11470  casual: '"Comic Sans MS", Impact, fantasy',
11471  script: '"Monotype Corsiva", cursive',
11472  smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif',
11473};
11474
11475/**
11476 * Construct an rgba color from a given hex color code.
11477 *
11478 * @param {number} color
11479 *        Hex number for color, like #f0e or #f604e2.
11480 *
11481 * @param {number} opacity
11482 *        Value for opacity, 0.0 - 1.0.
11483 *
11484 * @return {string}
11485 *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
11486 */
11487function constructColor(color, opacity) {
11488  let hex;
11489  if (color.length === 4) {
11490    // color looks like "#f0e"
11491    hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
11492  } else if (color.length === 7) {
11493    // color looks like "#f604e2"
11494    hex = color.slice(1);
11495  } else {
11496    throw new Error(
11497      'Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'
11498    );
11499  }
11500  return (
11501    'rgba(' +
11502    parseInt(hex.slice(0, 2), 16) +
11503    ',' +
11504    parseInt(hex.slice(2, 4), 16) +
11505    ',' +
11506    parseInt(hex.slice(4, 6), 16) +
11507    ',' +
11508    opacity +
11509    ')'
11510  );
11511}
11512
11513/**
11514 * Try to update the style of a DOM element. Some style changes will throw an error,
11515 * particularly in IE8. Those should be noops.
11516 *
11517 * @param {Element} el
11518 *        The DOM element to be styled.
11519 *
11520 * @param {string} style
11521 *        The CSS property on the element that should be styled.
11522 *
11523 * @param {string} rule
11524 *        The style rule that should be applied to the property.
11525 *
11526 * @private
11527 */
11528function tryUpdateStyle(el, style, rule) {
11529  try {
11530    el.style[style] = rule;
11531  } catch (e) {
11532    // Satisfies linter.
11533    return;
11534  }
11535}
11536
11537/**
11538 * Converts the CSS top/right/bottom/left property numeric value to string in pixels.
11539 *
11540 * @param {number} position
11541 *        The CSS top/right/bottom/left property value.
11542 *
11543 * @return {string}
11544 *          The CSS property value that was created, like '10px'.
11545 *
11546 * @private
11547 */
11548function getCSSPositionValue(position) {
11549  return position ? `${position}px` : '';
11550}
11551
11552/**
11553 * The component for displaying text track cues.
11554 *
11555 * @extends Component
11556 */
11557class TextTrackDisplay extends Component$1 {
11558  /**
11559   * Creates an instance of this class.
11560   *
11561   * @param {Player} player
11562   *        The `Player` that this class should be attached to.
11563   *
11564   * @param {Object} [options]
11565   *        The key/value store of player options.
11566   *
11567   * @param {Function} [ready]
11568   *        The function to call when `TextTrackDisplay` is ready.
11569   */
11570  constructor(player, options, ready) {
11571    super(player, options, ready);
11572    const updateDisplayTextHandler = (e) => this.updateDisplay(e);
11573    const updateDisplayHandler = (e) => {
11574      this.updateDisplayOverlay();
11575      this.updateDisplay(e);
11576    };
11577    player.on('loadstart', (e) => this.toggleDisplay(e));
11578    player.on('texttrackchange', updateDisplayTextHandler);
11579    player.on('loadedmetadata', (e) => {
11580      this.updateDisplayOverlay();
11581      this.preselectTrack(e);
11582    });
11583
11584    // This used to be called during player init, but was causing an error
11585    // if a track should show by default and the display hadn't loaded yet.
11586    // Should probably be moved to an external track loader when we support
11587    // tracks that don't need a display.
11588    player.ready(
11589      bind_(this, function () {
11590        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
11591          this.hide();
11592          return;
11593        }
11594        player.on('fullscreenchange', updateDisplayHandler);
11595        player.on('playerresize', updateDisplayHandler);
11596        const screenOrientation =
11597          window__default['default'].screen.orientation || window__default['default'];
11598        const changeOrientationEvent = window__default['default'].screen.orientation
11599          ? 'change'
11600          : 'orientationchange';
11601        screenOrientation.addEventListener(changeOrientationEvent, updateDisplayHandler);
11602        player.on('dispose', () =>
11603          screenOrientation.removeEventListener(changeOrientationEvent, updateDisplayHandler)
11604        );
11605        const tracks = this.options_.playerOptions.tracks || [];
11606        for (let i = 0; i < tracks.length; i++) {
11607          this.player_.addRemoteTextTrack(tracks[i], true);
11608        }
11609        this.preselectTrack();
11610      })
11611    );
11612  }
11613
11614  /**
11615   * Preselect a track following this precedence:
11616   * - matches the previously selected {@link TextTrack}'s language and kind
11617   * - matches the previously selected {@link TextTrack}'s language only
11618   * - is the first default captions track
11619   * - is the first default descriptions track
11620   *
11621   * @listens Player#loadstart
11622   */
11623  preselectTrack() {
11624    const modes = {
11625      captions: 1,
11626      subtitles: 1,
11627    };
11628    const trackList = this.player_.textTracks();
11629    const userPref = this.player_.cache_.selectedLanguage;
11630    let firstDesc;
11631    let firstCaptions;
11632    let preferredTrack;
11633    for (let i = 0; i < trackList.length; i++) {
11634      const track = trackList[i];
11635      if (
11636        userPref &&
11637        userPref.enabled &&
11638        userPref.language &&
11639        userPref.language === track.language &&
11640        track.kind in modes
11641      ) {
11642        // Always choose the track that matches both language and kind
11643        if (track.kind === userPref.kind) {
11644          preferredTrack = track;
11645          // or choose the first track that matches language
11646        } else if (!preferredTrack) {
11647          preferredTrack = track;
11648        }
11649
11650        // clear everything if offTextTrackMenuItem was clicked
11651      } else if (userPref && !userPref.enabled) {
11652        preferredTrack = null;
11653        firstDesc = null;
11654        firstCaptions = null;
11655      } else if (track.default) {
11656        if (track.kind === 'descriptions' && !firstDesc) {
11657          firstDesc = track;
11658        } else if (track.kind in modes && !firstCaptions) {
11659          firstCaptions = track;
11660        }
11661      }
11662    }
11663
11664    // The preferredTrack matches the user preference and takes
11665    // precedence over all the other tracks.
11666    // So, display the preferredTrack before the first default track
11667    // and the subtitles/captions track before the descriptions track
11668    if (preferredTrack) {
11669      preferredTrack.mode = 'showing';
11670    } else if (firstCaptions) {
11671      firstCaptions.mode = 'showing';
11672    } else if (firstDesc) {
11673      firstDesc.mode = 'showing';
11674    }
11675  }
11676
11677  /**
11678   * Turn display of {@link TextTrack}'s from the current state into the other state.
11679   * There are only two states:
11680   * - 'shown'
11681   * - 'hidden'
11682   *
11683   * @listens Player#loadstart
11684   */
11685  toggleDisplay() {
11686    if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
11687      this.hide();
11688    } else {
11689      this.show();
11690    }
11691  }
11692
11693  /**
11694   * Create the {@link Component}'s DOM element.
11695   *
11696   * @return {Element}
11697   *         The element that was created.
11698   */
11699  createEl() {
11700    return super.createEl(
11701      'div',
11702      {
11703        className: 'vjs-text-track-display',
11704      },
11705      {
11706        translate: 'yes',
11707        'aria-live': 'off',
11708        'aria-atomic': 'true',
11709      }
11710    );
11711  }
11712
11713  /**
11714   * Clear all displayed {@link TextTrack}s.
11715   */
11716  clearDisplay() {
11717    if (typeof window__default['default'].WebVTT === 'function') {
11718      window__default['default'].WebVTT.processCues(window__default['default'], [], this.el_);
11719    }
11720  }
11721
11722  /**
11723   * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
11724   * a {@link Player#fullscreenchange} is fired.
11725   *
11726   * @listens Player#texttrackchange
11727   * @listens Player#fullscreenchange
11728   */
11729  updateDisplay() {
11730    const tracks = this.player_.textTracks();
11731    const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
11732    this.clearDisplay();
11733    if (allowMultipleShowingTracks) {
11734      const showingTracks = [];
11735      for (let i = 0; i < tracks.length; ++i) {
11736        const track = tracks[i];
11737        if (track.mode !== 'showing') {
11738          continue;
11739        }
11740        showingTracks.push(track);
11741      }
11742      this.updateForTrack(showingTracks);
11743      return;
11744    }
11745
11746    //  Track display prioritization model: if multiple tracks are 'showing',
11747    //  display the first 'subtitles' or 'captions' track which is 'showing',
11748    //  otherwise display the first 'descriptions' track which is 'showing'
11749
11750    let descriptionsTrack = null;
11751    let captionsSubtitlesTrack = null;
11752    let i = tracks.length;
11753    while (i--) {
11754      const track = tracks[i];
11755      if (track.mode === 'showing') {
11756        if (track.kind === 'descriptions') {
11757          descriptionsTrack = track;
11758        } else {
11759          captionsSubtitlesTrack = track;
11760        }
11761      }
11762    }
11763    if (captionsSubtitlesTrack) {
11764      if (this.getAttribute('aria-live') !== 'off') {
11765        this.setAttribute('aria-live', 'off');
11766      }
11767      this.updateForTrack(captionsSubtitlesTrack);
11768    } else if (descriptionsTrack) {
11769      if (this.getAttribute('aria-live') !== 'assertive') {
11770        this.setAttribute('aria-live', 'assertive');
11771      }
11772      this.updateForTrack(descriptionsTrack);
11773    }
11774    if (!window__default['default'].CSS.supports('inset', '10px')) {
11775      const textTrackDisplay = this.el_;
11776      const vjsTextTrackCues = textTrackDisplay.querySelectorAll('.vjs-text-track-cue');
11777      const controlBarHeight = this.player_.controlBar.el_.getBoundingClientRect().height;
11778      const playerHeight = this.player_.el_.getBoundingClientRect().height;
11779
11780      // Clear inline style before getting actual height of textTrackDisplay
11781      textTrackDisplay.style = '';
11782
11783      // textrack style updates, this styles are required to be inline
vendor: 4,177 bytes, lines 11784-11896
11784      tryUpdateStyle(textTrackDisplay, 'position', 'relative');
11785      tryUpdateStyle(textTrackDisplay, 'height', playerHeight - controlBarHeight + 'px');
11786      tryUpdateStyle(textTrackDisplay, 'top', 'unset');
11787      if (IS_SMART_TV) {
11788        tryUpdateStyle(textTrackDisplay, 'bottom', playerHeight + 'px');
11789      } else {
11790        tryUpdateStyle(textTrackDisplay, 'bottom', '0px');
11791      }
11792
11793      // vjsTextTrackCue style updates
11794      if (vjsTextTrackCues.length > 0) {
11795        vjsTextTrackCues.forEach((vjsTextTrackCue) => {
11796          // verify if inset styles are inline
11797          if (vjsTextTrackCue.style.inset) {
11798            const insetStyles = vjsTextTrackCue.style.inset.split(' ');
11799
11800            // expected value is always 3
11801            if (insetStyles.length === 3) {
11802              Object.assign(vjsTextTrackCue.style, {
11803                top: insetStyles[0],
11804                right: insetStyles[1],
11805                bottom: insetStyles[2],
11806                left: 'unset',
11807              });
11808            }
11809          }
11810        });
11811      }
11812    }
11813  }
11814
11815  /**
11816   * Updates the displayed TextTrack to be sure it overlays the video when a either
11817   * a {@link Player#texttrackchange} or a {@link Player#fullscreenchange} is fired.
11818   */
11819  updateDisplayOverlay() {
11820    // inset-inline and inset-block are not supprted on old chrome, but these are
11821    // only likely to be used on TV devices
11822    if (
11823      !this.player_.videoHeight() ||
11824      !window__default['default'].CSS.supports('inset-inline: 10px')
11825    ) {
11826      return;
11827    }
11828    const playerWidth = this.player_.currentWidth();
11829    const playerHeight = this.player_.currentHeight();
11830    const playerAspectRatio = playerWidth / playerHeight;
11831    const videoAspectRatio = this.player_.videoWidth() / this.player_.videoHeight();
11832    let insetInlineMatch = 0;
11833    let insetBlockMatch = 0;
11834    if (Math.abs(playerAspectRatio - videoAspectRatio) > 0.1) {
11835      if (playerAspectRatio > videoAspectRatio) {
11836        insetInlineMatch = Math.round((playerWidth - playerHeight * videoAspectRatio) / 2);
11837      } else {
11838        insetBlockMatch = Math.round((playerHeight - playerWidth / videoAspectRatio) / 2);
11839      }
11840    }
11841    tryUpdateStyle(this.el_, 'insetInline', getCSSPositionValue(insetInlineMatch));
11842    tryUpdateStyle(this.el_, 'insetBlock', getCSSPositionValue(insetBlockMatch));
11843  }
11844
11845  /**
11846   * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
11847   *
11848   * @param {TextTrack} track
11849   *        Text track object containing active cues to style.
11850   */
11851  updateDisplayState(track) {
11852    const overrides = this.player_.textTrackSettings.getValues();
11853    const cues = track.activeCues;
11854    let i = cues.length;
11855    while (i--) {
11856      const cue = cues[i];
11857      if (!cue) {
11858        continue;
11859      }
11860      const cueDiv = cue.displayState;
11861      if (overrides.color) {
11862        cueDiv.firstChild.style.color = overrides.color;
11863      }
11864      if (overrides.textOpacity) {
11865        tryUpdateStyle(
11866          cueDiv.firstChild,
11867          'color',
11868          constructColor(overrides.color || '#fff', overrides.textOpacity)
11869        );
11870      }
11871      if (overrides.backgroundColor) {
11872        cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
11873      }
11874      if (overrides.backgroundOpacity) {
11875        tryUpdateStyle(
11876          cueDiv.firstChild,
11877          'backgroundColor',
11878          constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)
11879        );
11880      }
11881      if (overrides.windowColor) {
11882        if (overrides.windowOpacity) {
11883          tryUpdateStyle(
11884            cueDiv,
11885            'backgroundColor',
11886            constructColor(overrides.windowColor, overrides.windowOpacity)
11887          );
11888        } else {
11889          cueDiv.style.backgroundColor = overrides.windowColor;
11890        }
11891      }
11892      if (overrides.edgeStyle) {
11893        if (overrides.edgeStyle === 'dropshadow') {
11894          cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}`;
11895        } else if (overrides.edgeStyle === 'raised') {
11896          cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}`;
vendor: 7,593 bytes, lines 11897-12158
11897        } else if (overrides.edgeStyle === 'depressed') {
11898          cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
11899        } else if (overrides.edgeStyle === 'uniform') {
11900          cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
11901        }
11902      }
11903      if (overrides.fontPercent && overrides.fontPercent !== 1) {
11904        const fontSize = window__default['default'].parseFloat(cueDiv.style.fontSize);
11905        cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
11906        cueDiv.style.height = 'auto';
11907        cueDiv.style.top = 'auto';
11908      }
11909      if (overrides.fontFamily && overrides.fontFamily !== 'default') {
11910        if (overrides.fontFamily === 'small-caps') {
11911          cueDiv.firstChild.style.fontVariant = 'small-caps';
11912        } else {
11913          cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
11914        }
11915      }
11916    }
11917  }
11918
11919  /**
11920   * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
11921   *
11922   * @param {TextTrack|TextTrack[]} tracks
11923   *        Text track object or text track array to be added to the list.
11924   */
11925  updateForTrack(tracks) {
11926    if (!Array.isArray(tracks)) {
11927      tracks = [tracks];
11928    }
11929    if (
11930      typeof window__default['default'].WebVTT !== 'function' ||
11931      tracks.every((track) => {
11932        return !track.activeCues;
11933      })
11934    ) {
11935      return;
11936    }
11937    const cues = [];
11938
11939    // push all active track cues
11940    for (let i = 0; i < tracks.length; ++i) {
11941      const track = tracks[i];
11942      for (let j = 0; j < track.activeCues.length; ++j) {
11943        cues.push(track.activeCues[j]);
11944      }
11945    }
11946
11947    // removes all cues before it processes new ones
11948    window__default['default'].WebVTT.processCues(window__default['default'], cues, this.el_);
11949
11950    // add unique class to each language text track & add settings styling if necessary
11951    for (let i = 0; i < tracks.length; ++i) {
11952      const track = tracks[i];
11953      for (let j = 0; j < track.activeCues.length; ++j) {
11954        const cueEl = track.activeCues[j].displayState;
11955        addClass(
11956          cueEl,
11957          'vjs-text-track-cue',
11958          'vjs-text-track-cue-' + (track.language ? track.language : i)
11959        );
11960        if (track.language) {
11961          setAttribute(cueEl, 'lang', track.language);
11962        }
11963      }
11964      if (this.player_.textTrackSettings) {
11965        this.updateDisplayState(track);
11966      }
11967    }
11968  }
11969}
11970Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
11971
11972/**
11973 * @file loading-spinner.js
11974 */
11975
11976/**
11977 * A loading spinner for use during waiting/loading events.
11978 *
11979 * @extends Component
11980 */
11981class LoadingSpinner extends Component$1 {
11982  /**
11983   * Create the `LoadingSpinner`s DOM element.
11984   *
11985   * @return {Element}
11986   *         The dom element that gets created.
11987   */
11988  createEl() {
11989    const isAudio = this.player_.isAudio();
11990    const playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
11991    const controlText = createEl('span', {
11992      className: 'vjs-control-text',
11993      textContent: this.localize('{1} is loading.', [playerType]),
11994    });
11995    const el = super.createEl('div', {
11996      className: 'vjs-loading-spinner',
11997      dir: 'ltr',
11998    });
11999    el.appendChild(controlText);
12000    return el;
12001  }
12002
12003  /**
12004   * Update control text on languagechange
12005   */
12006  handleLanguagechange() {
12007    this.$('.vjs-control-text').textContent = this.localize('{1} is loading.', [
12008      this.player_.isAudio() ? 'Audio Player' : 'Video Player',
12009    ]);
12010  }
12011}
12012Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
12013
12014/**
12015 * @file button.js
12016 */
12017
12018/**
12019 * Base class for all buttons.
12020 *
12021 * @extends ClickableComponent
12022 */
12023class Button extends ClickableComponent {
12024  /**
12025   * Create the `Button`s DOM element.
12026   *
12027   * @param {string} [tag="button"]
12028   *        The element's node type. This argument is IGNORED: no matter what
12029   *        is passed, it will always create a `button` element.
12030   *
12031   * @param {Object} [props={}]
12032   *        An object of properties that should be set on the element.
12033   *
12034   * @param {Object} [attributes={}]
12035   *        An object of attributes that should be set on the element.
12036   *
12037   * @return {Element}
12038   *         The element that gets created.
12039   */
12040  createEl(tag, props = {}, attributes = {}) {
12041    tag = 'button';
12042    props = Object.assign(
12043      {
12044        className: this.buildCSSClass(),
12045      },
12046      props
12047    );
12048
12049    // Add attributes for button element
12050    attributes = Object.assign(
12051      {
12052        // Necessary since the default button type is "submit"
12053        type: 'button',
12054      },
12055      attributes
12056    );
12057    const el = createEl(tag, props, attributes);
12058    if (!this.player_.options_.experimentalSvgIcons) {
12059      el.appendChild(
12060        createEl(
12061          'span',
12062          {
12063            className: 'vjs-icon-placeholder',
12064          },
12065          {
12066            'aria-hidden': true,
12067          }
12068        )
12069      );
12070    }
12071    this.createControlTextEl(el);
12072    return el;
12073  }
12074
12075  /**
12076   * Add a child `Component` inside of this `Button`.
12077   *
12078   * @param {string|Component} child
12079   *        The name or instance of a child to add.
12080   *
12081   * @param {Object} [options={}]
12082   *        The key/value store of options that will get passed to children of
12083   *        the child.
12084   *
12085   * @return {Component}
12086   *         The `Component` that gets added as a child. When using a string the
12087   *         `Component` will get created by this process.
12088   *
12089   * @deprecated since version 5
12090   */
12091  addChild(child, options = {}) {
12092    const className = this.constructor.name;
12093    log$1.warn(
12094      `Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`
12095    );
12096
12097    // Avoid the error message generated by ClickableComponent's addChild method
12098    return Component$1.prototype.addChild.call(this, child, options);
12099  }
12100
12101  /**
12102   * Enable the `Button` element so that it can be activated or clicked. Use this with
12103   * {@link Button#disable}.
12104   */
12105  enable() {
12106    super.enable();
12107    this.el_.removeAttribute('disabled');
12108  }
12109
12110  /**
12111   * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12112   * {@link Button#enable}.
12113   */
12114  disable() {
12115    super.disable();
12116    this.el_.setAttribute('disabled', 'disabled');
12117  }
12118
12119  /**
12120   * This gets called when a `Button` has focus and `keydown` is triggered via a key
12121   * press.
12122   *
12123   * @param {KeyboardEvent} event
12124   *        The event that caused this function to get called.
12125   *
12126   * @listens keydown
12127   */
12128  handleKeyDown(event) {
12129    // Ignore Space or Enter key operation, which is handled by the browser for
12130    // a button - though not for its super class, ClickableComponent. Also,
12131    // prevent the event from propagating through the DOM and triggering Player
12132    // hotkeys. We do not preventDefault here because we _want_ the browser to
12133    // handle it.
12134    if (event.key === ' ' || event.key === 'Enter') {
12135      event.stopPropagation();
12136      return;
12137    }
12138
12139    // Pass keypress handling up for unsupported keys
12140    super.handleKeyDown(event);
12141  }
12142}
12143Component$1.registerComponent('Button', Button);
12144
12145/**
12146 * @file big-play-button.js
12147 */
12148
12149/**
12150 * The initial play button that shows before the video has played. The hiding of the
12151 * `BigPlayButton` get done via CSS and `Player` states.
12152 *
12153 * @extends Button
12154 */
12155class BigPlayButton extends Button {
12156  constructor(player, options) {
12157    super(player, options);
12158    this.mouseused_ = false;
vendor: 4,092 bytes, lines 12159-12318
12159    this.setIcon('play');
12160    this.on('mousedown', (e) => this.handleMouseDown(e));
12161  }
12162
12163  /**
12164   * Builds the default DOM `className`.
12165   *
12166   * @return {string}
12167   *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12168   */
12169  buildCSSClass() {
12170    return 'vjs-big-play-button';
12171  }
12172
12173  /**
12174   * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12175   * for more detailed information on what a click can be.
12176   *
12177   * @param {KeyboardEvent|MouseEvent|TouchEvent} event
12178   *        The `keydown`, `tap`, or `click` event that caused this function to be
12179   *        called.
12180   *
12181   * @listens tap
12182   * @listens click
12183   */
12184  handleClick(event) {
12185    const playPromise = this.player_.play();
12186
12187    // exit early if clicked via the mouse
12188    if (this.mouseused_ && 'clientX' in event && 'clientY' in event) {
12189      silencePromise(playPromise);
12190      if (this.player_.tech(true)) {
12191        this.player_.tech(true).focus();
12192      }
12193      return;
12194    }
12195    const cb = this.player_.getChild('controlBar');
12196    const playToggle = cb && cb.getChild('playToggle');
12197    if (!playToggle) {
12198      this.player_.tech(true).focus();
12199      return;
12200    }
12201    const playFocus = () => playToggle.focus();
12202    if (isPromise(playPromise)) {
12203      playPromise.then(playFocus, () => {});
12204    } else {
12205      this.setTimeout(playFocus, 1);
12206    }
12207  }
12208
12209  /**
12210   * Event handler that is called when a `BigPlayButton` receives a
12211   * `keydown` event.
12212   *
12213   * @param {KeyboardEvent} event
12214   *        The `keydown` event that caused this function to be called.
12215   *
12216   * @listens keydown
12217   */
12218  handleKeyDown(event) {
12219    this.mouseused_ = false;
12220    super.handleKeyDown(event);
12221  }
12222
12223  /**
12224   * Handle `mousedown` events on the `BigPlayButton`.
12225   *
12226   * @param {MouseEvent} event
12227   *        `mousedown` or `touchstart` event that triggered this function
12228   *
12229   * @listens mousedown
12230   */
12231  handleMouseDown(event) {
12232    this.mouseused_ = true;
12233  }
12234}
12235
12236/**
12237 * The text that should display over the `BigPlayButton`s controls. Added to for localization.
12238 *
12239 * @type {string}
12240 * @protected
12241 */
12242BigPlayButton.prototype.controlText_ = 'Play Video';
12243Component$1.registerComponent('BigPlayButton', BigPlayButton);
12244
12245/**
12246 * @file close-button.js
12247 */
12248
12249/** @import Player from './player' */
12250
12251/**
12252 * The `CloseButton` is a `{@link Button}` that fires a `close` event when
12253 * it gets clicked.
12254 *
12255 * @extends Button
12256 */
12257class CloseButton extends Button {
12258  /**
12259   * Creates an instance of the this class.
12260   *
12261   * @param  {Player} player
12262   *         The `Player` that this class should be attached to.
12263   *
12264   * @param  {Object} [options]
12265   *         The key/value store of player options.
12266   */
12267  constructor(player, options) {
12268    super(player, options);
12269    this.setIcon('cancel');
12270    this.controlText((options && options.controlText) || this.localize('Close'));
12271  }
12272
12273  /**
12274   * Builds the default DOM `className`.
12275   *
12276   * @return {string}
12277   *         The DOM `className` for this object.
12278   */
12279  buildCSSClass() {
12280    return `vjs-close-button ${super.buildCSSClass()}`;
12281  }
12282
12283  /**
12284   * This gets called when a `CloseButton` gets clicked. See
12285   * {@link ClickableComponent#handleClick} for more information on when
12286   * this will be triggered
12287   *
12288   * @param {Event} event
12289   *        The `keydown`, `tap`, or `click` event that caused this function to be
12290   *        called.
12291   *
12292   * @listens tap
12293   * @listens click
12294   * @fires CloseButton#close
12295   */
12296  handleClick(event) {
12297    /**
12298     * Triggered when the a `CloseButton` is clicked.
12299     *
12300     * @event CloseButton#close
12301     * @type {Event}
12302     *
12303     * @property {boolean} [bubbles=false]
12304     *           set to false so that the close event does not
12305     *           bubble up to parents if there is no listener
12306     */
12307    this.trigger({
12308      type: 'close',
12309      bubbles: false,
12310    });
12311  }
12312  /**
12313   * Event handler that is called when a `CloseButton` receives a
12314   * `keydown` event.
12315   *
12316   * By default, if the key is Esc, it will trigger a `click` event.
12317   *
12318   * @param {KeyboardEvent} event
vendor: 7,915 bytes, lines 12319-12624
12319   *        The `keydown` event that caused this function to be called.
12320   *
12321   * @listens keydown
12322   */
12323  handleKeyDown(event) {
12324    // Esc button will trigger `click` event
12325    if (event.key === 'Escape') {
12326      event.preventDefault();
12327      event.stopPropagation();
12328      this.trigger('click');
12329    } else {
12330      // Pass keypress handling up for unsupported keys
12331      super.handleKeyDown(event);
12332    }
12333  }
12334}
12335Component$1.registerComponent('CloseButton', CloseButton);
12336
12337/**
12338 * @file play-toggle.js
12339 */
12340
12341/** @import Player from './player' */
12342
12343/**
12344 * Button to toggle between play and pause.
12345 *
12346 * @extends Button
12347 */
12348class PlayToggle extends Button {
12349  /**
12350   * Creates an instance of this class.
12351   *
12352   * @param {Player} player
12353   *        The `Player` that this class should be attached to.
12354   *
12355   * @param {Object} [options={}]
12356   *        The key/value store of player options.
12357   */
12358  constructor(player, options = {}) {
12359    super(player, options);
12360
12361    // show or hide replay icon
12362    options.replay = options.replay === undefined || options.replay;
12363    this.setIcon('play');
12364    this.on(player, 'play', (e) => this.handlePlay(e));
12365    this.on(player, 'pause', (e) => this.handlePause(e));
12366    if (options.replay) {
12367      this.on(player, 'ended', (e) => this.handleEnded(e));
12368    }
12369  }
12370
12371  /**
12372   * Builds the default DOM `className`.
12373   *
12374   * @return {string}
12375   *         The DOM `className` for this object.
12376   */
12377  buildCSSClass() {
12378    return `vjs-play-control ${super.buildCSSClass()}`;
12379  }
12380
12381  /**
12382   * This gets called when an `PlayToggle` is "clicked". See
12383   * {@link ClickableComponent} for more detailed information on what a click can be.
12384   *
12385   * @param {Event} [event]
12386   *        The `keydown`, `tap`, or `click` event that caused this function to be
12387   *        called.
12388   *
12389   * @listens tap
12390   * @listens click
12391   */
12392  handleClick(event) {
12393    if (this.player_.paused()) {
12394      silencePromise(this.player_.play());
12395    } else {
12396      this.player_.pause();
12397    }
12398  }
12399
12400  /**
12401   * This gets called once after the video has ended and the user seeks so that
12402   * we can change the replay button back to a play button.
12403   *
12404   * @param {Event} [event]
12405   *        The event that caused this function to run.
12406   *
12407   * @listens Player#seeked
12408   */
12409  handleSeeked(event) {
12410    this.removeClass('vjs-ended');
12411    if (this.player_.paused()) {
12412      this.handlePause(event);
12413    } else {
12414      this.handlePlay(event);
12415    }
12416  }
12417
12418  /**
12419   * Add the vjs-playing class to the element so it can change appearance.
12420   *
12421   * @param {Event} [event]
12422   *        The event that caused this function to run.
12423   *
12424   * @listens Player#play
12425   */
12426  handlePlay(event) {
12427    this.removeClass('vjs-ended', 'vjs-paused');
12428    this.addClass('vjs-playing');
12429    // change the button text to "Pause"
12430    this.setIcon('pause');
12431    this.controlText('Pause');
12432  }
12433
12434  /**
12435   * Add the vjs-paused class to the element so it can change appearance.
12436   *
12437   * @param {Event} [event]
12438   *        The event that caused this function to run.
12439   *
12440   * @listens Player#pause
12441   */
12442  handlePause(event) {
12443    this.removeClass('vjs-playing');
12444    this.addClass('vjs-paused');
12445    // change the button text to "Play"
12446    this.setIcon('play');
12447    this.controlText('Play');
12448  }
12449
12450  /**
12451   * Add the vjs-ended class to the element so it can change appearance
12452   *
12453   * @param {Event} [event]
12454   *        The event that caused this function to run.
12455   *
12456   * @listens Player#ended
12457   */
12458  handleEnded(event) {
12459    this.removeClass('vjs-playing');
12460    this.addClass('vjs-ended');
12461    // change the button text to "Replay"
12462    this.setIcon('replay');
12463    this.controlText('Replay');
12464
12465    // on the next seek remove the replay button
12466    this.one(this.player_, 'seeked', (e) => this.handleSeeked(e));
12467  }
12468}
12469
12470/**
12471 * The text that should display over the `PlayToggle`s controls. Added for localization.
12472 *
12473 * @type {string}
12474 * @protected
12475 */
12476PlayToggle.prototype.controlText_ = 'Play';
12477Component$1.registerComponent('PlayToggle', PlayToggle);
12478
12479/**
12480 * @file time-display.js
12481 */
12482
12483/** @import Player from '../../player' */
12484
12485/**
12486 * Displays time information about the video
12487 *
12488 * @extends Component
12489 */
12490class TimeDisplay extends Component$1 {
12491  /**
12492   * Creates an instance of this class.
12493   *
12494   * @param {Player} player
12495   *        The `Player` that this class should be attached to.
12496   *
12497   * @param {Object} [options]
12498   *        The key/value store of player options.
12499   */
12500  constructor(player, options) {
12501    super(player, options);
12502    this.on(player, ['timeupdate', 'ended', 'seeking'], (e) => this.update(e));
12503    this.updateTextNode_();
12504  }
12505
12506  /**
12507   * Create the `Component`'s DOM element
12508   *
12509   * @return {Element}
12510   *         The element that was created.
12511   */
12512  createEl() {
12513    const className = this.buildCSSClass();
12514    const el = super.createEl('div', {
12515      className: `${className} vjs-time-control vjs-control`,
12516    });
12517    const span = createEl(
12518      'span',
12519      {
12520        className: 'vjs-control-text',
12521        textContent: `${this.localize(this.labelText_)}\u00a0`,
12522      },
12523      {
12524        role: 'presentation',
12525      }
12526    );
12527    el.appendChild(span);
12528    this.contentEl_ = createEl(
12529      'span',
12530      {
12531        className: `${className}-display`,
12532      },
12533      {
12534        // span elements have no implicit role, but some screen readers (notably VoiceOver)
12535        // treat them as a break between items in the DOM when using arrow keys
12536        // (or left-to-right swipes on iOS) to read contents of a page. Using
12537        // role='presentation' causes VoiceOver to NOT treat this span as a break.
12538        role: 'presentation',
12539      }
12540    );
12541    el.appendChild(this.contentEl_);
12542    return el;
12543  }
12544  dispose() {
12545    this.contentEl_ = null;
12546    this.textNode_ = null;
12547    super.dispose();
12548  }
12549
12550  /**
12551   * Updates the displayed time according to the `updateContent` function which is defined in the child class.
12552   *
12553   * @param {Event} [event]
12554   *          The `timeupdate`, `ended` or `seeking` (if enableSmoothSeeking is true) event that caused this function to be called.
12555   */
12556  update(event) {
12557    if (!this.player_.options_.enableSmoothSeeking && event.type === 'seeking') {
12558      return;
12559    }
12560    this.updateContent(event);
12561  }
12562
12563  /**
12564   * Updates the time display text node with a new time
12565   *
12566   * @param {number} [time=0] the time to update to
12567   *
12568   * @private
12569   */
12570  updateTextNode_(time = 0) {
12571    time = formatTime(time);
12572    if (this.formattedTime_ === time) {
12573      return;
12574    }
12575    this.formattedTime_ = time;
12576    this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', () => {
12577      if (!this.contentEl_) {
12578        return;
12579      }
12580      let oldNode = this.textNode_;
12581      if (oldNode && this.contentEl_.firstChild !== oldNode) {
12582        oldNode = null;
12583        log$1.warn(
12584          'TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.'
12585        );
12586      }
12587      this.textNode_ = document__default['default'].createTextNode(this.formattedTime_);
12588      if (!this.textNode_) {
12589        return;
12590      }
12591      if (oldNode) {
12592        this.contentEl_.replaceChild(this.textNode_, oldNode);
12593      } else {
12594        this.contentEl_.appendChild(this.textNode_);
12595      }
12596    });
12597  }
12598
12599  /**
12600   * To be filled out in the child class, should update the displayed time
12601   * in accordance with the fact that the current time has changed.
12602   *
12603   * @param {Event} [event]
12604   *        The `timeupdate`  event that caused this to run.
12605   *
12606   * @listens Player#timeupdate
12607   */
12608  updateContent(event) {}
12609}
12610
12611/**
12612 * The text that is added to the `TimeDisplay` for screen reader users.
12613 *
12614 * @type {string}
12615 * @private
12616 */
12617TimeDisplay.prototype.labelText_ = 'Time';
12618
12619/**
12620 * The text that should display over the `TimeDisplay`s controls. Added to for localization.
12621 *
12622 * @type {string}
12623 * @protected
12624 *
12625 * @deprecated in v7; controlText_ is not used in non-active display Components
12626 */
12627TimeDisplay.prototype.controlText_ = 'Time';
12628Component$1.registerComponent('TimeDisplay', TimeDisplay);
12629
12630/**
12631 * @file current-time-display.js
12632 */
12633
12634/**
12635 * Displays the current time
12636 *
12637 * @extends Component
12638 */
12639class CurrentTimeDisplay extends TimeDisplay {
12640  /**
12641   * Builds the default DOM `className`.
12642   *
12643   * @return {string}
12644   *         The DOM `className` for this object.
12645   */
12646  buildCSSClass() {
12647    return 'vjs-current-time';
12648  }
12649
12650  /**
12651   * Update current time display
12652   *
12653   * @param {Event} [event]
12654   *        The `timeupdate` event that caused this function to run.
12655   *
12656   * @listens Player#timeupdate
12657   */
12658  updateContent(event) {
12659    // Allows for smooth scrubbing, when player can't keep up.
12660    let time;
12661    if (this.player_.ended()) {
12662      time = this.player_.duration();
12663    } else {
12664      time = this.player_.scrubbing()
12665        ? this.player_.getCache().currentTime
12666        : this.player_.currentTime();
12667    }
12668    this.updateTextNode_(time);
12669  }
12670}
12671
12672/**
12673 * The text that is added to the `CurrentTimeDisplay` for screen reader users.
12674 *
12675 * @type {string}
12676 * @private
12677 */
12678CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
12679
12680/**
12681 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
12682 *
12683 * @type {string}
12684 * @protected
12685 *
12686 * @deprecated in v7; controlText_ is not used in non-active display Components
12687 */
12688CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
12689Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
12690
12691/**
12692 * @file duration-display.js
12693 */
12694
12695/** @import Player from '../../player' */
12696
12697/**
12698 * Displays the duration
12699 *
12700 * @extends Component
12701 */
12702class DurationDisplay extends TimeDisplay {
12703  /**
12704   * Creates an instance of this class.
12705   *
12706   * @param {Player} player
12707   *        The `Player` that this class should be attached to.
12708   *
12709   * @param {Object} [options]
12710   *        The key/value store of player options.
12711   */
12712  constructor(player, options) {
12713    super(player, options);
12714    const updateContent = (e) => this.updateContent(e);
12715
12716    // we do not want to/need to throttle duration changes,
12717    // as they should always display the changed duration as
12718    // it has changed
12719    this.on(player, 'durationchange', updateContent);
12720
12721    // Listen to loadstart because the player duration is reset when a new media element is loaded,
12722    // but the durationchange on the user agent will not fire.
12723    // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
12724    this.on(player, 'loadstart', updateContent);
12725
12726    // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
12727    // listeners could have broken dependent applications/libraries. These
12728    // can likely be removed for 7.0.
12729    this.on(player, 'loadedmetadata', updateContent);
12730  }
12731
12732  /**
12733   * Builds the default DOM `className`.
12734   *
12735   * @return {string}
12736   *         The DOM `className` for this object.
12737   */
12738  buildCSSClass() {
12739    return 'vjs-duration';
12740  }
12741
12742  /**
12743   * Update duration time display.
12744   *
12745   * @param {Event} [event]
12746   *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
12747   *        this function to be called.
12748   *
12749   * @listens Player#durationchange
12750   * @listens Player#timeupdate
12751   * @listens Player#loadedmetadata
12752   */
12753  updateContent(event) {
12754    const duration = this.player_.duration();
12755    this.updateTextNode_(duration);
12756  }
12757}
12758
12759/**
12760 * The text that is added to the `DurationDisplay` for screen reader users.
12761 *
12762 * @type {string}
12763 * @private
12764 */
12765DurationDisplay.prototype.labelText_ = 'Duration';
12766
12767/**
12768 * The text that should display over the `DurationDisplay`s controls. Added to for localization.
12769 *
12770 * @type {string}
12771 * @protected
12772 *
12773 * @deprecated in v7; controlText_ is not used in non-active display Components
12774 */
12775DurationDisplay.prototype.controlText_ = 'Duration';
12776Component$1.registerComponent('DurationDisplay', DurationDisplay);
12777
12778/**
12779 * @file time-divider.js
12780 */
12781
12782/**
12783 * The separator between the current time and duration.
12784 * Can be hidden if it's not needed in the design.
12785 *
12786 * @extends Component
12787 */
12788class TimeDivider extends Component$1 {
12789  /**
12790   * Create the component's DOM element
12791   *
12792   * @return {Element}
12793   *         The element that was created.
12794   */
12795  createEl() {
12796    const el = super.createEl(
12797      'div',
12798      {
12799        className: 'vjs-time-control vjs-time-divider',
12800      },
12801      {
12802        // this element and its contents can be hidden from assistive techs since
12803        // it is made extraneous by the announcement of the control text
12804        // for the current time and duration displays
12805        'aria-hidden': true,
12806      }
12807    );
12808    const div = super.createEl('div');
12809    const span = super.createEl('span', {
12810      textContent: '/',
12811    });
12812    div.appendChild(span);
12813    el.appendChild(div);
12814    return el;
12815  }
12816}
12817Component$1.registerComponent('TimeDivider', TimeDivider);
12818
12819/**
12820 * @file remaining-time-display.js
12821 */
12822
12823/** @import Player from '../../player' */
12824
12825/**
12826 * Displays the time left in the video
12827 *
12828 * @extends Component
12829 */
12830class RemainingTimeDisplay extends TimeDisplay {
12831  /**
12832   * Creates an instance of this class.
12833   *
12834   * @param {Player} player
12835   *        The `Player` that this class should be attached to.
12836   *
12837   * @param {Object} [options]
12838   *        The key/value store of player options.
12839   */
12840  constructor(player, options) {
12841    super(player, options);
12842    this.on(player, 'durationchange', (e) => this.updateContent(e));
12843  }
12844
12845  /**
12846   * Builds the default DOM `className`.
12847   *
12848   * @return {string}
12849   *         The DOM `className` for this object.
12850   */
12851  buildCSSClass() {
12852    return 'vjs-remaining-time';
12853  }
12854
12855  /**
12856   * Create the `Component`'s DOM element with the "minus" character prepend to the time
12857   *
12858   * @return {Element}
12859   *         The element that was created.
12860   */
12861  createEl() {
12862    const el = super.createEl();
12863    if (this.options_.displayNegative !== false) {
12864      el.insertBefore(
12865        createEl(
12866          'span',
12867          {},
12868          {
12869            'aria-hidden': true,
12870          },
12871          '-'
12872        ),
12873        this.contentEl_
12874      );
12875    }
12876    return el;
12877  }
12878
12879  /**
12880   * Update remaining time display.
12881   *
12882   * @param {Event} [event]
12883   *        The `timeupdate` or `durationchange` event that caused this to run.
12884   *
12885   * @listens Player#timeupdate
12886   * @listens Player#durationchange
12887   */
12888  updateContent(event) {
12889    if (typeof this.player_.duration() !== 'number') {
12890      return;
12891    }
12892    let time;
12893
12894    // @deprecated We should only use remainingTimeDisplay
12895    // as of video.js 7
12896    if (this.player_.ended()) {
12897      time = 0;
12898    } else if (this.player_.remainingTimeDisplay) {
12899      time = this.player_.remainingTimeDisplay();
12900    } else {
12901      time = this.player_.remainingTime();
12902    }
12903    this.updateTextNode_(time);
12904  }
12905}
12906
12907/**
12908 * The text that is added to the `RemainingTimeDisplay` for screen reader users.
12909 *
12910 * @type {string}
12911 * @private
12912 */
12913RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
12914
12915/**
12916 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
12917 *
12918 * @type {string}
12919 * @protected
12920 *
12921 * @deprecated in v7; controlText_ is not used in non-active display Components
12922 */
12923RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
12924Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
12925
12926/**
12927 * @file live-display.js
12928 */
12929
12930/** @import Player from './player' */
12931
12932// TODO - Future make it click to snap to live
12933
12934/**
12935 * Displays the live indicator when duration is Infinity.
12936 *
12937 * @extends Component
12938 */
12939class LiveDisplay extends Component$1 {
12940  /**
12941   * Creates an instance of this class.
12942   *
12943   * @param {Player} player
12944   *        The `Player` that this class should be attached to.
12945   *
12946   * @param {Object} [options]
12947   *        The key/value store of player options.
12948   */
12949  constructor(player, options) {
12950    super(player, options);
12951    this.updateShowing();
12952    this.on(this.player(), 'durationchange', (e) => this.updateShowing(e));
12953  }
12954
12955  /**
12956   * Create the `Component`'s DOM element
12957   *
12958   * @return {Element}
12959   *         The element that was created.
12960   */
12961  createEl() {
12962    const el = super.createEl('div', {
12963      className: 'vjs-live-control vjs-control',
12964    });
12965    this.contentEl_ = createEl(
12966      'div',
12967      {
12968        className: 'vjs-live-display',
12969      },
12970      {
12971        'aria-live': 'off',
12972      }
12973    );
12974    this.contentEl_.appendChild(
12975      createEl('span', {
12976        className: 'vjs-control-text',
12977        textContent: `${this.localize('Stream Type')}\u00a0`,
12978      })
12979    );
12980    this.contentEl_.appendChild(document__default['default'].createTextNode(this.localize('LIVE')));
12981    el.appendChild(this.contentEl_);
12982    return el;
12983  }
12984  dispose() {
12985    this.contentEl_ = null;
12986    super.dispose();
12987  }
12988
12989  /**
12990   * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
12991   * it accordingly
12992   *
12993   * @param {Event} [event]
12994   *        The {@link Player#durationchange} event that caused this function to run.
12995   *
12996   * @listens Player#durationchange
12997   */
12998  updateShowing(event) {
12999    if (this.player().duration() === Infinity) {
13000      this.show();
13001    } else {
13002      this.hide();
13003    }
13004  }
13005}
13006Component$1.registerComponent('LiveDisplay', LiveDisplay);
13007
13008/**
13009 * @file seek-to-live.js
13010 */
13011
13012/** @import Player from './player' */
13013
13014/**
13015 * Displays the live indicator when duration is Infinity.
13016 *
13017 * @extends Component
13018 */
13019class SeekToLive extends Button {
13020  /**
13021   * Creates an instance of this class.
13022   *
13023   * @param {Player} player
13024   *        The `Player` that this class should be attached to.
13025   *
13026   * @param {Object} [options]
13027   *        The key/value store of player options.
13028   */
13029  constructor(player, options) {
13030    super(player, options);
13031    this.updateLiveEdgeStatus();
13032    if (this.player_.liveTracker) {
13033      this.updateLiveEdgeStatusHandler_ = (e) => this.updateLiveEdgeStatus(e);
13034      this.on(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13035    }
13036  }
13037
13038  /**
13039   * Create the `Component`'s DOM element
13040   *
13041   * @return {Element}
13042   *         The element that was created.
13043   */
13044  createEl() {
13045    const el = super.createEl('button', {
13046      className: 'vjs-seek-to-live-control vjs-control',
13047    });
13048    this.setIcon('circle', el);
13049    this.textEl_ = createEl(
13050      'span',
13051      {
13052        className: 'vjs-seek-to-live-text',
13053        textContent: this.localize('LIVE'),
13054      },
13055      {
13056        'aria-hidden': 'true',
13057      }
13058    );
13059    el.appendChild(this.textEl_);
13060    return el;
13061  }
13062
13063  /**
13064   * Update the state of this button if we are at the live edge
13065   * or not
13066   */
13067  updateLiveEdgeStatus() {
13068    // default to live edge
13069    if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
13070      this.setAttribute('aria-disabled', true);
13071      this.addClass('vjs-at-live-edge');
13072      this.controlText('Seek to live, currently playing live');
13073    } else {
13074      this.setAttribute('aria-disabled', false);
13075      this.removeClass('vjs-at-live-edge');
13076      this.controlText('Seek to live, currently behind live');
13077    }
13078  }
13079
13080  /**
13081   * On click bring us as near to the live point as possible.
13082   * This requires that we wait for the next `live-seekable-change`
13083   * event which will happen every segment length seconds.
13084   */
13085  handleClick() {
13086    this.player_.liveTracker.seekToLiveEdge();
13087  }
13088
13089  /**
13090   * Dispose of the element and stop tracking
13091   */
13092  dispose() {
13093    if (this.player_.liveTracker) {
13094      this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13095    }
13096    this.textEl_ = null;
13097    super.dispose();
13098  }
13099}
13100/**
13101 * The text that should display over the `SeekToLive`s control. Added for localization.
13102 *
13103 * @type {string}
13104 * @protected
13105 */
13106SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
13107Component$1.registerComponent('SeekToLive', SeekToLive);
13108
13109/**
13110 * @file num.js
13111 * @module num
13112 */
13113
13114/**
13115 * Keep a number between a min and a max value
13116 *
13117 * @param {number} number
13118 *        The number to clamp
13119 *
13120 * @param {number} min
13121 *        The minimum value
13122 * @param {number} max
13123 *        The maximum value
13124 *
13125 * @return {number}
13126 *         the clamped number
13127 */
13128function clamp(number, min, max) {
13129  number = Number(number);
13130  return Math.min(max, Math.max(min, isNaN(number) ? min : number));
13131}
13132
13133var Num = /*#__PURE__*/ Object.freeze({
13134  __proto__: null,
13135  clamp: clamp,
13136});
13137
13138/**
13139 * @file slider.js
13140 */
13141
13142/** @import Player from '../player' */
13143
13144/**
13145 * The base functionality for a slider. Can be vertical or horizontal.
13146 * For instance the volume bar or the seek bar on a video is a slider.
13147 *
13148 * @extends Component
13149 */
13150class Slider extends Component$1 {
13151  /**
13152   * Create an instance of this class
13153   *
13154   * @param {Player} player
13155   *        The `Player` that this class should be attached to.
13156   *
13157   * @param {Object} [options]
13158   *        The key/value store of player options.
13159   */
13160  constructor(player, options) {
13161    super(player, options);
13162    this.handleMouseDown_ = (e) => this.handleMouseDown(e);
13163    this.handleMouseUp_ = (e) => this.handleMouseUp(e);
13164    this.handleKeyDown_ = (e) => this.handleKeyDown(e);
13165    this.handleClick_ = (e) => this.handleClick(e);
13166    this.handleMouseMove_ = (e) => this.handleMouseMove(e);
13167    this.update_ = (e) => this.update(e);
13168
13169    // Set property names to bar to match with the child Slider class is looking for
13170    this.bar = this.getChild(this.options_.barName);
13171
13172    // Set a horizontal or vertical class on the slider depending on the slider t
vendor: 10,506 bytes, lines 13172-13528
13172ype
13173    this.vertical(!!this.options_.vertical);
13174    this.enable();
13175  }
13176
13177  /**
13178   * Are controls are currently enabled for this slider or not.
13179   *
13180   * @return {boolean}
13181   *         true if controls are enabled, false otherwise
13182   */
13183  enabled() {
13184    return this.enabled_;
13185  }
13186
13187  /**
13188   * Enable controls for this slider if they are disabled
13189   */
13190  enable() {
13191    if (this.enabled()) {
13192      return;
13193    }
13194    this.on('mousedown', this.handleMouseDown_);
13195    this.on('touchstart', this.handleMouseDown_);
13196    this.on('keydown', this.handleKeyDown_);
13197    this.on('click', this.handleClick_);
13198
13199    // TODO: deprecated, controlsvisible does not seem to be fired
13200    this.on(this.player_, 'controlsvisible', this.update);
13201    if (this.playerEvent) {
13202      this.on(this.player_, this.playerEvent, this.update);
13203    }
13204    this.removeClass('disabled');
13205    this.setAttribute('tabindex', 0);
13206    this.enabled_ = true;
13207  }
13208
13209  /**
13210   * Disable controls for this slider if they are enabled
13211   */
13212  disable() {
13213    if (!this.enabled()) {
13214      return;
13215    }
13216    const doc = this.bar.el_.ownerDocument;
13217    this.off('mousedown', this.handleMouseDown_);
13218    this.off('touchstart', this.handleMouseDown_);
13219    this.off('keydown', this.handleKeyDown_);
13220    this.off('click', this.handleClick_);
13221    this.off(this.player_, 'controlsvisible', this.update_);
13222    this.off(doc, 'mousemove', this.handleMouseMove_);
13223    this.off(doc, 'mouseup', this.handleMouseUp_);
13224    this.off(doc, 'touchmove', this.handleMouseMove_);
13225    this.off(doc, 'touchend', this.handleMouseUp_);
13226    this.removeAttribute('tabindex');
13227    this.addClass('disabled');
13228    if (this.playerEvent) {
13229      this.off(this.player_, this.playerEvent, this.update);
13230    }
13231    this.enabled_ = false;
13232  }
13233
13234  /**
13235   * Create the `Slider`s DOM element.
13236   *
13237   * @param {string} type
13238   *        Type of element to create.
13239   *
13240   * @param {Object} [props={}]
13241   *        List of properties in Object form.
13242   *
13243   * @param {Object} [attributes={}]
13244   *        list of attributes in Object form.
13245   *
13246   * @return {Element}
13247   *         The element that gets created.
13248   */
13249  createEl(type, props = {}, attributes = {}) {
13250    // Add the slider element class to all sub classes
13251    props.className = props.className + ' vjs-slider';
13252    props = Object.assign(
13253      {
13254        tabIndex: 0,
13255      },
13256      props
13257    );
13258    attributes = Object.assign(
13259      {
13260        role: 'slider',
13261        'aria-valuenow': 0,
13262        'aria-valuemin': 0,
13263        'aria-valuemax': 100,
13264      },
13265      attributes
13266    );
13267    return super.createEl(type, props, attributes);
13268  }
13269
13270  /**
13271   * Handle `mousedown` or `touchstart` events on the `Slider`.
13272   *
13273   * @param {MouseEvent} event
13274   *        `mousedown` or `touchstart` event that triggered this function
13275   *
13276   * @listens mousedown
13277   * @listens touchstart
13278   * @fires Slider#slideractive
13279   */
13280  handleMouseDown(event) {
13281    const doc = this.bar.el_.ownerDocument;
13282    if (event.type === 'mousedown') {
13283      event.preventDefault();
13284    }
13285    // Do not call preventDefault() on touchstart in Chrome
13286    // to avoid console warnings. Use a 'touch-action: none' style
13287    // instead to prevent unintended scrolling.
13288    // https://developers.google.com/web/updates/2017/01/scrolling-intervention
13289    if (event.type === 'touchstart' && !IS_CHROME) {
13290      event.preventDefault();
13291    }
13292    blockTextSelection();
13293    this.addClass('vjs-sliding');
13294    /**
13295     * Triggered when the slider is in an active state
13296     *
13297     * @event Slider#slideractive
13298     * @type {MouseEvent}
13299     */
13300    this.trigger('slideractive');
13301    this.on(doc, 'mousemove', this.handleMouseMove_);
13302    this.on(doc, 'mouseup', this.handleMouseUp_);
13303    this.on(doc, 'touchmove', this.handleMouseMove_);
13304    this.on(doc, 'touchend', this.handleMouseUp_);
13305    this.handleMouseMove(event, true);
13306  }
13307
13308  /**
13309   * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
13310   * The `mousemove` and `touchmove` events will only only trigger this function during
13311   * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
13312   * {@link Slider#handleMouseUp}.
13313   *
13314   * @param {MouseEvent} event
13315   *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
13316   *        this function
13317   * @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.
13318   *
13319   * @listens mousemove
13320   * @listens touchmove
13321   */
13322  handleMouseMove(event) {}
13323
13324  /**
13325   * Handle `mouseup` or `touchend` events on the `Slider`.
13326   *
13327   * @param {MouseEvent} event
13328   *        `mouseup` or `touchend` event that triggered this function.
13329   *
13330   * @listens touchend
13331   * @listens mouseup
13332   * @fires Slider#sliderinactive
13333   */
13334  handleMouseUp(event) {
13335    const doc = this.bar.el_.ownerDocument;
13336    unblockTextSelection();
13337    this.removeClass('vjs-sliding');
13338    /**
13339     * Triggered when the slider is no longer in an active state.
13340     *
13341     * @event Slider#sliderinactive
13342     * @type {Event}
13343     */
13344    this.trigger('sliderinactive');
13345    this.off(doc, 'mousemove', this.handleMouseMove_);
13346    this.off(doc, 'mouseup', this.handleMouseUp_);
13347    this.off(doc, 'touchmove', this.handleMouseMove_);
13348    this.off(doc, 'touchend', this.handleMouseUp_);
13349    this.update();
13350  }
13351
13352  /**
13353   * Update the progress bar of the `Slider`.
13354   *
13355   * @return {number}
13356   *          The percentage of progress the progress bar represents as a
13357   *          number from 0 to 1.
13358   */
13359  update() {
13360    // In VolumeBar init we have a setTimeout for update that pops and update
13361    // to the end of the execution stack. The player is destroyed before then
13362    // update will cause an error
13363    // If there's no bar...
13364    if (!this.el_ || !this.bar) {
13365      return;
13366    }
13367
13368    // clamp progress between 0 and 1
13369    // and only round to four decimal places, as we round to two below
13370    const progress = this.getProgress();
13371    if (progress === this.progress_) {
13372      return progress;
13373    }
13374    this.progress_ = progress;
13375    this.requestNamedAnimationFrame('Slider#update', () => {
13376      // Set the new bar width or height
13377      const sizeKey = this.vertical() ? 'height' : 'width';
13378
13379      // Convert to a percentage for css value
13380      this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
13381    });
13382    return progress;
13383  }
13384
13385  /**
13386   * Get the percentage of the bar that should be filled
13387   * but clamped and rounded.
13388   *
13389   * @return {number}
13390   *         percentage filled that the slider is
13391   */
13392  getProgress() {
13393    return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
13394  }
13395
13396  /**
13397   * Calculate distance for slider
13398   *
13399   * @param {Event} event
13400   *        The event that caused this function to run.
13401   *
13402   * @return {number}
13403   *         The current position of the Slider.
13404   *         - position.x for vertical `Slider`s
13405   *         - position.y for horizontal `Slider`s
13406   */
13407  calculateDistance(event) {
13408    const position = getPointerPosition(this.el_, event);
13409    if (this.vertical()) {
13410      return position.y;
13411    }
13412    return position.x;
13413  }
13414
13415  /**
13416   * Handle a `keydown` event on the `Slider`. Watches for left, right, up, and down
13417   * arrow keys. This function will only be called when the slider has focus. See
13418   * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
13419   *
13420   * @param {KeyboardEvent} event
13421   *        the `keydown` event that caused this function to run.
13422   *
13423   * @listens keydown
13424   */
13425  handleKeyDown(event) {
13426    const spatialNavOptions = this.options_.playerOptions.spatialNavigation;
13427    const spatialNavEnabled = spatialNavOptions && spatialNavOptions.enabled;
13428    const horizontalSeek = spatialNavOptions && spatialNavOptions.horizontalSeek;
13429    if (spatialNavEnabled) {
13430      if (
13431        (horizontalSeek && event.key === 'ArrowLeft') ||
13432        (!horizontalSeek && event.key === 'ArrowDown')
13433      ) {
13434        event.preventDefault();
13435        event.stopPropagation();
13436        this.stepBack();
13437      } else if (
13438        (horizontalSeek && event.key === 'ArrowRight') ||
13439        (!horizontalSeek && event.key === 'ArrowUp')
13440      ) {
13441        event.preventDefault();
13442        event.stopPropagation();
13443        this.stepForward();
13444      } else {
13445        super.handleKeyDown(event);
13446      }
13447
13448      // Left and Down Arrows
13449    } else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
13450      event.preventDefault();
13451      event.stopPropagation();
13452      this.stepBack();
13453
13454      // Up and Right Arrows
13455    } else if (event.key === 'ArrowUp' || event.key === 'ArrowRight') {
13456      event.preventDefault();
13457      event.stopPropagation();
13458      this.stepForward();
13459    } else {
13460      // Pass keydown handling up for unsupported keys
13461      super.handleKeyDown(event);
13462    }
13463  }
13464
13465  /**
13466   * Listener for click events on slider, used to prevent clicks
13467   *   from bubbling up to parent elements like button menus.
13468   *
13469   * @param {Object} event
13470   *        Event that caused this object to run
13471   */
13472  handleClick(event) {
13473    event.stopPropagation();
13474    event.preventDefault();
13475  }
13476
13477  /**
13478   * Get/set if slider is horizontal for vertical
13479   *
13480   * @param {boolean} [bool]
13481   *        - true if slider is vertical,
13482   *        - false is horizontal
13483   *
13484   * @return {boolean}
13485   *         - true if slider is vertical, and getting
13486   *         - false if the slider is horizontal, and getting
13487   */
13488  vertical(bool) {
13489    if (bool === undefined) {
13490      return this.vertical_ || false;
13491    }
13492    this.vertical_ = !!bool;
13493    if (this.vertical_) {
13494      this.addClass('vjs-slider-vertical');
13495    } else {
13496      this.addClass('vjs-slider-horizontal');
13497    }
13498  }
13499}
13500Component$1.registerComponent('Slider', Slider);
13501
13502/**
13503 * @file load-progress-bar.js
13504 */
13505
13506/** @import Player from '../../player' */
13507
13508// get the percent width of a time compared to the total end
13509const percentify = (time, end) => clamp((time / end) * 100, 0, 100).toFixed(2) + '%';
13510
13511/**
13512 * Shows loading progress
13513 *
13514 * @extends Component
13515 */
13516class LoadProgressBar extends Component$1 {
13517  /**
13518   * Creates an instance of this class.
13519   *
13520   * @param {Player} player
13521   *        The `Player` that this class should be attached to.
13522   *
13523   * @param {Object} [options]
13524   *        The key/value store of player options.
13525   */
13526  constructor(player, options) {
13527    super(player, options);
13528    this.partEls_ = [];
vendor: 16,202 bytes, lines 13529-14065
13529    this.on(player, 'progress', (e) => this.update(e));
13530  }
13531
13532  /**
13533   * Create the `Component`'s DOM element
13534   *
13535   * @return {Element}
13536   *         The element that was created.
13537   */
13538  createEl() {
13539    const el = super.createEl('div', {
13540      className: 'vjs-load-progress',
13541    });
13542    const wrapper = createEl('span', {
13543      className: 'vjs-control-text',
13544    });
13545    const loadedText = createEl('span', {
13546      textContent: this.localize('Loaded'),
13547    });
13548    const separator = document__default['default'].createTextNode(': ');
13549    this.percentageEl_ = createEl('span', {
13550      className: 'vjs-control-text-loaded-percentage',
13551      textContent: '0%',
13552    });
13553    el.appendChild(wrapper);
13554    wrapper.appendChild(loadedText);
13555    wrapper.appendChild(separator);
13556    wrapper.appendChild(this.percentageEl_);
13557    return el;
13558  }
13559  dispose() {
13560    this.partEls_ = null;
13561    this.percentageEl_ = null;
13562    super.dispose();
13563  }
13564
13565  /**
13566   * Update progress bar
13567   *
13568   * @param {Event} [event]
13569   *        The `progress` event that caused this function to run.
13570   *
13571   * @listens Player#progress
13572   */
13573  update(event) {
13574    this.requestNamedAnimationFrame('LoadProgressBar#update', () => {
13575      const liveTracker = this.player_.liveTracker;
13576      const buffered = this.player_.buffered();
13577      const duration =
13578        liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
13579      const bufferedEnd = this.player_.bufferedEnd();
13580      const children = this.partEls_;
13581      const percent = percentify(bufferedEnd, duration);
13582      if (this.percent_ !== percent) {
13583        // update the width of the progress bar
13584        this.el_.style.width = percent;
13585        // update the control-text
13586        textContent(this.percentageEl_, percent);
13587        this.percent_ = percent;
13588      }
13589
13590      // add child elements to represent the individual buffered time ranges
13591      for (let i = 0; i < buffered.length; i++) {
13592        const start = buffered.start(i);
13593        const end = buffered.end(i);
13594        let part = children[i];
13595        if (!part) {
13596          part = this.el_.appendChild(createEl());
13597          children[i] = part;
13598        }
13599
13600        //  only update if changed
13601        if (part.dataset.start === start && part.dataset.end === end) {
13602          continue;
13603        }
13604        part.dataset.start = start;
13605        part.dataset.end = end;
13606
13607        // set the percent based on the width of the progress bar (bufferedEnd)
13608        part.style.left = percentify(start, bufferedEnd);
13609        part.style.width = percentify(end - start, bufferedEnd);
13610      }
13611
13612      // remove unused buffered range elements
13613      for (let i = children.length; i > buffered.length; i--) {
13614        this.el_.removeChild(children[i - 1]);
13615      }
13616      children.length = buffered.length;
13617    });
13618  }
13619}
13620Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
13621
13622/**
13623 * @file time-tooltip.js
13624 */
13625
13626/** @import Player from '../../player' */
13627
13628/**
13629 * Time tooltips display a time above the progress bar.
13630 *
13631 * @extends Component
13632 */
13633class TimeTooltip extends Component$1 {
13634  /**
13635   * Creates an instance of this class.
13636   *
13637   * @param {Player} player
13638   *        The {@link Player} that this class should be attached to.
13639   *
13640   * @param {Object} [options]
13641   *        The key/value store of player options.
13642   */
13643  constructor(player, options) {
13644    super(player, options);
13645    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
13646  }
13647
13648  /**
13649   * Create the time tooltip DOM element
13650   *
13651   * @return {Element}
13652   *         The element that was created.
13653   */
13654  createEl() {
13655    return super.createEl(
13656      'div',
13657      {
13658        className: 'vjs-time-tooltip',
13659      },
13660      {
13661        'aria-hidden': 'true',
13662      }
13663    );
13664  }
13665
13666  /**
13667   * Updates the position of the time tooltip relative to the `SeekBar`.
13668   *
13669   * @param {Object} seekBarRect
13670   *        The `ClientRect` for the {@link SeekBar} element.
13671   *
13672   * @param {number} seekBarPoint
13673   *        A number from 0 to 1, representing a horizontal reference point
13674   *        from the left edge of the {@link SeekBar}
13675   */
13676  update(seekBarRect, seekBarPoint, content) {
13677    const tooltipRect = findPosition(this.el_);
13678    const playerRect = getBoundingClientRect(this.player_.el());
13679    const seekBarPointPx = seekBarRect.width * seekBarPoint;
13680
13681    // do nothing if either rect isn't available
13682    // for example, if the player isn't in the DOM for testing
13683    if (!playerRect || !tooltipRect) {
13684      return;
13685    }
13686
13687    // This is the space left of the `seekBarPoint` available within the bounds
13688    // of the player. We calculate any gap between the left edge of the player
13689    // and the left edge of the `SeekBar` and add the number of pixels in the
13690    // `SeekBar` before hitting the `seekBarPoint`
13691    let spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
13692
13693    // This is the space right of the `seekBarPoint` available within the bounds
13694    // of the player. We calculate the number of pixels from the `seekBarPoint`
13695    // to the right edge of the `SeekBar` and add to that any gap between the
13696    // right edge of the `SeekBar` and the player.
13697    let spaceRightOfPoint =
13698      seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
13699
13700    // spaceRightOfPoint is always NaN for mouse time display
13701    // because the seekbarRect does not have a right property. This causes
13702    // the mouse tool tip to be truncated when it's close to the right edge of the player.
13703    // In such cases, we ignore the `playerRect.right - seekBarRect.right` value when calculating.
13704    // For the sake of consistency, we ignore seekBarRect.left - playerRect.left for the left edge.
13705    if (!spaceRightOfPoint) {
13706      spaceRightOfPoint = seekBarRect.width - seekBarPointPx;
13707      spaceLeftOfPoint = seekBarPointPx;
13708    }
13709    // This is the number of pixels by which the tooltip will need to be pulled
13710    // further to the right to center it over the `seekBarPoint`.
13711    let pullTooltipBy = tooltipRect.width / 2;
13712
13713    // Adjust the `pullTooltipBy` distance to the left or right depending on
13714    // the results of the space calculations above.
13715    if (spaceLeftOfPoint < pullTooltipBy) {
13716      pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
13717    } else if (spaceRightOfPoint < pullTooltipBy) {
13718      pullTooltipBy = spaceRightOfPoint;
13719    }
13720
13721    // Due to the imprecision of decimal/ratio based calculations and varying
13722    // rounding behaviors, there are cases where the spacing adjustment is off
13723    // by a pixel or two. This adds insurance to these calculations.
13724    if (pullTooltipBy < 0) {
13725      pullTooltipBy = 0;
13726    } else if (pullTooltipBy > tooltipRect.width) {
13727      pullTooltipBy = tooltipRect.width;
13728    }
13729
13730    // prevent small width fluctuations within 0.4px from
13731    // changing the value below.
13732    // This really helps for live to prevent the play
13733    // progress time tooltip from jittering
13734    pullTooltipBy = Math.round(pullTooltipBy);
13735    this.el_.style.right = `-${pullTooltipBy}px`;
13736    this.write(content);
13737  }
13738
13739  /**
13740   * Write the time to the tooltip DOM element.
13741   *
13742   * @param {string} content
13743   *        The formatted time for the tooltip.
13744   */
13745  write(content) {
13746    textContent(this.el_, content);
13747  }
13748
13749  /**
13750   * Updates the position of the time tooltip relative to the `SeekBar`.
13751   *
13752   * @param {Object} seekBarRect
13753   *        The `ClientRect` for the {@link SeekBar} element.
13754   *
13755   * @param {number} seekBarPoint
13756   *        A number from 0 to 1, representing a horizontal reference point
13757   *        from the left edge of the {@link SeekBar}
13758   *
13759   * @param {number} time
13760   *        The time to update the tooltip to, not used during live playback
13761   *
13762   * @param {Function} cb
13763   *        A function that will be called during the request animation frame
13764   *        for tooltips that need to do additional animations from the default
13765   */
13766  updateTime(seekBarRect, seekBarPoint, time, cb) {
13767    this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
13768      let content;
13769      const duration = this.player_.duration();
13770      if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
13771        const liveWindow = this.player_.liveTracker.liveWindow();
13772        const secondsBehind = liveWindow - seekBarPoint * liveWindow;
13773        content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
13774      } else {
13775        content = formatTime(time, duration);
13776      }
13777      this.update(seekBarRect, seekBarPoint, content);
13778      if (cb) {
13779        cb();
13780      }
13781    });
13782  }
13783}
13784Component$1.registerComponent('TimeTooltip', TimeTooltip);
13785
13786/**
13787 * @file play-progress-bar.js
13788 */
13789
13790/**
13791 * Used by {@link SeekBar} to display media playback progress as part of the
13792 * {@link ProgressControl}.
13793 *
13794 * @extends Component
13795 */
13796class PlayProgressBar extends Component$1 {
13797  /**
13798   * Creates an instance of this class.
13799   *
13800   * @param {Player} player
13801   *        The {@link Player} that this class should be attached to.
13802   *
13803   * @param {Object} [options]
13804   *        The key/value store of player options.
13805   */
13806  constructor(player, options) {
13807    super(player, options);
13808    this.setIcon('circle');
13809    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
13810  }
13811
13812  /**
13813   * Create the the DOM element for this class.
13814   *
13815   * @return {Element}
13816   *         The element that was created.
13817   */
13818  createEl() {
13819    return super.createEl(
13820      'div',
13821      {
13822        className: 'vjs-play-progress vjs-slider-bar',
13823      },
13824      {
13825        'aria-hidden': 'true',
13826      }
13827    );
13828  }
13829
13830  /**
13831   * Enqueues updates to its own DOM as well as the DOM of its
13832   * {@link TimeTooltip} child.
13833   *
13834   * @param {Object} seekBarRect
13835   *        The `ClientRect` for the {@link SeekBar} element.
13836   *
13837   * @param {number} seekBarPoint
13838   *        A number from 0 to 1, representing a horizontal reference point
13839   *        from the left edge of the {@link SeekBar}
13840   */
13841  update(seekBarRect, seekBarPoint) {
13842    const timeTooltip = this.getChild('timeTooltip');
13843    if (!timeTooltip) {
13844      return;
13845    }
13846    const time = this.player_.scrubbing()
13847      ? this.player_.getCache().currentTime
13848      : this.player_.currentTime();
13849    timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
13850  }
13851}
13852
13853/**
13854 * Default options for {@link PlayProgressBar}.
13855 *
13856 * @type {Object}
13857 * @private
13858 */
13859PlayProgressBar.prototype.options_ = {
13860  children: [],
13861};
13862
13863// Time tooltips should not be added to a player on mobile devices
13864if (!IS_IOS && !IS_ANDROID) {
13865  PlayProgressBar.prototype.options_.children.push('timeTooltip');
13866}
13867Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
13868
13869/**
13870 * @file mouse-time-display.js
13871 */
13872
13873/**
13874 * The {@link MouseTimeDisplay} component tracks mouse movement over the
13875 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
13876 * indicating the time which is represented by a given point in the
13877 * {@link ProgressControl}.
13878 *
13879 * @extends Component
13880 */
13881class MouseTimeDisplay extends Component$1 {
13882  /**
13883   * Creates an instance of this class.
13884   *
13885   * @param {Player} player
13886   *        The {@link Player} that this class should be attached to.
13887   *
13888   * @param {Object} [options]
13889   *        The key/value store of player options.
13890   */
13891  constructor(player, options) {
13892    super(player, options);
13893    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
13894  }
13895
13896  /**
13897   * Create the DOM element for this class.
13898   *
13899   * @return {Element}
13900   *         The element that was created.
13901   */
13902  createEl() {
13903    return super.createEl('div', {
13904      className: 'vjs-mouse-display',
13905    });
13906  }
13907
13908  /**
13909   * Enqueues updates to its own DOM as well as the DOM of its
13910   * {@link TimeTooltip} child.
13911   *
13912   * @param {Object} seekBarRect
13913   *        The `ClientRect` for the {@link SeekBar} element.
13914   *
13915   * @param {number} seekBarPoint
13916   *        A number from 0 to 1, representing a horizontal reference point
13917   *        from the left edge of the {@link SeekBar}
13918   */
13919  update(seekBarRect, seekBarPoint) {
13920    const time = seekBarPoint * this.player_.duration();
13921    this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, () => {
13922      this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
13923    });
13924  }
13925}
13926
13927/**
13928 * Default options for `MouseTimeDisplay`
13929 *
13930 * @type {Object}
13931 * @private
13932 */
13933MouseTimeDisplay.prototype.options_ = {
13934  children: ['timeTooltip'],
13935};
13936Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
13937
13938/**
13939 * @file seek-bar.js
13940 */
13941
13942// The number of seconds the `step*` functions move the timeline.
13943const STEP_SECONDS = 5;
13944
13945// The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
13946const PAGE_KEY_MULTIPLIER = 12;
13947
13948/**
13949 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
13950 * as its `bar`.
13951 *
13952 * @extends Slider
13953 */
13954class SeekBar extends Slider {
13955  /**
13956   * Creates an instance of this class.
13957   *
13958   * @param {Player} player
13959   *        The `Player` that this class should be attached to.
13960   *
13961   * @param {Object} [options]
13962   *        The key/value store of player options.
13963   */
13964  constructor(player, options) {
13965    super(player, options);
13966    this.setEventHandlers_();
13967  }
13968
13969  /**
13970   * Sets the event handlers
13971   *
13972   * @private
13973   */
13974  setEventHandlers_() {
13975    this.update_ = bind_(this, this.update);
13976    this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
13977    this.on(this.player_, ['durationchange', 'timeupdate'], this.update);
13978    this.on(this.player_, ['ended'], this.update_);
13979    if (this.player_.liveTracker) {
13980      this.on(this.player_.liveTracker, 'liveedgechange', this.update);
13981    }
13982
13983    // when playing, let's ensure we smoothly update the play progress bar
13984    // via an interval
13985    this.updateInterval = null;
13986    this.enableIntervalHandler_ = (e) => this.enableInterval_(e);
13987    this.disableIntervalHandler_ = (e) => this.disableInterval_(e);
13988    this.on(this.player_, ['playing'], this.enableIntervalHandler_);
13989    this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
13990
13991    // we don't need to update the play progress if the document is hidden,
13992    // also, this causes the CPU to spike and eventually crash the page on IE11.
13993    if (
13994      'hidden' in document__default['default'] &&
13995      'visibilityState' in document__default['default']
13996    ) {
13997      this.on(document__default['default'], 'visibilitychange', this.toggleVisibility_);
13998    }
13999  }
14000  toggleVisibility_(e) {
14001    if (document__default['default'].visibilityState === 'hidden') {
14002      this.cancelNamedAnimationFrame('SeekBar#update');
14003      this.cancelNamedAnimationFrame('Slider#update');
14004      this.disableInterval_(e);
14005    } else {
14006      if (!this.player_.ended() && !this.player_.paused()) {
14007        this.enableInterval_();
14008      }
14009
14010      // we just switched back to the page and someone may be looking, so, update ASAP
14011      this.update();
14012    }
14013  }
14014  enableInterval_() {
14015    if (this.updateInterval) {
14016      return;
14017    }
14018    this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
14019  }
14020  disableInterval_(e) {
14021    if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
14022      return;
14023    }
14024    if (!this.updateInterval) {
14025      return;
14026    }
14027    this.clearInterval(this.updateInterval);
14028    this.updateInterval = null;
14029  }
14030
14031  /**
14032   * Create the `Component`'s DOM element
14033   *
14034   * @return {Element}
14035   *         The element that was created.
14036   */
14037  createEl() {
14038    return super.createEl(
14039      'div',
14040      {
14041        className: 'vjs-progress-holder',
14042      },
14043      {
14044        'aria-label': this.localize('Progress Bar'),
14045      }
14046    );
14047  }
14048
14049  /**
14050   * This function updates the play progress bar and accessibility
14051   * attributes to whatever is passed in.
14052   *
14053   * @param {Event} [event]
14054   *        The `timeupdate` or `ended` event that caused this to run.
14055   *
14056   * @listens Player#timeupdate
14057   *
14058   * @return {number}
14059   *          The current percent at a number from 0-1
14060   */
14061  update(event) {
14062    // ignore updates while the tab is hidden
14063    if (document__default['default'].visibilityState === 'hidden') {
14064      return;
14065    }
vendor: 3,509 bytes, lines 14066-14178
14066    const percent = super.update();
14067    this.requestNamedAnimationFrame('SeekBar#update', () => {
14068      const currentTime = this.player_.ended() ? this.player_.duration() : this.getCurrentTime_();
14069      const liveTracker = this.player_.liveTracker;
14070      let duration = this.player_.duration();
14071      if (liveTracker && liveTracker.isLive()) {
14072        duration = this.player_.liveTracker.liveCurrentTime();
14073      }
14074      if (this.percent_ !== percent) {
14075        // machine readable value of progress bar (percentage complete)
14076        this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14077        this.percent_ = percent;
14078      }
14079      if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
14080        // human readable value of progress bar (time complete)
14081        this.el_.setAttribute(
14082          'aria-valuetext',
14083          this.localize(
14084            'progress bar timing: currentTime={1} duration={2}',
14085            [formatTime(currentTime, duration), formatTime(duration, duration)],
14086            '{1} of {2}'
14087          )
14088        );
14089        this.currentTime_ = currentTime;
14090        this.duration_ = duration;
14091      }
14092
14093      // update the progress bar time tooltip with the current time
14094      if (this.bar) {
14095        this.bar.update(getBoundingClientRect(this.el()), this.getProgress());
14096      }
14097    });
14098    return percent;
14099  }
14100
14101  /**
14102   * Prevent liveThreshold from causing seeks to seem like they
14103   * are not happening from a user perspective.
14104   *
14105   * @param {number} ct
14106   *        current time to seek to
14107   */
14108  userSeek_(ct) {
14109    if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
14110      this.player_.liveTracker.nextSeekedFromUser();
14111    }
14112    this.player_.currentTime(ct);
14113  }
14114
14115  /**
14116   * Get the value of current time but allows for smooth scrubbing,
14117   * when player can't keep up.
14118   *
14119   * @return {number}
14120   *         The current time value to display
14121   *
14122   * @private
14123   */
14124  getCurrentTime_() {
14125    return this.player_.scrubbing()
14126      ? this.player_.getCache().currentTime
14127      : this.player_.currentTime();
14128  }
14129
14130  /**
14131   * Get the percentage of media played so far.
14132   *
14133   * @return {number}
14134   *         The percentage of media played so far (0 to 1).
14135   */
14136  getPercent() {
14137    const currentTime = this.getCurrentTime_();
14138    let percent;
14139    const liveTracker = this.player_.liveTracker;
14140    if (liveTracker && liveTracker.isLive()) {
14141      percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
14142
14143      // prevent the percent from changing at the live edge
14144      if (liveTracker.atLiveEdge()) {
14145        percent = 1;
14146      }
14147    } else {
14148      percent = currentTime / this.player_.duration();
14149    }
14150    return percent;
14151  }
14152
14153  /**
14154   * Handle mouse down on seek bar
14155   *
14156   * @param {MouseEvent} event
14157   *        The `mousedown` event that caused this to run.
14158   *
14159   * @listens mousedown
14160   */
14161  handleMouseDown(event) {
14162    if (!isSingleLeftClick(event)) {
14163      return;
14164    }
14165
14166    // Stop event propagation to prevent double fire in progress-control.js
14167    event.stopPropagation();
14168    this.videoWasPlaying = !this.player_.paused();
14169    this.player_.pause();
14170    super.handleMouseDown(event);
14171  }
14172
14173  /**
14174   * Handle mouse move on seek bar
14175   *
14176   * @param {MouseEvent} event
14177   *        The `mousemove` event that caused this to run.
14178   * @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 c
14178oming from mouse down but should happen on regular mouse move handler. Defaults to false
14179   *
14180   * @listens mousemove
14181   */
14182  handleMouseMove(event, mouseDown = false) {
14183    if (!isSingleLeftClick(event) || isNaN(this.player_.duration())) {
14184      return;
14185    }
14186    if (!mouseDown && !this.player_.scrubbing()) {
14187      this.player_.scrubbing(true);
14188    }
14189    let newTime;
14190    const distance = this.calculateDistance(event);
14191    const liveTracker = this.player_.liveTracker;
14192    if (!liveTracker || !liveTracker.isLive()) {
14193      newTime = distance * this.player_.duration();
14194
14195      // Don't let video end while scrubbing.
14196      if (newTime === this.player_.duration()) {
14197        newTime = newTime - 0.1;
14198      }
14199    } else {
14200      if (distance >= 0.99) {
14201        liveTracker.seekToLiveEdge();
14202        return;
14203      }
14204      const seekableStart = liveTracker.seekableStart();
14205      const seekableEnd = liveTracker.liveCurrentTime();
14206      newTime = seekableStart + distance * liveTracker.liveWindow();
14207
14208      // Don't let video end while scrubbing.
14209      if (newTime >= seekableEnd) {
14210        newTime = seekableEnd;
14211      }
14212
14213      // Compensate for precision differences so that currentTime is not less
14214      // than seekable start
14215      if (newTime <= seekableStart) {
14216        newTime = seekableStart + 0.1;
14217      }
14218
14219      // On android seekableEnd can be Infinity sometimes,
14220      // this will cause newTime to be Infinity, which is
14221      // not a valid currentTime.
14222      if (newTime === Infinity) {
14223        return;
14224      }
14225    }
14226
14227    // Set new time (tell player to seek to new time)
14228    this.userSeek_(newTime);
14229    if (this.player_.options_.enableSmoothSeeking) {
14230      this.update();
14231    }
14232  }
14233  enable() {
14234    super.enable();
14235    const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14236    if (!mouseTimeDisplay) {
14237      return;
14238    }
14239    mouseTimeDisplay.show();
14240  }
14241  disable() {
14242    super.disable();
14243    const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14244    if (!mouseTimeDisplay) {
14245      return;
14246    }
14247    mouseTimeDisplay.hide();
14248  }
14249
14250  /**
14251   * Handle mouse up on seek bar
14252   *
14253   * @param {MouseEvent} event
14254   *        The `mouseup` event that caused this to run.
14255   *
14256   * @listens mouseup
14257   */
14258  handleMouseUp(event) {
14259    super.handleMouseUp(event);
14260
14261    // Stop event propagation to prevent double fire in progress-control.js
14262    if (event) {
14263      event.stopPropagation();
14264    }
14265    this.player_.scrubbing(false);
14266
14267    /**
14268     * Trigger timeupdate because we're done seeking and the time has changed.
14269     * This is particularly useful for if the player is paused to time the time displays.
14270     *
14271     * @event Tech#timeupdate
14272     * @type {Event}
14273     */
14274    this.player_.trigger({
14275      type: 'timeupdate',
14276      target: this,
14277      manuallyTriggered: true,
14278    });
14279    if (this.videoWasPlaying) {
14280      silencePromise(this.player_.play());
14281    } else {
14282      // We're done seeking and the time has changed.
14283      // If the player is paused, make sure we display the correct time on the seek bar.
14284      this.update_();
14285    }
14286  }
14287
14288  /**
14289   * Move more quickly fast forward for keyboard-only users
14290   */
14291  stepForward() {
14292    this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
14293  }
14294
14295  /**
14296   * Move more quickly rewind for keyboard-only users
14297   */
14298  stepBack() {
14299    this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
14300  }
14301
14302  /**
14303   * Toggles the playback state of the player
14304   * This gets called when enter or space is used on the seekbar
14305   *
14306   * @param {KeyboardEvent} event
14307   *        The `keydown` event that caused this function to be called
14308   *
14309   */
14310  handleAction(event) {
14311    if (this.player_.paused()) {
14312      this.player_.play();
14313    } else {
14314      this.player_.pause();
14315    }
14316  }
14317
14318  /**
14319   * Called when this SeekBar has focus and a key gets pressed down.
14320   * Supports the following keys:
14321   *
14322   *   Space or Enter key fire a click event
14323   *   Home key moves to start of the timeline
14324   *   End key moves to end of the timeline
14325   *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
14326   *   PageDown key moves back a larger step than ArrowDown
14327   *   PageUp key moves forward a large step
14328   *
14329   * @param {KeyboardEvent} event
vendor: 5,904 bytes, lines 14330-14519
14330   *        The `keydown` event that caused this function to be called.
14331   *
14332   * @listens keydown
14333   */
14334  handleKeyDown(event) {
14335    const liveTracker = this.player_.liveTracker;
14336    if (event.key === ' ' || event.key === 'Enter') {
14337      event.preventDefault();
14338      event.stopPropagation();
14339      this.handleAction(event);
14340    } else if (event.key === 'Home') {
14341      event.preventDefault();
14342      event.stopPropagation();
14343      this.userSeek_(0);
14344    } else if (event.key === 'End') {
14345      event.preventDefault();
14346      event.stopPropagation();
14347      if (liveTracker && liveTracker.isLive()) {
14348        this.userSeek_(liveTracker.liveCurrentTime());
14349      } else {
14350        this.userSeek_(this.player_.duration());
14351      }
14352    } else if (/^[0-9]$/.test(event.key)) {
14353      event.preventDefault();
14354      event.stopPropagation();
14355      const gotoFraction = parseInt(event.key, 10) * 0.1;
14356      if (liveTracker && liveTracker.isLive()) {
14357        this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
14358      } else {
14359        this.userSeek_(this.player_.duration() * gotoFraction);
14360      }
14361    } else if (event.key === 'PageDown') {
14362      event.preventDefault();
14363      event.stopPropagation();
14364      this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
14365    } else if (event.key === 'PageUp') {
14366      event.preventDefault();
14367      event.stopPropagation();
14368      this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
14369    } else {
14370      // Pass keydown handling up for unsupported keys
14371      super.handleKeyDown(event);
14372    }
14373  }
14374  dispose() {
14375    this.disableInterval_();
14376    this.off(this.player_, ['durationchange', 'timeupdate'], this.update);
14377    this.off(this.player_, ['ended'], this.update_);
14378    if (this.player_.liveTracker) {
14379      this.off(this.player_.liveTracker, 'liveedgechange', this.update);
14380    }
14381    this.off(this.player_, ['playing'], this.enableIntervalHandler_);
14382    this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
14383
14384    // we don't need to update the play progress if the document is hidden,
14385    // also, this causes the CPU to spike and eventually crash the page on IE11.
14386    if (
14387      'hidden' in document__default['default'] &&
14388      'visibilityState' in document__default['default']
14389    ) {
14390      this.off(document__default['default'], 'visibilitychange', this.toggleVisibility_);
14391    }
14392    super.dispose();
14393  }
14394}
14395
14396/**
14397 * Default options for the `SeekBar`
14398 *
14399 * @type {Object}
14400 * @private
14401 */
14402SeekBar.prototype.options_ = {
14403  children: ['loadProgressBar', 'playProgressBar'],
14404  barName: 'playProgressBar',
14405};
14406
14407// MouseTimeDisplay tooltips should not be added to a player on mobile devices
14408if (!IS_IOS && !IS_ANDROID) {
14409  SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
14410}
14411Component$1.registerComponent('SeekBar', SeekBar);
14412
14413/**
14414 * @file progress-control.js
14415 */
14416
14417/**
14418 * The Progress Control component contains the seek bar, load progress,
14419 * and play progress.
14420 *
14421 * @extends Component
14422 */
14423class ProgressControl extends Component$1 {
14424  /**
14425   * Creates an instance of this class.
14426   *
14427   * @param {Player} player
14428   *        The `Player` that this class should be attached to.
14429   *
14430   * @param {Object} [options]
14431   *        The key/value store of player options.
14432   */
14433  constructor(player, options) {
14434    super(player, options);
14435    this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
14436    this.throttledHandleMouseSeek = throttle(
14437      bind_(this, this.handleMouseSeek),
14438      UPDATE_REFRESH_INTERVAL
14439    );
14440    this.handleMouseUpHandler_ = (e) => this.handleMouseUp(e);
14441    this.handleMouseDownHandler_ = (e) => this.handleMouseDown(e);
14442    this.enable();
14443  }
14444
14445  /**
14446   * Create the `Component`'s DOM element
14447   *
14448   * @return {Element}
14449   *         The element that was created.
14450   */
14451  createEl() {
14452    return super.createEl('div', {
14453      className: 'vjs-progress-control vjs-control',
14454    });
14455  }
14456
14457  /**
14458   * When the mouse moves over the `ProgressControl`, the pointer position
14459   * gets passed down to the `MouseTimeDisplay` component.
14460   *
14461   * @param {Event} event
14462   *        The `mousemove` event that caused this function to run.
14463   *
14464   * @listen mousemove
14465   */
14466  handleMouseMove(event) {
14467    const seekBar = this.getChild('seekBar');
14468    if (!seekBar) {
14469      return;
14470    }
14471    const playProgressBar = seekBar.getChild('playProgressBar');
14472    const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
14473    if (!playProgressBar && !mouseTimeDisplay) {
14474      return;
14475    }
14476    const seekBarEl = seekBar.el();
14477    const seekBarRect = findPosition(seekBarEl);
14478    let seekBarPoint = getPointerPosition(seekBarEl, event).x;
14479
14480    // The default skin has a gap on either side of the `SeekBar`. This means
14481    // that it's possible to trigger this behavior outside the boundaries of
14482    // the `SeekBar`. This ensures we stay within it at all times.
14483    seekBarPoint = clamp(seekBarPoint, 0, 1);
14484    if (mouseTimeDisplay) {
14485      mouseTimeDisplay.update(seekBarRect, seekBarPoint);
14486    }
14487    if (playProgressBar) {
14488      playProgressBar.update(seekBarRect, seekBar.getProgress());
14489    }
14490  }
14491
14492  /**
14493   * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
14494   *
14495   * @method ProgressControl#throttledHandleMouseSeek
14496   * @param {Event} event
14497   *        The `mousemove` event that caused this function to run.
14498   *
14499   * @listen mousemove
14500   * @listen touchmove
14501   */
14502
14503  /**
14504   * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
14505   *
14506   * @param {Event} event
14507   *        `mousedown` or `touchstart` event that triggered this function
14508   *
14509   * @listens mousemove
14510   * @listens touchmove
14511   */
14512  handleMouseSeek(event) {
14513    const seekBar = this.getChild('seekBar');
14514    if (seekBar) {
14515      seekBar.handleMouseMove(event);
14516    }
14517  }
14518
14519  /**
14520   * Are controls are currently enabled for this progress control.
14521   *
14522   * @return {boolean}
14523   *         true if controls are enabled, false otherwise
14524   */
14525  enabled() {
14526    return this.enabled_;
14527  }
14528
14529  /**
14530   * Disable all controls on the progress control and its children
14531   */
14532  disable() {
14533    this.children().forEach((child) => child.disable && child.disable());
14534    if (!this.enabled()) {
14535      return;
14536    }
14537    this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
14538    this.off(this.el_, 'mousemove', this.handleMouseMove);
14539    this.removeListenersAddedOnMousedownAndTouchstart();
14540    this.addClass('disabled');
14541    this.enabled_ = false;
14542
14543    // Restore normal playback state if controls are disabled while scrubbing
14544    if (this.player_.scrubbing()) {
14545      const seekBar = this.getChild('seekBar');
14546      this.player_.scrubbing(false);
14547      if (seekBar.videoWasPlaying) {
14548        silencePromise(this.player_.play());
14549      }
14550    }
14551  }
14552
14553  /**
14554   * Enable all controls on the progress control and its children
14555   */
14556  enable() {
14557    this.children().forEach((child) => child.enable && child.enable());
14558    if (this.enabled()) {
14559      return;
14560    }
14561    this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
14562    this.on(this.el_, 'mousemove', this.handleMouseMove);
14563    this.removeClass('disabled');
14564    this.enabled_ = true;
14565  }
14566
14567  /**
14568   * Cleanup listeners after the user finishes interacting with the progress controls
14569   */
14570  removeListenersAddedOnMousedownAndTouchstart() {
14571    const doc = this.el_.ownerDocument;
14572    this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
14573    this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
14574    this.off(doc, 'mouseup', this.handleMouseUpHandler_);
14575    this.off(doc, 'touchend', this.handleMouseUpHandler_);
14576  }
14577
14578  /**
14579   * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
14580   *
14581   * @param {Event} event
14582   *        `mousedown` or `touchstart` event that triggered this function
14583   *
14584   * @listens mousedown
14585   * @listens touchstart
14586   */
14587  handleMouseDown(event) {
14588    const doc = this.el_.ownerDocument;
14589    const seekBar = this.getChild('seekBar');
14590    if (seekBar) {
14591      seekBar.handleMouseDown(event);
14592    }
14593    this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
14594    this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
14595    this.on(doc, 'mouseup', this.handleMouseUpHandler_);
14596    this.on(doc, 'touchend', this.handleMouseUpHandler_);
14597  }
14598
14599  /**
14600   * Handle `mouseup` or `touchend` events on the `ProgressControl`.
14601   *
14602   * @param {Event} event
14603   *        `mouseup` or `touchend` event that triggered this function.
14604   *
14605   * @listens touchend
14606   * @listens mouseup
14607   */
14608  handleMouseUp(event) {
14609    const seekBar = this.getChild('seekBar');
14610    if (seekBar) {
14611      seekBar.handleMouseUp(event);
14612    }
14613    this.removeListenersAddedOnMousedownAndTouchstart();
14614  }
14615}
14616
14617/**
14618 * Default options for `ProgressControl`
14619 *
14620 * @type {Object}
14621 * @private
14622 */
14623ProgressControl.prototype.options_ = {
14624  children: ['seekBar'],
14625};
14626Component$1.registerComponent('ProgressControl', ProgressControl);
14627
14628/**
14629 * @file picture-in-picture-toggle.js
14630 */
14631
14632/** @import Player from './player' */
14633
14634/**
14635 * Toggle Picture-in-Picture mode
14636 *
14637 * @extends Button
14638 */
14639class PictureInPictureToggle extends Button {
14640  /**
14641   * Creates an instance of this class.
14642   *
14643   * @param {Player} player
14644   *        The `Player` that this class should be attached to.
14645   *
14646   * @param {Object} [options]
14647   *        The key/value store of player options.
14648   *
14649   * @listens Player#enterpictureinpicture
14650   * @listens Player#leavepictureinpicture
14651   */
14652  constructor(player, options) {
14653    super(player, options);
14654    this.setIcon('picture-in-picture-enter');
14655    this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], (e) =>
14656      this.handlePictureInPictureChange(e)
14657    );
14658    this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], (e) =>
14659      this.handlePictureInPictureEnabledChange(e)
14660    );
14661    this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () =>
14662      this.handlePictureInPictureAudioModeChange()
14663    );
14664
14665    // TODO: Deactivate button on player emptied event.
14666    this.disable();
14667  }
14668
14669  /**
14670   * Builds the default DOM `className`.
14671   *
14672   * @return {string}
14673   *         The DOM `className` for this object.
14674   */
14675  buildCSSClass() {
14676    return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
14677  }
14678
14679  /**
14680   * Displays or hides the button depending on the audio mode detection.
14681   * Exits picture-in-picture if it is enabled when switching to audio mode.
14682   */
14683  handlePictureInPictureAudioModeChange() {
14684    // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
14685    const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
14686    const isAudioMode =
14687      isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();
14688    if (!isAudioMode) {
14689      this.show();
14690      return;
14691    }
14692    if (this.player_.isInPictureInPicture()) {
14693      this.player_.exitPictureInPicture();
14694    }
14695    this.hide();
14696  }
14697
14698  /**
14699   * Enables or disables button based on availability of a Picture-In-Picture mode.
14700   *
14701   * Enabled if
14702   * - `player.options().enableDocumentPictureInPicture` is true and
14703   *   window.documentPictureInPicture is available; or
14704   * - `player.disablePictureInPicture()` is false and
14705   *   element.requestPictureInPicture is available
14706   */
14707  handlePictureInPictureEnabledChange() {
14708    if (
14709      (document__default['default'].pictureInPictureEnabled &&
14710        this.player_.disablePictureInPicture() === false) ||
14711      (this.player_.options_.enableDocumentPictureInPicture &&
14712        'documentPictureInPicture' in window__default['default'])
14713    ) {
14714      this.enable();
14715    } else {
14716      this.disable();
14717    }
14718  }
14719
14720  /**
14721   * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
14722   *
14723   * @param {Event} [event]
14724   *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
14725   *        called.
14726   *
14727   * @listens Player#enterpictureinpicture
14728   * @listens Player#leavepictureinpicture
14729   */
14730  handlePictureInPictureChange(event) {
14731    if (this.player_.isInPictureInPicture()) {
14732      this.setIcon('picture-in-picture-exit');
14733      this.controlText('Exit Picture-in-Picture');
14734    } else {
14735      this.setIcon('picture-in-picture-enter');
14736      this.controlText('Picture-in-Picture');
14737    }
14738    this.handlePictureInPictureEnabledChange();
14739  }
14740
14741  /**
14742   * This gets called when an `PictureInPictureToggle` is "clicked". See
14743   * {@link ClickableComponent} for more detailed information on what a click can be.
14744   *
14745   * @param {Event} [event]
vendor: 6,879 bytes, lines 14746-15004
14746   *        The `keydown`, `tap`, or `click` event that caused this function to be
14747   *        called.
14748   *
14749   * @listens tap
14750   * @listens click
14751   */
14752  handleClick(event) {
14753    if (!this.player_.isInPictureInPicture()) {
14754      this.player_.requestPictureInPicture();
14755    } else {
14756      this.player_.exitPictureInPicture();
14757    }
14758  }
14759
14760  /**
14761   * Show the `Component`s element if it is hidden by removing the
14762   * 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
14763   */
14764  show() {
14765    // Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
14766    if (typeof document__default['default'].exitPictureInPicture !== 'function') {
14767      return;
14768    }
14769    super.show();
14770  }
14771}
14772
14773/**
14774 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
14775 *
14776 * @type {string}
14777 * @protected
14778 */
14779PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
14780Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
14781
14782/**
14783 * @file fullscreen-toggle.js
14784 */
14785
14786/** @import Player from './player' */
14787
14788/**
14789 * Toggle fullscreen video
14790 *
14791 * @extends Button
14792 */
14793class FullscreenToggle extends Button {
14794  /**
14795   * Creates an instance of this class.
14796   *
14797   * @param {Player} player
14798   *        The `Player` that this class should be attached to.
14799   *
14800   * @param {Object} [options]
14801   *        The key/value store of player options.
14802   */
14803  constructor(player, options) {
14804    super(player, options);
14805    this.setIcon('fullscreen-enter');
14806    this.on(player, 'fullscreenchange', (e) => this.handleFullscreenChange(e));
14807    if (document__default['default'][player.fsApi_.fullscreenEnabled] === false) {
14808      this.disable();
14809    }
14810  }
14811
14812  /**
14813   * Builds the default DOM `className`.
14814   *
14815   * @return {string}
14816   *         The DOM `className` for this object.
14817   */
14818  buildCSSClass() {
14819    return `vjs-fullscreen-control ${super.buildCSSClass()}`;
14820  }
14821
14822  /**
14823   * Handles fullscreenchange on the player and change control text accordingly.
14824   *
14825   * @param {Event} [event]
14826   *        The {@link Player#fullscreenchange} event that caused this function to be
14827   *        called.
14828   *
14829   * @listens Player#fullscreenchange
14830   */
14831  handleFullscreenChange(event) {
14832    if (this.player_.isFullscreen()) {
14833      this.controlText('Exit Fullscreen');
14834      this.setIcon('fullscreen-exit');
14835    } else {
14836      this.controlText('Fullscreen');
14837      this.setIcon('fullscreen-enter');
14838    }
14839  }
14840
14841  /**
14842   * This gets called when an `FullscreenToggle` is "clicked". See
14843   * {@link ClickableComponent} for more detailed information on what a click can be.
14844   *
14845   * @param {Event} [event]
14846   *        The `keydown`, `tap`, or `click` event that caused this function to be
14847   *        called.
14848   *
14849   * @listens tap
14850   * @listens click
14851   */
14852  handleClick(event) {
14853    if (!this.player_.isFullscreen()) {
14854      this.player_.requestFullscreen();
14855    } else {
14856      this.player_.exitFullscreen();
14857    }
14858  }
14859}
14860
14861/**
14862 * The text that should display over the `FullscreenToggle`s controls. Added for localization.
14863 *
14864 * @type {string}
14865 * @protected
14866 */
14867FullscreenToggle.prototype.controlText_ = 'Fullscreen';
14868Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
14869
14870/** @import Component from '../../component' */
14871/** @import Player from '../../player' */
14872
14873/**
14874 * Check if volume control is supported and if it isn't hide the
14875 * `Component` that was passed  using the `vjs-hidden` class.
14876 *
14877 * @param {Component} self
14878 *        The component that should be hidden if volume is unsupported
14879 *
14880 * @param {Player} player
14881 *        A reference to the player
14882 *
14883 * @private
14884 */
14885const checkVolumeSupport = function (self, player) {
14886  // hide volume controls when they're not supported by the current tech
14887  if (player.tech_ && !player.tech_.featuresVolumeControl) {
14888    self.addClass('vjs-hidden');
14889  }
14890  self.on(player, 'loadstart', function () {
14891    if (!player.tech_.featuresVolumeControl) {
14892      self.addClass('vjs-hidden');
14893    } else {
14894      self.removeClass('vjs-hidden');
14895    }
14896  });
14897};
14898
14899/**
14900 * @file volume-level.js
14901 */
14902
14903/**
14904 * Shows volume level
14905 *
14906 * @extends Component
14907 */
14908class VolumeLevel extends Component$1 {
14909  /**
14910   * Create the `Component`'s DOM element
14911   *
14912   * @return {Element}
14913   *         The element that was created.
14914   */
14915  createEl() {
14916    const el = super.createEl('div', {
14917      className: 'vjs-volume-level',
14918    });
14919    this.setIcon('circle', el);
14920    el.appendChild(
14921      super.createEl('span', {
14922        className: 'vjs-control-text',
14923      })
14924    );
14925    return el;
14926  }
14927}
14928Component$1.registerComponent('VolumeLevel', VolumeLevel);
14929
14930/**
14931 * @file volume-level-tooltip.js
14932 */
14933
14934/** @import Player from '../../player' */
14935
14936/**
14937 * Volume level tooltips display a volume above or side by side the volume bar.
14938 *
14939 * @extends Component
14940 */
14941class VolumeLevelTooltip extends Component$1 {
14942  /**
14943   * Creates an instance of this class.
14944   *
14945   * @param {Player} player
14946   *        The {@link Player} that this class should be attached to.
14947   *
14948   * @param {Object} [options]
14949   *        The key/value store of player options.
14950   */
14951  constructor(player, options) {
14952    super(player, options);
14953    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14954  }
14955
14956  /**
14957   * Create the volume tooltip DOM element
14958   *
14959   * @return {Element}
14960   *         The element that was created.
14961   */
14962  createEl() {
14963    return super.createEl(
14964      'div',
14965      {
14966        className: 'vjs-volume-tooltip',
14967      },
14968      {
14969        'aria-hidden': 'true',
14970      }
14971    );
14972  }
14973
14974  /**
14975   * Updates the position of the tooltip relative to the `VolumeBar` and
14976   * its content text.
14977   *
14978   * @param {Object} rangeBarRect
14979   *        The `ClientRect` for the {@link VolumeBar} element.
14980   *
14981   * @param {number} rangeBarPoint
14982   *        A number from 0 to 1, representing a horizontal/vertical reference point
14983   *        from the left edge of the {@link VolumeBar}
14984   *
14985   * @param {boolean} vertical
14986   *        Referees to the Volume control position
14987   *        in the control bar{@link VolumeControl}
14988   *
14989   */
14990  update(rangeBarRect, rangeBarPoint, vertical, content) {
14991    if (!vertical) {
14992      const tooltipRect = getBoundingClientRect(this.el_);
14993      const playerRect = getBoundingClientRect(this.player_.el());
14994      const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
14995      if (!playerRect || !tooltipRect) {
14996        return;
14997      }
14998      const spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
14999      const spaceRightOfPoint =
15000        rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
15001      let pullTooltipBy = tooltipRect.width / 2;
15002      if (spaceLeftOfPoint < pullTooltipBy) {
15003        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15004      }
vendor: 19,162 bytes, lines 15004-15701
15004 else if (spaceRightOfPoint < pullTooltipBy) {
15005        pullTooltipBy = spaceRightOfPoint;
15006      }
15007      if (pullTooltipBy < 0) {
15008        pullTooltipBy = 0;
15009      } else if (pullTooltipBy > tooltipRect.width) {
15010        pullTooltipBy = tooltipRect.width;
15011      }
15012      this.el_.style.right = `-${pullTooltipBy}px`;
15013    }
15014    this.write(`${content}%`);
15015  }
15016
15017  /**
15018   * Write the volume to the tooltip DOM element.
15019   *
15020   * @param {string} content
15021   *        The formatted volume for the tooltip.
15022   */
15023  write(content) {
15024    textContent(this.el_, content);
15025  }
15026
15027  /**
15028   * Updates the position of the volume tooltip relative to the `VolumeBar`.
15029   *
15030   * @param {Object} rangeBarRect
15031   *        The `ClientRect` for the {@link VolumeBar} element.
15032   *
15033   * @param {number} rangeBarPoint
15034   *        A number from 0 to 1, representing a horizontal/vertical reference point
15035   *        from the left edge of the {@link VolumeBar}
15036   *
15037   * @param {boolean} vertical
15038   *        Referees to the Volume control position
15039   *        in the control bar{@link VolumeControl}
15040   *
15041   * @param {number} volume
15042   *        The volume level to update the tooltip to
15043   *
15044   * @param {Function} cb
15045   *        A function that will be called during the request animation frame
15046   *        for tooltips that need to do additional animations from the default
15047   */
15048  updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
15049    this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
15050      this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
15051      if (cb) {
15052        cb();
15053      }
15054    });
15055  }
15056}
15057Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
15058
15059/**
15060 * @file mouse-volume-level-display.js
15061 */
15062
15063/**
15064 * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
15065 * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
15066 * indicating the volume level which is represented by a given point in the
15067 * {@link VolumeBar}.
15068 *
15069 * @extends Component
15070 */
15071class MouseVolumeLevelDisplay extends Component$1 {
15072  /**
15073   * Creates an instance of this class.
15074   *
15075   * @param {Player} player
15076   *        The {@link Player} that this class should be attached to.
15077   *
15078   * @param {Object} [options]
15079   *        The key/value store of player options.
15080   */
15081  constructor(player, options) {
15082    super(player, options);
15083    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
15084  }
15085
15086  /**
15087   * Create the DOM element for this class.
15088   *
15089   * @return {Element}
15090   *         The element that was created.
15091   */
15092  createEl() {
15093    return super.createEl('div', {
15094      className: 'vjs-mouse-display',
15095    });
15096  }
15097
15098  /**
15099   * Enquires updates to its own DOM as well as the DOM of its
15100   * {@link VolumeLevelTooltip} child.
15101   *
15102   * @param {Object} rangeBarRect
15103   *        The `ClientRect` for the {@link VolumeBar} element.
15104   *
15105   * @param {number} rangeBarPoint
15106   *        A number from 0 to 1, representing a horizontal/vertical reference point
15107   *        from the left edge of the {@link VolumeBar}
15108   *
15109   * @param {boolean} vertical
15110   *        Referees to the Volume control position
15111   *        in the control bar{@link VolumeControl}
15112   *
15113   */
15114  update(rangeBarRect, rangeBarPoint, vertical) {
15115    const volume = 100 * rangeBarPoint;
15116    this.getChild('volumeLevelTooltip').updateVolume(
15117      rangeBarRect,
15118      rangeBarPoint,
15119      vertical,
15120      volume,
15121      () => {
15122        if (vertical) {
15123          this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
15124        } else {
15125          this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
15126        }
15127      }
15128    );
15129  }
15130}
15131
15132/**
15133 * Default options for `MouseVolumeLevelDisplay`
15134 *
15135 * @type {Object}
15136 * @private
15137 */
15138MouseVolumeLevelDisplay.prototype.options_ = {
15139  children: ['volumeLevelTooltip'],
15140};
15141Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
15142
15143/**
15144 * @file volume-bar.js
15145 */
15146
15147/**
15148 * The bar that contains the volume level and can be clicked on to adjust the level
15149 *
15150 * @extends Slider
15151 */
15152class VolumeBar extends Slider {
15153  /**
15154   * Creates an instance of this class.
15155   *
15156   * @param {Player} player
15157   *        The `Player` that this class should be attached to.
15158   *
15159   * @param {Object} [options]
15160   *        The key/value store of player options.
15161   */
15162  constructor(player, options) {
15163    super(player, options);
15164    this.on('slideractive', (e) => this.updateLastVolume_(e));
15165    this.on(player, 'volumechange', (e) => this.updateARIAAttributes(e));
15166    player.ready(() => this.updateARIAAttributes());
15167  }
15168
15169  /**
15170   * Create the `Component`'s DOM element
15171   *
15172   * @return {Element}
15173   *         The element that was created.
15174   */
15175  createEl() {
15176    return super.createEl(
15177      'div',
15178      {
15179        className: 'vjs-volume-bar vjs-slider-bar',
15180      },
15181      {
15182        'aria-label': this.localize('Volume Level'),
15183        'aria-live': 'polite',
15184      }
15185    );
15186  }
15187
15188  /**
15189   * Handle mouse down on volume bar
15190   *
15191   * @param {Event} event
15192   *        The `mousedown` event that caused this to run.
15193   *
15194   * @listens mousedown
15195   */
15196  handleMouseDown(event) {
15197    if (!isSingleLeftClick(event)) {
15198      return;
15199    }
15200    super.handleMouseDown(event);
15201  }
15202
15203  /**
15204   * Handle movement events on the {@link VolumeMenuButton}.
15205   *
15206   * @param {Event} event
15207   *        The event that caused this function to run.
15208   *
15209   * @listens mousemove
15210   */
15211  handleMouseMove(event) {
15212    const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
15213    if (mouseVolumeLevelDisplay) {
15214      const volumeBarEl = this.el();
15215      const volumeBarRect = getBoundingClientRect(volumeBarEl);
15216      const vertical = this.vertical();
15217      let volumeBarPoint = getPointerPosition(volumeBarEl, event);
15218      volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
15219      // The default skin has a gap on either side of the `VolumeBar`. This means
15220      // that it's possible to trigger this behavior outside the boundaries of
15221      // the `VolumeBar`. This ensures we stay within it at all times.
15222      volumeBarPoint = clamp(volumeBarPoint, 0, 1);
15223      mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
15224    }
15225    if (!isSingleLeftClick(event)) {
15226      return;
15227    }
15228    this.checkMuted();
15229    this.player_.volume(this.calculateDistance(event));
15230  }
15231
15232  /**
15233   * If the player is muted unmute it.
15234   */
15235  checkMuted() {
15236    if (this.player_.muted()) {
15237      this.player_.muted(false);
15238    }
15239  }
15240
15241  /**
15242   * Get percent of volume level
15243   *
15244   * @return {number}
15245   *         Volume level percent as a decimal number.
15246   */
15247  getPercent() {
15248    if (this.player_.muted()) {
15249      return 0;
15250    }
15251    return this.player_.volume();
15252  }
15253
15254  /**
15255   * Increase volume level for keyboard users
15256   */
15257  stepForward() {
15258    this.checkMuted();
15259    this.player_.volume(this.player_.volume() + 0.1);
15260  }
15261
15262  /**
15263   * Decrease volume level for keyboard users
15264   */
15265  stepBack() {
15266    this.checkMuted();
15267    this.player_.volume(this.player_.volume() - 0.1);
15268  }
15269
15270  /**
15271   * Update ARIA accessibility attributes
15272   *
15273   * @param {Event} [event]
15274   *        The `volumechange` event that caused this function to run.
15275   *
15276   * @listens Player#volumechange
15277   */
15278  updateARIAAttributes(event) {
15279    const ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15280    this.el_.setAttribute('aria-valuenow', ariaValue);
15281    this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15282  }
15283
15284  /**
15285   * Returns the current value of the player volume as a percentage
15286   *
15287   * @private
15288   */
15289  volumeAsPercentage_() {
15290    return Math.round(this.player_.volume() * 100);
15291  }
15292
15293  /**
15294   * When user starts dragging the VolumeBar, store the volume and listen for
15295   * the end of the drag. When the drag ends, if the volume was set to zero,
15296   * set lastVolume to the stored volume.
15297   *
15298   * @listens slideractive
15299   * @private
15300   */
15301  updateLastVolume_() {
15302    const volumeBeforeDrag = this.player_.volume();
15303    this.one('sliderinactive', () => {
15304      if (this.player_.volume() === 0) {
15305        this.player_.lastVolume_(volumeBeforeDrag);
15306      }
15307    });
15308  }
15309}
15310
15311/**
15312 * Default options for the `VolumeBar`
15313 *
15314 * @type {Object}
15315 * @private
15316 */
15317VolumeBar.prototype.options_ = {
15318  children: ['volumeLevel'],
15319  barName: 'volumeLevel',
15320};
15321
15322// MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
15323if (!IS_IOS && !IS_ANDROID) {
15324  VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
15325}
15326
15327/**
15328 * Call the update event for this Slider when this event happens on the player.
15329 *
15330 * @type {string}
15331 */
15332VolumeBar.prototype.playerEvent = 'volumechange';
15333Component$1.registerComponent('VolumeBar', VolumeBar);
15334
15335/**
15336 * @file volume-control.js
15337 */
15338
15339/**
15340 * The component for controlling the volume level
15341 *
15342 * @extends Component
15343 */
15344class VolumeControl extends Component$1 {
15345  /**
15346   * Creates an instance of this class.
15347   *
15348   * @param {Player} player
15349   *        The `Player` that this class should be attached to.
15350   *
15351   * @param {Object} [options={}]
15352   *        The key/value store of player options.
15353   */
15354  constructor(player, options = {}) {
15355    options.vertical = options.vertical || false;
15356
15357    // Pass the vertical option down to the VolumeBar if
15358    // the VolumeBar is turned on.
15359    if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
15360      options.volumeBar = options.volumeBar || {};
15361      options.volumeBar.vertical = options.vertical;
15362    }
15363    super(player, options);
15364
15365    // hide this control if volume support is missing
15366    checkVolumeSupport(this, player);
15367    this.throttledHandleMouseMove = throttle(
15368      bind_(this, this.handleMouseMove),
15369      UPDATE_REFRESH_INTERVAL
15370    );
15371    this.handleMouseUpHandler_ = (e) => this.handleMouseUp(e);
15372    this.on('mousedown', (e) => this.handleMouseDown(e));
15373    this.on('touchstart', (e) => this.handleMouseDown(e));
15374    this.on('mousemove', (e) => this.handleMouseMove(e));
15375
15376    // while the slider is active (the mouse has been pressed down and
15377    // is dragging) or in focus we do not want to hide the VolumeBar
15378    this.on(this.volumeBar, ['focus', 'slideractive'], () => {
15379      this.volumeBar.addClass('vjs-slider-active');
15380      this.addClass('vjs-slider-active');
15381      this.trigger('slideractive');
15382    });
15383    this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
15384      this.volumeBar.removeClass('vjs-slider-active');
15385      this.removeClass('vjs-slider-active');
15386      this.trigger('sliderinactive');
15387    });
15388  }
15389
15390  /**
15391   * Create the `Component`'s DOM element
15392   *
15393   * @return {Element}
15394   *         The element that was created.
15395   */
15396  createEl() {
15397    let orientationClass = 'vjs-volume-horizontal';
15398    if (this.options_.vertical) {
15399      orientationClass = 'vjs-volume-vertical';
15400    }
15401    return super.createEl('div', {
15402      className: `vjs-volume-control vjs-control ${orientationClass}`,
15403    });
15404  }
15405
15406  /**
15407   * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15408   *
15409   * @param {Event} event
15410   *        `mousedown` or `touchstart` event that triggered this function
15411   *
15412   * @listens mousedown
15413   * @listens touchstart
15414   */
15415  handleMouseDown(event) {
15416    const doc = this.el_.ownerDocument;
15417    this.on(doc, 'mousemove', this.throttledHandleMouseMove);
15418    this.on(doc, 'touchmove', this.throttledHandleMouseMove);
15419    this.on(doc, 'mouseup', this.handleMouseUpHandler_);
15420    this.on(doc, 'touchend', this.handleMouseUpHandler_);
15421  }
15422
15423  /**
15424   * Handle `mouseup` or `touchend` events on the `VolumeControl`.
15425   *
15426   * @param {Event} event
15427   *        `mouseup` or `touchend` event that triggered this function.
15428   *
15429   * @listens touchend
15430   * @listens mouseup
15431   */
15432  handleMouseUp(event) {
15433    const doc = this.el_.ownerDocument;
15434    this.off(doc, 'mousemove', this.throttledHandleMouseMove);
15435    this.off(doc, 'touchmove', this.throttledHandleMouseMove);
15436    this.off(doc, 'mouseup', this.handleMouseUpHandler_);
15437    this.off(doc, 'touchend', this.handleMouseUpHandler_);
15438  }
15439
15440  /**
15441   * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15442   *
15443   * @param {Event} event
15444   *        `mousedown` or `touchstart` event that triggered this function
15445   *
15446   * @listens mousedown
15447   * @listens touchstart
15448   */
15449  handleMouseMove(event) {
15450    this.volumeBar.handleMouseMove(event);
15451  }
15452}
15453
15454/**
15455 * Default options for the `VolumeControl`
15456 *
15457 * @type {Object}
15458 * @private
15459 */
15460VolumeControl.prototype.options_ = {
15461  children: ['volumeBar'],
15462};
15463Component$1.registerComponent('VolumeControl', VolumeControl);
15464
15465/** @import Component from '../../component' */
15466/** @import Player from '../../player' */
15467
15468/**
15469 * Check if muting volume is supported and if it isn't hide the mute toggle
15470 * button.
15471 *
15472 * @param {Component} self
15473 *        A reference to the mute toggle button
15474 *
15475 * @param {Player} player
15476 *        A reference to the player
15477 *
15478 * @private
15479 */
15480const checkMuteSupport = function (self, player) {
15481  // hide mute toggle button if it's not supported by the current tech
15482  if (player.tech_ && !player.tech_.featuresMuteControl) {
15483    self.addClass('vjs-hidden');
15484  }
15485  self.on(player, 'loadstart', function () {
15486    if (!player.tech_.featuresMuteControl) {
15487      self.addClass('vjs-hidden');
15488    } else {
15489      self.removeClass('vjs-hidden');
15490    }
15491  });
15492};
15493
15494/**
15495 * @file mute-toggle.js
15496 */
15497
15498/** @import Player from './player' */
15499
15500/**
15501 * A button component for muting the audio.
15502 *
15503 * @extends Button
15504 */
15505class MuteToggle extends Button {
15506  /**
15507   * Creates an instance of this class.
15508   *
15509   * @param {Player} player
15510   *        The `Player` that this class should be attached to.
15511   *
15512   * @param {Object} [options]
15513   *        The key/value store of player options.
15514   */
15515  constructor(player, options) {
15516    super(player, options);
15517
15518    // hide this control if volume support is missing
15519    checkMuteSupport(this, player);
15520    this.on(player, ['loadstart', 'volumechange'], (e) => this.update(e));
15521  }
15522
15523  /**
15524   * Builds the default DOM `className`.
15525   *
15526   * @return {string}
15527   *         The DOM `className` for this object.
15528   */
15529  buildCSSClass() {
15530    return `vjs-mute-control ${super.buildCSSClass()}`;
15531  }
15532
15533  /**
15534   * This gets called when an `MuteToggle` is "clicked". See
15535   * {@link ClickableComponent} for more detailed information on what a click can be.
15536   *
15537   * @param {Event} [event]
15538   *        The `keydown`, `tap`, or `click` event that caused this function to be
15539   *        called.
15540   *
15541   * @listens tap
15542   * @listens click
15543   */
15544  handleClick(event) {
15545    const vol = this.player_.volume();
15546    const lastVolume = this.player_.lastVolume_();
15547    if (vol === 0) {
15548      const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
15549      this.player_.volume(volumeToSet);
15550      this.player_.muted(false);
15551    } else {
15552      this.player_.muted(this.player_.muted() ? false : true);
15553    }
15554  }
15555
15556  /**
15557   * Update the `MuteToggle` button based on the state of `volume` and `muted`
15558   * on the player.
15559   *
15560   * @param {Event} [event]
15561   *        The {@link Player#loadstart} event if this function was called
15562   *        through an event.
15563   *
15564   * @listens Player#loadstart
15565   * @listens Player#volumechange
15566   */
15567  update(event) {
15568    this.updateIcon_();
15569    this.updateControlText_();
15570  }
15571
15572  /**
15573   * Update the appearance of the `MuteToggle` icon.
15574   *
15575   * Possible states (given `level` variable below):
15576   * - 0: crossed out
15577   * - 1: zero bars of volume
15578   * - 2: one bar of volume
15579   * - 3: two bars of volume
15580   *
15581   * @private
15582   */
15583  updateIcon_() {
15584    const vol = this.player_.volume();
15585    let level = 3;
15586    this.setIcon('volume-high');
15587
15588    // in iOS when a player is loaded with muted attribute
15589    // and volume is changed with a native mute button
15590    // we want to make sure muted state is updated
15591    if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
15592      this.player_.muted(this.player_.tech_.el_.muted);
15593    }
15594    if (vol === 0 || this.player_.muted()) {
15595      this.setIcon('volume-mute');
15596      level = 0;
15597    } else if (vol < 0.33) {
15598      this.setIcon('volume-low');
15599      level = 1;
15600    } else if (vol < 0.67) {
15601      this.setIcon('volume-medium');
15602      level = 2;
15603    }
15604    removeClass(
15605      this.el_,
15606      [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, '')
15607    );
15608    addClass(this.el_, `vjs-vol-${level}`);
15609  }
15610
15611  /**
15612   * If `muted` has changed on the player, update the control text
15613   * (`title` attribute on `vjs-mute-control` element and content of
15614   * `vjs-control-text` element).
15615   *
15616   * @private
15617   */
15618  updateControlText_() {
15619    const soundOff = this.player_.muted() || this.player_.volume() === 0;
15620    const text = soundOff ? 'Unmute' : 'Mute';
15621    if (this.controlText() !== text) {
15622      this.controlText(text);
15623    }
15624  }
15625}
15626
15627/**
15628 * The text that should display over the `MuteToggle`s controls. Added for localization.
15629 *
15630 * @type {string}
15631 * @protected
15632 */
15633MuteToggle.prototype.controlText_ = 'Mute';
15634Component$1.registerComponent('MuteToggle', MuteToggle);
15635
15636/**
15637 * @file volume-control.js
15638 */
15639
15640/**
15641 * A Component to contain the MuteToggle and VolumeControl so that
15642 * they can work together.
15643 *
15644 * @extends Component
15645 */
15646class VolumePanel extends Component$1 {
15647  /**
15648   * Creates an instance of this class.
15649   *
15650   * @param {Player} player
15651   *        The `Player` that this class should be attached to.
15652   *
15653   * @param {Object} [options={}]
15654   *        The key/value store of player options.
15655   */
15656  constructor(player, options = {}) {
15657    if (typeof options.inline !== 'undefined') {
15658      options.inline = options.inline;
15659    } else {
15660      options.inline = true;
15661    }
15662
15663    // pass the inline option down to the VolumeControl as vertical if
15664    // the VolumeControl is on.
15665    if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
15666      options.volumeControl = options.volumeControl || {};
15667      options.volumeControl.vertical = !options.inline;
15668    }
15669    super(player, options);
15670
15671    // this handler is used by mouse handler methods below
15672    this.handleKeyPressHandler_ = (e) => this.handleKeyPress(e);
15673    this.on(player, ['loadstart'], (e) => this.volumePanelState_(e));
15674    this.on(this.muteToggle, 'keyup', (e) => this.handleKeyPress(e));
15675    this.on(this.volumeControl, 'keyup', (e) => this.handleVolumeControlKeyUp(e));
15676    this.on('keydown', (e) => this.handleKeyPress(e));
15677    this.on('mouseover', (e) => this.handleMouseOver(e));
15678    this.on('mouseout', (e) => this.handleMouseOut(e));
15679
15680    // while the slider is active (the mouse has been pressed down and
15681    // is dragging) we do not want to hide the VolumeBar
15682    this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
15683    this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
15684  }
15685
15686  /**
15687   * Add vjs-slider-active class to the VolumePanel
15688   *
15689   * @listens VolumeControl#slideractive
15690   * @private
15691   */
15692  sliderActive_() {
15693    this.addClass('vjs-slider-active');
15694  }
15695
15696  /**
15697   * Removes vjs-slider-active class to the VolumePanel
15698   *
15699   * @listens VolumeControl#sliderinactive
15700   * @private
15701   */
vendor: 20,592 bytes, lines 15702-16447
15702  sliderInactive_() {
15703    this.removeClass('vjs-slider-active');
15704  }
15705
15706  /**
15707   * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
15708   * depending on MuteToggle and VolumeControl state
15709   *
15710   * @listens Player#loadstart
15711   * @private
15712   */
15713  volumePanelState_() {
15714    // hide volume panel if neither volume control or mute toggle
15715    // are displayed
15716    if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
15717      this.addClass('vjs-hidden');
15718    }
15719
15720    // if only mute toggle is visible we don't want
15721    // volume panel expanding when hovered or active
15722    if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
15723      this.addClass('vjs-mute-toggle-only');
15724    }
15725  }
15726
15727  /**
15728   * Create the `Component`'s DOM element
15729   *
15730   * @return {Element}
15731   *         The element that was created.
15732   */
15733  createEl() {
15734    let orientationClass = 'vjs-volume-panel-horizontal';
15735    if (!this.options_.inline) {
15736      orientationClass = 'vjs-volume-panel-vertical';
15737    }
15738    return super.createEl('div', {
15739      className: `vjs-volume-panel vjs-control ${orientationClass}`,
15740    });
15741  }
15742
15743  /**
15744   * Dispose of the `volume-panel` and all child components.
15745   */
15746  dispose() {
15747    this.handleMouseOut();
15748    super.dispose();
15749  }
15750
15751  /**
15752   * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
15753   * the volume panel and sets focus on `MuteToggle`.
15754   *
15755   * @param {Event} event
15756   *        The `keyup` event that caused this function to be called.
15757   *
15758   * @listens keyup
15759   */
15760  handleVolumeControlKeyUp(event) {
15761    if (event.key === 'Escape') {
15762      this.muteToggle.focus();
15763    }
15764  }
15765
15766  /**
15767   * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
15768   * Turns on listening for `mouseover` event. When they happen it
15769   * calls `this.handleMouseOver`.
15770   *
15771   * @param {Event} event
15772   *        The `mouseover` event that caused this function to be called.
15773   *
15774   * @listens mouseover
15775   */
15776  handleMouseOver(event) {
15777    this.addClass('vjs-hover');
15778    on(document__default['default'], 'keyup', this.handleKeyPressHandler_);
15779  }
15780
15781  /**
15782   * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
15783   * Turns on listening for `mouseout` event. When they happen it
15784   * calls `this.handleMouseOut`.
15785   *
15786   * @param {Event} event
15787   *        The `mouseout` event that caused this function to be called.
15788   *
15789   * @listens mouseout
15790   */
15791  handleMouseOut(event) {
15792    this.removeClass('vjs-hover');
15793    off(document__default['default'], 'keyup', this.handleKeyPressHandler_);
15794  }
15795
15796  /**
15797   * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
15798   * looking for ESC, which hides the `VolumeControl`.
15799   *
15800   * @param {Event} event
15801   *        The keypress that triggered this event.
15802   *
15803   * @listens keydown | keyup
15804   */
15805  handleKeyPress(event) {
15806    if (event.key === 'Escape') {
15807      this.handleMouseOut();
15808    }
15809  }
15810}
15811
15812/**
15813 * Default options for the `VolumeControl`
15814 *
15815 * @type {Object}
15816 * @private
15817 */
15818VolumePanel.prototype.options_ = {
15819  children: ['muteToggle', 'volumeControl'],
15820};
15821Component$1.registerComponent('VolumePanel', VolumePanel);
15822
15823/**
15824 * Button to skip forward a configurable amount of time
15825 * through a video. Renders in the control bar.
15826 *
15827 * e.g. options: {controlBar: {skipButtons: forward: 5}}
15828 *
15829 * @extends Button
15830 */
15831class SkipForward extends Button {
15832  constructor(player, options) {
15833    super(player, options);
15834    this.validOptions = [5, 10, 30];
15835    this.skipTime = this.getSkipForwardTime();
15836    if (this.skipTime && this.validOptions.includes(this.skipTime)) {
15837      this.setIcon(`forward-${this.skipTime}`);
15838      this.controlText(
15839        this.localize('Skip forward {1} seconds', [this.skipTime.toLocaleString(player.language())])
15840      );
15841      this.show();
15842    } else {
15843      this.hide();
15844    }
15845  }
15846  getSkipForwardTime() {
15847    const playerOptions = this.options_.playerOptions;
15848    return (
15849      playerOptions.controlBar &&
15850      playerOptions.controlBar.skipButtons &&
15851      playerOptions.controlBar.skipButtons.forward
15852    );
15853  }
15854  buildCSSClass() {
15855    return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
15856  }
15857
15858  /**
15859   * On click, skips forward in the duration/seekable range by a configurable amount of seconds.
15860   * If the time left in the duration/seekable range is less than the configured 'skip forward' time,
15861   * skips to end of duration/seekable range.
15862   *
15863   * Handle a click on a `SkipForward` button
15864   *
15865   * @param {EventTarget~Event} event
15866   *        The `click` event that caused this function
15867   *        to be called
15868   */
15869  handleClick(event) {
15870    if (isNaN(this.player_.duration())) {
15871      return;
15872    }
15873    const currentVideoTime = this.player_.currentTime();
15874    const liveTracker = this.player_.liveTracker;
15875    const duration =
15876      liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
15877    let newTime;
15878    if (currentVideoTime + this.skipTime <= duration) {
15879      newTime = currentVideoTime + this.skipTime;
15880    } else {
15881      newTime = duration;
15882    }
15883    this.player_.currentTime(newTime);
15884  }
15885
15886  /**
15887   * Update control text on languagechange
15888   */
15889  handleLanguagechange() {
15890    this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
15891  }
15892}
15893SkipForward.prototype.controlText_ = 'Skip Forward';
15894Component$1.registerComponent('SkipForward', SkipForward);
15895
15896/**
15897 * Button to skip backward a configurable amount of time
15898 * through a video. Renders in the control bar.
15899 *
15900 *  * e.g. options: {controlBar: {skipButtons: backward: 5}}
15901 *
15902 * @extends Button
15903 */
15904class SkipBackward extends Button {
15905  constructor(player, options) {
15906    super(player, options);
15907    this.validOptions = [5, 10, 30];
15908    this.skipTime = this.getSkipBackwardTime();
15909    if (this.skipTime && this.validOptions.includes(this.skipTime)) {
15910      this.setIcon(`replay-${this.skipTime}`);
15911      this.controlText(
15912        this.localize('Skip backward {1} seconds', [
15913          this.skipTime.toLocaleString(player.language()),
15914        ])
15915      );
15916      this.show();
15917    } else {
15918      this.hide();
15919    }
15920  }
15921  getSkipBackwardTime() {
15922    const playerOptions = this.options_.playerOptions;
15923    return (
15924      playerOptions.controlBar &&
15925      playerOptions.controlBar.skipButtons &&
15926      playerOptions.controlBar.skipButtons.backward
15927    );
15928  }
15929  buildCSSClass() {
15930    return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
15931  }
15932
15933  /**
15934   * On click, skips backward in the video by a configurable amount of seconds.
15935   * If the current time in the video is less than the configured 'skip backward' time,
15936   * skips to beginning of video or seekable range.
15937   *
15938   * Handle a click on a `SkipBackward` button
15939   *
15940   * @param {EventTarget~Event} event
15941   *        The `click` event that caused this function
15942   *        to be called
15943   */
15944  handleClick(event) {
15945    const currentVideoTime = this.player_.currentTime();
15946    const liveTracker = this.player_.liveTracker;
15947    const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
15948    let newTime;
15949    if (seekableStart && currentVideoTime - this.skipTime <= seekableStart) {
15950      newTime = seekableStart;
15951    } else if (currentVideoTime >= this.skipTime) {
15952      newTime = currentVideoTime - this.skipTime;
15953    } else {
15954      newTime = 0;
15955    }
15956    this.player_.currentTime(newTime);
15957  }
15958
15959  /**
15960   * Update control text on languagechange
15961   */
15962  handleLanguagechange() {
15963    this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
15964  }
15965}
15966SkipBackward.prototype.controlText_ = 'Skip Backward';
15967Component$1.registerComponent('SkipBackward', SkipBackward);
15968
15969/**
15970 * @file menu.js
15971 */
15972
15973/** @import Player from '../player' */
15974
15975/**
15976 * The Menu component is used to build popup menus, including subtitle and
15977 * captions selection menus.
15978 *
15979 * @extends Component
15980 */
15981class Menu extends Component$1 {
15982  /**
15983   * Create an instance of this class.
15984   *
15985   * @param {Player} player
15986   *        the player that this component should attach to
15987   *
15988   * @param {Object} [options]
15989   *        Object of option names and values
15990   *
15991   */
15992  constructor(player, options) {
15993    super(player, options);
15994    if (options) {
15995      this.menuButton_ = options.menuButton;
15996    }
15997    this.focusedChild_ = -1;
15998    this.on('keydown', (e) => this.handleKeyDown(e));
15999
16000    // All the menu item instances share the same blur handler provided by the menu container.
16001    this.boundHandleBlur_ = (e) => this.handleBlur(e);
16002    this.boundHandleTapClick_ = (e) => this.handleTapClick(e);
16003  }
16004
16005  /**
16006   * Add event listeners to the {@link MenuItem}.
16007   *
16008   * @param {Object} component
16009   *        The instance of the `MenuItem` to add listeners to.
16010   *
16011   */
16012  addEventListenerForItem(component) {
16013    if (!(component instanceof Component$1)) {
16014      return;
16015    }
16016    this.on(component, 'blur', this.boundHandleBlur_);
16017    this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
16018  }
16019
16020  /**
16021   * Remove event listeners from the {@link MenuItem}.
16022   *
16023   * @param {Object} component
16024   *        The instance of the `MenuItem` to remove listeners.
16025   *
16026   */
16027  removeEventListenerForItem(component) {
16028    if (!(component instanceof Component$1)) {
16029      return;
16030    }
16031    this.off(component, 'blur', this.boundHandleBlur_);
16032    this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
16033  }
16034
16035  /**
16036   * This method will be called indirectly when the component has been added
16037   * before the component adds to the new menu instance by `addItem`.
16038   * In this case, the original menu instance will remove the component
16039   * by calling `removeChild`.
16040   *
16041   * @param {Object} component
16042   *        The instance of the `MenuItem`
16043   */
16044  removeChild(component) {
16045    if (typeof component === 'string') {
16046      component = this.getChild(component);
16047    }
16048    this.removeEventListenerForItem(component);
16049    super.removeChild(component);
16050  }
16051
16052  /**
16053   * Add a {@link MenuItem} to the menu.
16054   *
16055   * @param {Object|string} component
16056   *        The name or instance of the `MenuItem` to add.
16057   *
16058   */
16059  addItem(component) {
16060    const childComponent = this.addChild(component);
16061    if (childComponent) {
16062      this.addEventListenerForItem(childComponent);
16063    }
16064  }
16065
16066  /**
16067   * Create the `Menu`s DOM element.
16068   *
16069   * @return {Element}
16070   *         the element that was created
16071   */
16072  createEl() {
16073    const contentElType = this.options_.contentElType || 'ul';
16074    this.contentEl_ = createEl(contentElType, {
16075      className: 'vjs-menu-content',
16076    });
16077    this.contentEl_.setAttribute('role', 'menu');
16078    const el = super.createEl('div', {
16079      append: this.contentEl_,
16080      className: 'vjs-menu',
16081    });
16082    el.appendChild(this.contentEl_);
16083
16084    // Prevent clicks from bubbling up. Needed for Menu Buttons,
16085    // where a click on the parent is significant
16086    on(el, 'click', function (event) {
16087      event.preventDefault();
16088      event.stopImmediatePropagation();
16089    });
16090    return el;
16091  }
16092  dispose() {
16093    this.contentEl_ = null;
16094    this.boundHandleBlur_ = null;
16095    this.boundHandleTapClick_ = null;
16096    super.dispose();
16097  }
16098
16099  /**
16100   * Called when a `MenuItem` loses focus.
16101   *
16102   * @param {Event} event
16103   *        The `blur` event that caused this function to be called.
16104   *
16105   * @listens blur
16106   */
16107  handleBlur(event) {
16108    const relatedTarget = event.relatedTarget || document__default['default'].activeElement;
16109
16110    // Close menu popup when a user clicks outside the menu
16111    if (
16112      !this.children().some((element) => {
16113        return element.el() === relatedTarget;
16114      })
16115    ) {
16116      const btn = this.menuButton_;
16117      if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
16118        btn.unpressButton();
16119      }
16120    }
16121  }
16122
16123  /**
16124   * Called when a `MenuItem` gets clicked or tapped.
16125   *
16126   * @param {Event} event
16127   *        The `click` or `tap` event that caused this function to be called.
16128   *
16129   * @listens click,tap
16130   */
16131  handleTapClick(event) {
16132    // Unpress the associated MenuButton, and move focus back to it
16133    if (this.menuButton_) {
16134      this.menuButton_.unpressButton();
16135      const childComponents = this.children();
16136      if (!Array.isArray(childComponents)) {
16137        return;
16138      }
16139      const foundComponent = childComponents.filter(
16140        (component) => component.el() === event.target
16141      )[0];
16142      if (!foundComponent) {
16143        return;
16144      }
16145
16146      // don't focus menu button if item is a caption settings item
16147      // because focus will move elsewhere
16148      if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
16149        this.menuButton_.focus();
16150      }
16151    }
16152  }
16153
16154  /**
16155   * Handle a `keydown` event on this menu. This listener is added in the constructor.
16156   *
16157   * @param {KeyboardEvent} event
16158   *        A `keydown` event that happened on the menu.
16159   *
16160   * @listens keydown
16161   */
16162  handleKeyDown(event) {
16163    // Left and Down Arrows
16164    if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
16165      event.preventDefault();
16166      event.stopPropagation();
16167      this.stepForward();
16168
16169      // Up and Right Arrows
16170    } else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
16171      event.preventDefault();
16172      event.stopPropagation();
16173      this.stepBack();
16174    }
16175  }
16176
16177  /**
16178   * Move to next (lower) menu item for keyboard users.
16179   */
16180  stepForward() {
16181    let stepChild = 0;
16182    if (this.focusedChild_ !== undefined) {
16183      stepChild = this.focusedChild_ + 1;
16184    }
16185    this.focus(stepChild);
16186  }
16187
16188  /**
16189   * Move to previous (higher) menu item for keyboard users.
16190   */
16191  stepBack() {
16192    let stepChild = 0;
16193    if (this.focusedChild_ !== undefined) {
16194      stepChild = this.focusedChild_ - 1;
16195    }
16196    this.focus(stepChild);
16197  }
16198
16199  /**
16200   * Set focus on a {@link MenuItem} in the `Menu`.
16201   *
16202   * @param {Object|string} [item=0]
16203   *        Index of child item set focus on.
16204   */
16205  focus(item = 0) {
16206    const children = this.children().slice();
16207    const haveTitle = children.length && children[0].hasClass('vjs-menu-title');
16208    if (haveTitle) {
16209      children.shift();
16210    }
16211    if (children.length > 0) {
16212      if (item < 0) {
16213        item = 0;
16214      } else if (item >= children.length) {
16215        item = children.length - 1;
16216      }
16217      this.focusedChild_ = item;
16218      children[item].el_.focus();
16219    }
16220  }
16221}
16222Component$1.registerComponent('Menu', Menu);
16223
16224/**
16225 * @file menu-button.js
16226 */
16227
16228/** @import Player from '../player' */
16229
16230/**
16231 * A `MenuButton` class for any popup {@link Menu}.
16232 *
16233 * @extends Component
16234 */
16235class MenuButton extends Component$1 {
16236  /**
16237   * Creates an instance of this class.
16238   *
16239   * @param {Player} player
16240   *        The `Player` that this class should be attached to.
16241   *
16242   * @param {Object} [options={}]
16243   *        The key/value store of player options.
16244   */
16245  constructor(player, options = {}) {
16246    super(player, options);
16247    this.menuButton_ = new Button(player, options);
16248    this.menuButton_.controlText(this.controlText_);
16249    this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
16250
16251    // Add buildCSSClass values to the button, not the wrapper
16252    const buttonClass = Button.prototype.buildCSSClass();
16253    this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
16254    this.menuButton_.removeClass('vjs-control');
16255    this.addChild(this.menuButton_);
16256    this.update();
16257    this.enabled_ = true;
16258    const handleClick = (e) => this.handleClick(e);
16259    this.handleMenuKeyUp_ = (e) => this.handleMenuKeyUp(e);
16260    this.on(this.menuButton_, 'tap', handleClick);
16261    this.on(this.menuButton_, 'click', handleClick);
16262    this.on(this.menuButton_, 'keydown', (e) => this.handleKeyDown(e));
16263    this.on(this.menuButton_, 'mouseenter', () => {
16264      this.addClass('vjs-hover');
16265      this.menu.show();
16266      on(document__default['default'], 'keyup', this.handleMenuKeyUp_);
16267    });
16268    this.on('mouseleave', (e) => this.handleMouseLeave(e));
16269    this.on('keydown', (e) => this.handleSubmenuKeyDown(e));
16270  }
16271
16272  /**
16273   * Update the menu based on the current state of its items.
16274   */
16275  update() {
16276    const menu = this.createMenu();
16277    if (this.menu) {
16278      this.menu.dispose();
16279      this.removeChild(this.menu);
16280    }
16281    this.menu = menu;
16282    this.addChild(menu);
16283
16284    /**
16285     * Track the state of the menu button
16286     *
16287     * @type {Boolean}
16288     * @private
16289     */
16290    this.buttonPressed_ = false;
16291    this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16292    if (this.items && this.items.length <= this.hideThreshold_) {
16293      this.hide();
16294      this.menu.contentEl_.removeAttribute('role');
16295    } else {
16296      this.show();
16297      this.menu.contentEl_.setAttribute('role', 'menu');
16298    }
16299  }
16300
16301  /**
16302   * Create the menu and add all items to it.
16303   *
16304   * @return {Menu}
16305   *         The constructed menu
16306   */
16307  createMenu() {
16308    const menu = new Menu(this.player_, {
16309      menuButton: this,
16310    });
16311
16312    /**
16313     * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16314     * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16315     * it here because every time we run `createMenu` we need to reset the value.
16316     *
16317     * @protected
16318     * @type {Number}
16319     */
16320    this.hideThreshold_ = 0;
16321
16322    // Add a title list item to the top
16323    if (this.options_.title) {
16324      const titleEl = createEl('li', {
16325        className: 'vjs-menu-title',
16326        textContent: toTitleCase$1(this.options_.title),
16327        tabIndex: -1,
16328      });
16329      const titleComponent = new Component$1(this.player_, {
16330        el: titleEl,
16331      });
16332      menu.addItem(titleComponent);
16333    }
16334    this.items = this.createItems();
16335    if (this.items) {
16336      // Add menu items to the menu
16337      for (let i = 0; i < this.items.length; i++) {
16338        menu.addItem(this.items[i]);
16339      }
16340    }
16341    return menu;
16342  }
16343
16344  /**
16345   * Create the list of menu items. Specific to each subclass.
16346   *
16347   * @abstract
16348   */
16349  createItems() {}
16350
16351  /**
16352   * Create the `MenuButtons`s DOM element.
16353   *
16354   * @return {Element}
16355   *         The element that gets created.
16356   */
16357  createEl() {
16358    return super.createEl(
16359      'div',
16360      {
16361        className: this.buildWrapperCSSClass(),
16362      },
16363      {}
16364    );
16365  }
16366
16367  /**
16368   * Overwrites the `setIcon` method from `Component`.
16369   * In this case, we want the icon to be appended to the menuButton.
16370   *
16371   * @param {string} name
16372   *         The icon name to be added.
16373   */
16374  setIcon(name) {
16375    super.setIcon(name, this.menuButton_.el_);
16376  }
16377
16378  /**
16379   * Allow sub components to stack CSS class names for the wrapper element
16380   *
16381   * @return {string}
16382   *         The constructed wrapper DOM `className`
16383   */
16384  buildWrapperCSSClass() {
16385    let menuButtonClass = 'vjs-menu-button';
16386
16387    // If the inline option is passed, we want to use different styles altogether.
16388    if (this.options_.inline === true) {
16389      menuButtonClass += '-inline';
16390    } else {
16391      menuButtonClass += '-popup';
16392    }
16393
16394    // TODO: Fix the CSS so that this isn't necessary
16395    const buttonClass = Button.prototype.buildCSSClass();
16396    return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
16397  }
16398
16399  /**
16400   * Builds the default DOM `className`.
16401   *
16402   * @return {string}
16403   *         The DOM `className` for this object.
16404   */
16405  buildCSSClass() {
16406    let menuButtonClass = 'vjs-menu-button';
16407
16408    // If the inline option is passed, we want to use different styles altogether.
16409    if (this.options_.inline === true) {
16410      menuButtonClass += '-inline';
16411    } else {
16412      menuButtonClass += '-popup';
16413    }
16414    return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
16415  }
16416
16417  /**
16418   * Get or set the localized control text that will be used for accessibility.
16419   *
16420   * > NOTE: This will come from the internal `menuButton_` element.
16421   *
16422   * @param {string} [text]
16423   *        Control text for element.
16424   *
16425   * @param {Element} [el=this.menuButton_.el()]
16426   *        Element to set the title on.
16427   *
16428   * @return {string}
16429   *         - The control text when getting
16430   */
16431  controlText(text, el = this.menuButton_.el()) {
16432    return this.menuButton_.controlText(text, el);
16433  }
16434
16435  /**
16436   * Dispose of the `menu-button` and all child components.
16437   */
16438  dispose() {
16439    this.handleMouseLeave();
16440    super.dispose();
16441  }
16442
16443  /**
16444   * Handle a click on a `MenuButton`.
16445   * See {@link ClickableComponent#handleClick} for instances where this is called.
16446   *
16447   * @param {Event} event
16448   *        The `keydown`, `tap`, or `click` event that caused this function to be
16449   *        called.
16450   *
16451   * @listens tap
16452   * @listens click
16453   */
16454  handleClick(event) {
16455    if (this.buttonPressed_) {
16456      this.unpressButton();
16457    } else {
16458      this.pressButton();
16459    }
16460  }
16461
16462  /**
16463   * Handle `mouseleave` for `MenuButton`.
16464   *
16465   * @param {Event} event
16466   *        The `mouseleave` event that caused this function to be called.
16467   *
16468   * @listens mouseleave
16469   */
16470  handleMouseLeave(event) {
16471    this.removeClass('vjs-hover');
16472    off(document__default['default'], 'keyup', this.handleMenuKeyUp_);
16473  }
16474
16475  /**
16476   * Set the focus to the actual button, not to this element
16477   */
16478  focus() {
16479    this.menuButton_.focus();
16480  }
16481
16482  /**
16483   * Remove the focus from the actual button, not this element
16484   */
16485  blur() {
16486    this.menuButton_.blur();
16487  }
16488
16489  /**
16490   * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
16491   * {@link ClickableComponent#handleKeyDown} for instances where this is called.
16492   *
16493   * @param {Event} event
16494   *        The `keydown` event that caused this function to be called.
16495   *
16496   * @listens keydown
16497   */
16498  handleKeyDown(event) {
16499    // Escape or Tab unpress the 'button'
16500    if (event.key === 'Escape' || event.key === 'Tab') {
16501      if (this.buttonPressed_) {
16502        this.unpressButton();
16503      }
16504
16505      // Don't preventDefault for Tab key - we still want to lose focus
16506      if (!event.key === 'Tab') {
16507        event.preventDefault();
16508        // Set focus back to the menu button's button
16509        this.menuButton_.focus();
16510      }
16511      // Up Arrow or Down Arrow also 'press' the button to open the menu
16512    } else if (
16513      event.key === 'Up' ||
16514      (event.key === 'Down' &&
16515        !(
16516          this.player_.options_.playerOptions.spatialNavigation &&
16517          this.player_.options_.playerOptions.spatialNavigation.enabled
16518        ))
16519    ) {
16520      if (!this.buttonPressed_) {
16521        event.preventDefault();
16522        this.pressButton();
16523      }
16524    }
16525  }
16526
16527  /**
16528   * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
16529   * the constructor.
16530   *
16531   * @param {Event} event
16532   *        Key press event
16533   *
16534   * @listens keyup
16535   */
16536  handleMenuKeyUp(event) {
16537    // Escape hides popup menu
16538    if (event.key === 'Escape' || event.key === 'Tab') {
16539      this.removeClass('vjs-hover');
16540    }
16541  }
16542
16543  /**
16544   * This method name now delegates to `handleSubmenuKeyDown`. This means
16545   * anyone calling `handleSubmenuKeyPress` will not see their method calls
16546   * stop working.
16547   *
16548   * @param {Event} event
16549   *        The event that caused this function to be called.
16550   */
16551  handleSubmenuKeyPress(event) {
16552    this.handleSubmenuKeyDown(event);
16553  }
16554
16555  /**
16556   * Handle a `keydown` event on a sub-menu. The listener for this is added in
16557   * the constructor.
16558   *
16559   * @param {Event} event
16560   *        Key press event
16561   *
16562   * @listens keydown
16563   */
16564  handleSubmenuKeyDown(event) {
16565    // Escape or Tab unpress the 'button'
16566    if (event.key === 'Escape' || event.key === 'Tab') {
16567      if (this.buttonPressed_) {
16568        this.unpressButton();
16569      }
16570      // Don't preventDefault for Tab key - we still want to lose focus
16571      if (!event.key === 'Tab') {
16572        event.preventDefault();
16573        // Set focus back to the menu button's button
16574        this.menuButton_.focus();
16575      }
16576    }
16577  }
16578
16579  /**
16580   * Put the current `MenuButton` into a pressed state.
16581   */
16582  pressButton() {
16583    if (this.enabled_) {
16584      this.buttonPressed_ = true;
16585      this.menu.show();
16586      this.menu.lockShowing();
16587      this.menuButton_.el_.setAttribute('aria-expanded', 'true');
16588
16589      // set the focus into the submenu, except on iOS where it is resulting in
16590      // undesired scrolling behavior when the player is in an iframe
16591      if (IS_IOS && isInFrame()) {
16592        // Return early so that the menu isn't focused
16593        return;
16594      }
16595      this.menu.focus();
16596    }
16597  }
16598
16599  /**
16600   * Take the current `MenuButton` out of a pressed state.
16601   */
16602  unpressButton() {
16603    if (this.enabled_) {
16604      this.buttonPressed_ = false;
16605      this.menu.unlockShowing();
16606      this.menu.hide();
16607      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16608    }
16609  }
16610
16611  /**
16612   * Disable the `MenuButton`. Don't allow it to be clicked.
16613   */
16614  disable() {
16615    this.unpressButton();
16616    this.enabled_ = false;
vendor: 4,450 bytes, lines 16617-16780
16617    this.addClass('vjs-disabled');
16618    this.menuButton_.disable();
16619  }
16620
16621  /**
16622   * Enable the `MenuButton`. Allow it to be clicked.
16623   */
16624  enable() {
16625    this.enabled_ = true;
16626    this.removeClass('vjs-disabled');
16627    this.menuButton_.enable();
16628  }
16629}
16630Component$1.registerComponent('MenuButton', MenuButton);
16631
16632/**
16633 * @file track-button.js
16634 */
16635
16636/** @import Player from './player' */
16637
16638/**
16639 * The base class for buttons that toggle specific  track types (e.g. subtitles).
16640 *
16641 * @extends MenuButton
16642 */
16643class TrackButton extends MenuButton {
16644  /**
16645   * Creates an instance of this class.
16646   *
16647   * @param {Player} player
16648   *        The `Player` that this class should be attached to.
16649   *
16650   * @param {Object} [options]
16651   *        The key/value store of player options.
16652   */
16653  constructor(player, options) {
16654    const tracks = options.tracks;
16655    super(player, options);
16656    if (this.items.length <= 1) {
16657      this.hide();
16658    }
16659    if (!tracks) {
16660      return;
16661    }
16662    const updateHandler = bind_(this, this.update);
16663    tracks.addEventListener('removetrack', updateHandler);
16664    tracks.addEventListener('addtrack', updateHandler);
16665    tracks.addEventListener('labelchange', updateHandler);
16666    this.player_.on('ready', updateHandler);
16667    this.player_.on('dispose', function () {
16668      tracks.removeEventListener('removetrack', updateHandler);
16669      tracks.removeEventListener('addtrack', updateHandler);
16670      tracks.removeEventListener('labelchange', updateHandler);
16671    });
16672  }
16673}
16674Component$1.registerComponent('TrackButton', TrackButton);
16675
16676/**
16677 * @file menu-item.js
16678 */
16679
16680/** @import Player from '../player' */
16681
16682/**
16683 * The component for a menu item. `<li>`
16684 *
16685 * @extends ClickableComponent
16686 */
16687class MenuItem extends ClickableComponent {
16688  /**
16689   * Creates an instance of the this class.
16690   *
16691   * @param {Player} player
16692   *        The `Player` that this class should be attached to.
16693   *
16694   * @param {Object} [options={}]
16695   *        The key/value store of player options.
16696   *
16697   */
16698  constructor(player, options) {
16699    super(player, options);
16700    this.selectable = options.selectable;
16701    this.isSelected_ = options.selected || false;
16702    this.multiSelectable = options.multiSelectable;
16703    this.selected(this.isSelected_);
16704    if (this.selectable) {
16705      if (this.multiSelectable) {
16706        this.el_.setAttribute('role', 'menuitemcheckbox');
16707      } else {
16708        this.el_.setAttribute('role', 'menuitemradio');
16709      }
16710    } else {
16711      this.el_.setAttribute('role', 'menuitem');
16712    }
16713  }
16714
16715  /**
16716   * Create the `MenuItem's DOM element
16717   *
16718   * @param {string} [type=li]
16719   *        Element's node type, not actually used, always set to `li`.
16720   *
16721   * @param {Object} [props={}]
16722   *        An object of properties that should be set on the element
16723   *
16724   * @param {Object} [attrs={}]
16725   *        An object of attributes that should be set on the element
16726   *
16727   * @return {Element}
16728   *         The element that gets created.
16729   */
16730  createEl(type, props, attrs) {
16731    // The control is textual, not just an icon
16732    this.nonIconControl = true;
16733    const el = super.createEl(
16734      'li',
16735      Object.assign(
16736        {
16737          className: 'vjs-menu-item',
16738          tabIndex: -1,
16739        },
16740        props
16741      ),
16742      attrs
16743    );
16744
16745    // swap icon with menu item text.
16746    const menuItemEl = createEl('span', {
16747      className: 'vjs-menu-item-text',
16748      textContent: this.localize(this.options_.label),
16749    });
16750
16751    // If using SVG icons, the element with vjs-icon-placeholder will be added separately.
16752    if (this.player_.options_.experimentalSvgIcons) {
16753      el.appendChild(menuItemEl);
16754    } else {
16755      el.replaceChild(menuItemEl, el.querySelector('.vjs-icon-placeholder'));
16756    }
16757    return el;
16758  }
16759
16760  /**
16761   * Ignore keys which are used by the menu, but pass any other ones up. See
16762   * {@link ClickableComponent#handleKeyDown} for instances where this is called.
16763   *
16764   * @param {KeyboardEvent} event
16765   *        The `keydown` event that caused this function to be called.
16766   *
16767   * @listens keydown
16768   */
16769  handleKeyDown(event) {
16770    if (!['Tab', 'Escape', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown'].includes(event.key)) {
16771      // Pass keydown handling up for unused keys
16772      super.handleKeyDown(event);
16773    }
16774  }
16775
16776  /**
16777   * Any click on a `MenuItem` puts it into the selected state.
16778   * See {@link ClickableComponent#handleClick} for instances where this is called.
16779   *
16780   * @param {Event} event
vendor: 7,319 bytes, lines 16781-17012
16781   *        The `keydown`, `tap`, or `click` event that caused this function to be
16782   *        called.
16783   *
16784   * @listens tap
16785   * @listens click
16786   */
16787  handleClick(event) {
16788    this.selected(true);
16789  }
16790
16791  /**
16792   * Set the state for this menu item as selected or not.
16793   *
16794   * @param {boolean} selected
16795   *        if the menu item is selected or not
16796   */
16797  selected(selected) {
16798    if (this.selectable) {
16799      if (selected) {
16800        this.addClass('vjs-selected');
16801        this.el_.setAttribute('aria-checked', 'true');
16802        // aria-checked isn't fully supported by browsers/screen readers,
16803        // so indicate selected state to screen reader in the control text.
16804        this.controlText(', selected');
16805        this.isSelected_ = true;
16806      } else {
16807        this.removeClass('vjs-selected');
16808        this.el_.setAttribute('aria-checked', 'false');
16809        // Indicate un-selected state to screen reader
16810        this.controlText('');
16811        this.isSelected_ = false;
16812      }
16813    }
16814  }
16815}
16816Component$1.registerComponent('MenuItem', MenuItem);
16817
16818/**
16819 * @file text-track-menu-item.js
16820 */
16821
16822/** @import Player from '../../player' */
16823
16824/**
16825 * The specific menu item type for selecting a language within a text track kind
16826 *
16827 * @extends MenuItem
16828 */
16829class TextTrackMenuItem extends MenuItem {
16830  /**
16831   * Creates an instance of this class.
16832   *
16833   * @param {Player} player
16834   *        The `Player` that this class should be attached to.
16835   *
16836   * @param {Object} [options]
16837   *        The key/value store of player options.
16838   */
16839  constructor(player, options) {
16840    const track = options.track;
16841    const tracks = player.textTracks();
16842
16843    // Modify options for parent MenuItem class's init.
16844    options.label = track.label || track.language || 'Unknown';
16845    options.selected = track.mode === 'showing';
16846    super(player, options);
16847    this.track = track;
16848    // Determine the relevant kind(s) of tracks for this component and filter
16849    // out empty kinds.
16850    this.kinds = (options.kinds || [options.kind || this.track.kind]).filter(Boolean);
16851    const changeHandler = (...args) => {
16852      this.handleTracksChange.apply(this, args);
16853    };
16854    const selectedLanguageChangeHandler = (...args) => {
16855      this.handleSelectedLanguageChange.apply(this, args);
16856    };
16857    player.on(['loadstart', 'texttrackchange'], changeHandler);
16858    tracks.addEventListener('change', changeHandler);
16859    tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16860    this.on('dispose', function () {
16861      player.off(['loadstart', 'texttrackchange'], changeHandler);
16862      tracks.removeEventListener('change', changeHandler);
16863      tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16864    });
16865
16866    // iOS7 doesn't dispatch change events to TextTrackLists when an
16867    // associated track's mode changes. Without something like
16868    // Object.observe() (also not present on iOS7), it's not
16869    // possible to detect changes to the mode attribute and polyfill
16870    // the change event. As a poor substitute, we manually dispatch
16871    // change events whenever the controls modify the mode.
16872    if (tracks.onchange === undefined) {
16873      let event;
16874      this.on(['tap', 'click'], function () {
16875        if (typeof window__default['default'].Event !== 'object') {
16876          // Android 2.3 throws an Illegal Constructor error for window.Event
16877          try {
16878            event = new window__default['default'].Event('change');
16879          } catch (err) {
16880            // continue regardless of error
16881          }
16882        }
16883        if (!event) {
16884          event = document__default['default'].createEvent('Event');
16885          event.initEvent('change', true, true);
16886        }
16887        tracks.dispatchEvent(event);
16888      });
16889    }
16890
16891    // set the default state based on current tracks
16892    this.handleTracksChange();
16893  }
16894
16895  /**
16896   * This gets called when an `TextTrackMenuItem` is "clicked". See
16897   * {@link ClickableComponent} for more detailed information on what a click can be.
16898   *
16899   * @param {Event} event
16900   *        The `keydown`, `tap`, or `click` event that caused this function to be
16901   *        called.
16902   *
16903   * @listens tap
16904   * @listens click
16905   */
16906  handleClick(event) {
16907    const referenceTrack = this.track;
16908    const tracks = this.player_.textTracks();
16909    super.handleClick(event);
16910    if (!tracks) {
16911      return;
16912    }
16913    for (let i = 0; i < tracks.length; i++) {
16914      const track = tracks[i];
16915
16916      // If the track from the text tracks list is not of the right kind,
16917      // skip it. We do not want to affect tracks of incompatible kind(s).
16918      if (this.kinds.indexOf(track.kind) === -1) {
16919        continue;
16920      }
16921
16922      // If this text track is the component's track and it is not showing,
16923      // set it to showing.
16924      if (track === referenceTrack) {
16925        if (track.mode !== 'showing') {
16926          track.mode = 'showing';
16927        }
16928
16929        // If this text track is not the component's track and it is not
16930        // disabled, set it to disabled.
16931      } else if (track.mode !== 'disabled') {
16932        track.mode = 'disabled';
16933      }
16934    }
16935  }
16936
16937  /**
16938   * Handle text track list change
16939   *
16940   * @param {Event} event
16941   *        The `change` event that caused this function to be called.
16942   *
16943   * @listens TextTrackList#change
16944   */
16945  handleTracksChange(event) {
16946    const shouldBeSelected = this.track.mode === 'showing';
16947
16948    // Prevent redundant selected() calls because they may cause
16949    // screen readers to read the appended control text unnecessarily
16950    if (shouldBeSelected !== this.isSelected_) {
16951      this.selected(shouldBeSelected);
16952    }
16953  }
16954  handleSelectedLanguageChange(event) {
16955    if (this.track.mode === 'showing') {
16956      const selectedLanguage = this.player_.cache_.selectedLanguage;
16957
16958      // Don't replace the kind of track across the same language
16959      if (
16960        selectedLanguage &&
16961        selectedLanguage.enabled &&
16962        selectedLanguage.language === this.track.language &&
16963        selectedLanguage.kind !== this.track.kind
16964      ) {
16965        return;
16966      }
16967      this.player_.cache_.selectedLanguage = {
16968        enabled: true,
16969        language: this.track.language,
16970        kind: this.track.kind,
16971      };
16972    }
16973  }
16974  dispose() {
16975    // remove reference to track object on dispose
16976    this.track = null;
16977    super.dispose();
16978  }
16979}
16980Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
16981
16982/**
16983 * @file off-text-track-menu-item.js
16984 */
16985
16986/** @import Player from '../../player' */
16987
16988/**
16989 * A special menu item for turning off a specific type of text track
16990 *
16991 * @extends TextTrackMenuItem
16992 */
16993class OffTextTrackMenuItem extends TextTrackMenuItem {
16994  /**
16995   * Creates an instance of this class.
16996   *
16997   * @param {Player} player
16998   *        The `Player` that this class should be attached to.
16999   *
17000   * @param {Object} [options]
17001   *        The key/value store of player options.
17002   */
17003  constructor(player, options) {
17004    // Create pseudo track info
17005    // Requires options['kind']
17006    options.track = {
17007      player,
17008      // it is no longer necessary to store `kind` or `kinds` on the track itself
17009      // since they are now stored in the `kinds` property of all instances of
17010      // TextTrackMenuItem, but this will remain for backwards compatibility
17011      kind: options.kind,
17012      kinds: options.kinds,
vendor: 15,716 bytes, lines 17013-17607
17013      default: false,
17014      mode: 'disabled',
17015    };
17016    if (!options.kinds) {
17017      options.kinds = [options.kind];
17018    }
17019    if (options.label) {
17020      options.track.label = options.label;
17021    } else {
17022      options.track.label = options.kinds.join(' and ') + ' off';
17023    }
17024
17025    // MenuItem is selectable
17026    options.selectable = true;
17027    // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17028    options.multiSelectable = false;
17029    super(player, options);
17030  }
17031
17032  /**
17033   * Handle text track change
17034   *
17035   * @param {Event} event
17036   *        The event that caused this function to run
17037   */
17038  handleTracksChange(event) {
17039    const tracks = this.player().textTracks();
17040    let shouldBeSelected = true;
17041    for (let i = 0, l = tracks.length; i < l; i++) {
17042      const track = tracks[i];
17043      if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
17044        shouldBeSelected = false;
17045        break;
17046      }
17047    }
17048
17049    // Prevent redundant selected() calls because they may cause
17050    // screen readers to read the appended control text unnecessarily
17051    if (shouldBeSelected !== this.isSelected_) {
17052      this.selected(shouldBeSelected);
17053    }
17054  }
17055  handleSelectedLanguageChange(event) {
17056    const tracks = this.player().textTracks();
17057    let allHidden = true;
17058    for (let i = 0, l = tracks.length; i < l; i++) {
17059      const track = tracks[i];
17060      if (
17061        ['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 &&
17062        track.mode === 'showing'
17063      ) {
17064        allHidden = false;
17065        break;
17066      }
17067    }
17068    if (allHidden) {
17069      this.player_.cache_.selectedLanguage = {
17070        enabled: false,
17071      };
17072    }
17073  }
17074
17075  /**
17076   * Update control text and label on languagechange
17077   */
17078  handleLanguagechange() {
17079    this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.label);
17080    super.handleLanguagechange();
17081  }
17082}
17083Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
17084
17085/**
17086 * @file text-track-button.js
17087 */
17088
17089/** @import Player from '../../player' */
17090
17091/**
17092 * The base class for buttons that toggle specific text track types (e.g. subtitles)
17093 *
17094 * @extends MenuButton
17095 */
17096class TextTrackButton extends TrackButton {
17097  /**
17098   * Creates an instance of this class.
17099   *
17100   * @param {Player} player
17101   *        The `Player` that this class should be attached to.
17102   *
17103   * @param {Object} [options={}]
17104   *        The key/value store of player options.
17105   */
17106  constructor(player, options = {}) {
17107    options.tracks = player.textTracks();
17108    super(player, options);
17109  }
17110
17111  /**
17112   * Create a menu item for each text track
17113   *
17114   * @param {TextTrackMenuItem[]} [items=[]]
17115   *        Existing array of items to use during creation
17116   *
17117   * @return {TextTrackMenuItem[]}
17118   *         Array of menu items that were created
17119   */
17120  createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
17121    // Label is an override for the [track] off label
17122    // USed to localise captions/subtitles
17123    let label;
17124    if (this.label_) {
17125      label = `${this.label_} off`;
17126    }
17127    // Add an OFF menu item to turn all tracks off
17128    items.push(
17129      new OffTextTrackMenuItem(this.player_, {
17130        kinds: this.kinds_,
17131        kind: this.kind_,
17132        label,
17133      })
17134    );
17135    this.hideThreshold_ += 1;
17136    const tracks = this.player_.textTracks();
17137    if (!Array.isArray(this.kinds_)) {
17138      this.kinds_ = [this.kind_];
17139    }
17140    for (let i = 0; i < tracks.length; i++) {
17141      const track = tracks[i];
17142
17143      // only add tracks that are of an appropriate kind and have a label
17144      if (this.kinds_.indexOf(track.kind) > -1) {
17145        const item = new TrackMenuItem(this.player_, {
17146          track,
17147          kinds: this.kinds_,
17148          kind: this.kind_,
17149          // MenuItem is selectable
17150          selectable: true,
17151          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17152          multiSelectable: false,
17153        });
17154        item.addClass(`vjs-${track.kind}-menu-item`);
17155        items.push(item);
17156      }
17157    }
17158    return items;
17159  }
17160}
17161Component$1.registerComponent('TextTrackButton', TextTrackButton);
17162
17163/**
17164 * @file chapters-track-menu-item.js
17165 */
17166
17167/** @import Player from '../../player' */
17168
17169/**
17170 * The chapter track menu item
17171 *
17172 * @extends MenuItem
17173 */
17174class ChaptersTrackMenuItem extends MenuItem {
17175  /**
17176   * Creates an instance of this class.
17177   *
17178   * @param {Player} player
17179   *        The `Player` that this class should be attached to.
17180   *
17181   * @param {Object} [options]
17182   *        The key/value store of player options.
17183   */
17184  constructor(player, options) {
17185    const track = options.track;
17186    const cue = options.cue;
17187    const currentTime = player.currentTime();
17188
17189    // Modify options for parent MenuItem class's init.
17190    options.selectable = true;
17191    options.multiSelectable = false;
17192    options.label = cue.text;
17193    options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17194    super(player, options);
17195    this.track = track;
17196    this.cue = cue;
17197  }
17198
17199  /**
17200   * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17201   * {@link ClickableComponent} for more detailed information on what a click can be.
17202   *
17203   * @param {Event} [event]
17204   *        The `keydown`, `tap`, or `click` event that caused this function to be
17205   *        called.
17206   *
17207   * @listens tap
17208   * @listens click
17209   */
17210  handleClick(event) {
17211    super.handleClick();
17212    this.player_.currentTime(this.cue.startTime);
17213  }
17214}
17215Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17216
17217/**
17218 * @file chapters-button.js
17219 */
17220
17221/** @import Player from '../../player' */
17222/** @import Menu from '../../menu/menu' */
17223/** @import TextTrack from '../../tracks/text-track' */
17224/** @import TextTrackMenuItem from '../text-track-controls/text-track-menu-item' */
17225
17226/**
17227 * The button component for toggling and selecting chapters
17228 * Chapters act much differently than other text tracks
17229 * Cues are navigation vs. other tracks of alternative languages
17230 *
17231 * @extends TextTrackButton
17232 */
17233class ChaptersButton extends TextTrackButton {
17234  /**
17235   * Creates an instance of this class.
17236   *
17237   * @param {Player} player
17238   *        The `Player` that this class should be attached to.
17239   *
17240   * @param {Object} [options]
17241   *        The key/value store of player options.
17242   *
17243   * @param {Function} [ready]
17244   *        The function to call when this function is ready.
17245   */
17246  constructor(player, options, ready) {
17247    super(player, options, ready);
17248    this.setIcon('chapters');
17249    this.selectCurrentItem_ = () => {
17250      this.items.forEach((item) => {
17251        item.selected(this.track_.activeCues[0] === item.cue);
17252      });
17253    };
17254  }
17255
17256  /**
17257   * Builds the default DOM `className`.
17258   *
17259   * @return {string}
17260   *         The DOM `className` for this object.
17261   */
17262  buildCSSClass() {
17263    return `vjs-chapters-button ${super.buildCSSClass()}`;
17264  }
17265  buildWrapperCSSClass() {
17266    return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
17267  }
17268
17269  /**
17270   * Update the menu based on the current state of its items.
17271   *
17272   * @param {Event} [event]
17273   *        An event that triggered this function to run.
17274   *
17275   * @listens TextTrackList#addtrack
17276   * @listens TextTrackList#removetrack
17277   * @listens TextTrackList#change
17278   */
17279  update(event) {
17280    if (event && event.track && event.track.kind !== 'chapters') {
17281      return;
17282    }
17283    const track = this.findChaptersTrack();
17284    if (track !== this.track_) {
17285      this.setTrack(track);
17286      super.update();
17287    } else if (!this.items || (track && track.cues && track.cues.length !== this.items.length)) {
17288      // Update the menu initially or if the number of cues has changed since set
17289      super.update();
17290    }
17291  }
17292
17293  /**
17294   * Set the currently selected track for the chapters button.
17295   *
17296   * @param {TextTrack} track
17297   *        The new track to select. Nothing will change if this is the currently selected
17298   *        track.
17299   */
17300  setTrack(track) {
17301    if (this.track_ === track) {
17302      return;
17303    }
17304    if (!this.updateHandler_) {
17305      this.updateHandler_ = this.update.bind(this);
17306    }
17307
17308    // here this.track_ refers to the old track instance
17309    if (this.track_) {
17310      const remoteTextTrackEl = this.player_
17311        .remoteTextTrackEls()
17312        .getTrackElementByTrack_(this.track_);
17313      if (remoteTextTrackEl) {
17314        remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17315      }
17316      this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
17317      this.track_ = null;
17318    }
17319    this.track_ = track;
17320
17321    // here this.track_ refers to the new track instance
17322    if (this.track_) {
17323      this.track_.mode = 'hidden';
17324      const remoteTextTrackEl = this.player_
17325        .remoteTextTrackEls()
17326        .getTrackElementByTrack_(this.track_);
17327      if (remoteTextTrackEl) {
17328        remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17329      }
17330      this.track_.addEventListener('cuechange', this.selectCurrentItem_);
17331    }
17332  }
17333
17334  /**
17335   * Find the track object that is currently in use by this ChaptersButton
17336   *
17337   * @return {TextTrack|undefined}
17338   *         The current track or undefined if none was found.
17339   */
17340  findChaptersTrack() {
17341    const tracks = this.player_.textTracks() || [];
17342    for (let i = tracks.length - 1; i >= 0; i--) {
17343      // We will always choose the last track as our chaptersTrack
17344      const track = tracks[i];
17345      if (track.kind === this.kind_) {
17346        return track;
17347      }
17348    }
17349  }
17350
17351  /**
17352   * Get the caption for the ChaptersButton based on the track label. This will also
17353   * use the current tracks localized kind as a fallback if a label does not exist.
17354   *
17355   * @return {string}
17356   *         The tracks current label or the localized track kind.
17357   */
17358  getMenuCaption() {
17359    if (this.track_ && this.track_.label) {
17360      return this.track_.label;
17361    }
17362    return this.localize(toTitleCase$1(this.kind_));
17363  }
17364
17365  /**
17366   * Create menu from chapter track
17367   *
17368   * @return {Menu}
17369   *         New menu for the chapter buttons
17370   */
17371  createMenu() {
17372    this.options_.title = this.getMenuCaption();
17373    return super.createMenu();
17374  }
17375
17376  /**
17377   * Create a menu item for each text track
17378   *
17379   * @return  {TextTrackMenuItem[]}
17380   *         Array of menu items
17381   */
17382  createItems() {
17383    const items = [];
17384    if (!this.track_) {
17385      return items;
17386    }
17387    const cues = this.track_.cues;
17388    if (!cues) {
17389      return items;
17390    }
17391    for (let i = 0, l = cues.length; i < l; i++) {
17392      const cue = cues[i];
17393      const mi = new ChaptersTrackMenuItem(this.player_, {
17394        track: this.track_,
17395        cue,
17396      });
17397      items.push(mi);
17398    }
17399    return items;
17400  }
17401}
17402
17403/**
17404 * `kind` of TextTrack to look for to associate it with this menu.
17405 *
17406 * @type {string}
17407 * @private
17408 */
17409ChaptersButton.prototype.kind_ = 'chapters';
17410
17411/**
17412 * The text that should display over the `ChaptersButton`s controls. Added for localization.
17413 *
17414 * @type {string}
17415 * @protected
17416 */
17417ChaptersButton.prototype.controlText_ = 'Chapters';
17418Component$1.registerComponent('ChaptersButton', ChaptersButton);
17419
17420/**
17421 * @file descriptions-button.js
17422 */
17423
17424/** @import Player from '../../player' */
17425
17426/**
17427 * The button component for toggling and selecting descriptions
17428 *
17429 * @extends TextTrackButton
17430 */
17431class DescriptionsButton extends TextTrackButton {
17432  /**
17433   * Creates an instance of this class.
17434   *
17435   * @param {Player} player
17436   *        The `Player` that this class should be attached to.
17437   *
17438   * @param {Object} [options]
17439   *        The key/value store of player options.
17440   *
17441   * @param {Function} [ready]
17442   *        The function to call when this component is ready.
17443   */
17444  constructor(player, options, ready) {
17445    super(player, options, ready);
17446    this.setIcon('audio-description');
17447    const tracks = player.textTracks();
17448    const changeHandler = bind_(this, this.handleTracksChange);
17449    tracks.addEventListener('change', changeHandler);
17450    this.on('dispose', function () {
17451      tracks.removeEventListener('change', changeHandler);
17452    });
17453  }
17454
17455  /**
17456   * Handle text track change
17457   *
17458   * @param {Event} event
17459   *        The event that caused this function to run
17460   *
17461   * @listens TextTrackList#change
17462   */
17463  handleTracksChange(event) {
17464    const tracks = this.player().textTracks();
17465    let disabled = false;
17466
17467    // Check whether a track of a different kind is showing
17468    for (let i = 0, l = tracks.length; i < l; i++) {
17469      const track = tracks[i];
17470      if (track.kind !== this.kind_ && track.mode === 'showing') {
17471        disabled = true;
17472        break;
17473      }
17474    }
17475
17476    // If another track is showing, disable this menu button
17477    if (disabled) {
17478      this.disable();
17479    } else {
17480      this.enable();
17481    }
17482  }
17483
17484  /**
17485   * Builds the default DOM `className`.
17486   *
17487   * @return {string}
17488   *         The DOM `className` for this object.
17489   */
17490  buildCSSClass() {
17491    return `vjs-descriptions-button ${super.buildCSSClass()}`;
17492  }
17493  buildWrapperCSSClass() {
17494    return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
17495  }
17496}
17497
17498/**
17499 * `kind` of TextTrack to look for to associate it with this menu.
17500 *
17501 * @type {string}
17502 * @private
17503 */
17504DescriptionsButton.prototype.kind_ = 'descriptions';
17505
17506/**
17507 * The text that should display over the `DescriptionsButton`s controls. Added for localization.
17508 *
17509 * @type {string}
17510 * @protected
17511 */
17512DescriptionsButton.prototype.controlText_ = 'Descriptions';
17513Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
17514
17515/**
17516 * @file subtitles-button.js
17517 */
17518
17519/** @import Player from '../../player' */
17520
17521/**
17522 * The button component for toggling and selecting subtitles
17523 *
17524 * @extends TextTrackButton
17525 */
17526class SubtitlesButton extends TextTrackButton {
17527  /**
17528   * Creates an instance of this class.
17529   *
17530   * @param {Player} player
17531   *        The `Player` that this class should be attached to.
17532   *
17533   * @param {Object} [options]
17534   *        The key/value store of player options.
17535   *
17536   * @param {Function} [ready]
17537   *        The function to call when this component is ready.
17538   */
17539  constructor(player, options, ready) {
17540    super(player, options, ready);
17541    this.setIcon('subtitles');
17542  }
17543
17544  /**
17545   * Builds the default DOM `className`.
17546   *
17547   * @return {string}
17548   *         The DOM `className` for this object.
17549   */
17550  buildCSSClass() {
17551    return `vjs-subtitles-button ${super.buildCSSClass()}`;
17552  }
17553  buildWrapperCSSClass() {
17554    return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
17555  }
17556}
17557
17558/**
17559 * `kind` of TextTrack to look for to associate it with this menu.
17560 *
17561 * @type {string}
17562 * @private
17563 */
17564SubtitlesButton.prototype.kind_ = 'subtitles';
17565
17566/**
17567 * The text that should display over the `SubtitlesButton`s controls. Added for localization.
17568 *
17569 * @type {string}
17570 * @protected
17571 */
17572SubtitlesButton.prototype.controlText_ = 'Subtitles';
17573Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
17574
17575/**
17576 * @file caption-settings-menu-item.js
17577 */
17578
17579/** @import Player from '../../player' */
17580
17581/**
17582 * The menu item for caption track settings menu
17583 *
17584 * @extends TextTrackMenuItem
17585 */
17586class CaptionSettingsMenuItem extends TextTrackMenuItem {
17587  /**
17588   * Creates an instance of this class.
17589   *
17590   * @param {Player} player
17591   *        The `Player` that this class should be attached to.
17592   *
17593   * @param {Object} [options]
17594   *        The key/value store of player options.
17595   */
17596  constructor(player, options) {
17597    options.track = {
17598      player,
17599      kind: options.kind,
17600      label: options.kind + ' settings',
17601      selectable: false,
17602      default: false,
17603      mode: 'disabled',
17604    };
17605
17606    // CaptionSettingsMenuItem has no concept of 'selected'
17607    options.selectable = false;
vendor: 8,251 bytes, lines 17608-17928
17608    options.name = 'CaptionSettingsMenuItem';
17609    super(player, options);
17610    this.addClass('vjs-texttrack-settings');
17611    this.controlText(', opens ' + options.kind + ' settings dialog');
17612  }
17613
17614  /**
17615   * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
17616   * {@link ClickableComponent} for more detailed information on what a click can be.
17617   *
17618   * @param {Event} [event]
17619   *        The `keydown`, `tap`, or `click` event that caused this function to be
17620   *        called.
17621   *
17622   * @listens tap
17623   * @listens click
17624   */
17625  handleClick(event) {
17626    this.player().getChild('textTrackSettings').open();
17627  }
17628
17629  /**
17630   * Update control text and label on languagechange
17631   */
17632  handleLanguagechange() {
17633    this.$('.vjs-menu-item-text').textContent = this.player_.localize(
17634      this.options_.kind + ' settings'
17635    );
17636    super.handleLanguagechange();
17637  }
17638}
17639Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
17640
17641/**
17642 * @file captions-button.js
17643 */
17644
17645/** @import Player from '../../player' */
17646
17647/**
17648 * The button component for toggling and selecting captions
17649 *
17650 * @extends TextTrackButton
17651 */
17652class CaptionsButton extends TextTrackButton {
17653  /**
17654   * Creates an instance of this class.
17655   *
17656   * @param {Player} player
17657   *        The `Player` that this class should be attached to.
17658   *
17659   * @param {Object} [options]
17660   *        The key/value store of player options.
17661   *
17662   * @param {Function} [ready]
17663   *        The function to call when this component is ready.
17664   */
17665  constructor(player, options, ready) {
17666    super(player, options, ready);
17667    this.setIcon('captions');
17668  }
17669
17670  /**
17671   * Builds the default DOM `className`.
17672   *
17673   * @return {string}
17674   *         The DOM `className` for this object.
17675   */
17676  buildCSSClass() {
17677    return `vjs-captions-button ${super.buildCSSClass()}`;
17678  }
17679  buildWrapperCSSClass() {
17680    return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
17681  }
17682
17683  /**
17684   * Create caption menu items
17685   *
17686   * @return {CaptionSettingsMenuItem[]}
17687   *         The array of current menu items.
17688   */
17689  createItems() {
17690    const items = [];
17691    if (
17692      !(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) &&
17693      this.player().getChild('textTrackSettings')
17694    ) {
17695      items.push(
17696        new CaptionSettingsMenuItem(this.player_, {
17697          kind: this.kind_,
17698        })
17699      );
17700      this.hideThreshold_ += 1;
17701    }
17702    return super.createItems(items);
17703  }
17704}
17705
17706/**
17707 * `kind` of TextTrack to look for to associate it with this menu.
17708 *
17709 * @type {string}
17710 * @private
17711 */
17712CaptionsButton.prototype.kind_ = 'captions';
17713
17714/**
17715 * The text that should display over the `CaptionsButton`s controls. Added for localization.
17716 *
17717 * @type {string}
17718 * @protected
17719 */
17720CaptionsButton.prototype.controlText_ = 'Captions';
17721Component$1.registerComponent('CaptionsButton', CaptionsButton);
17722
17723/**
17724 * @file subs-caps-menu-item.js
17725 */
17726
17727/**
17728 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
17729 * in the SubsCapsMenu.
17730 *
17731 * @extends TextTrackMenuItem
17732 */
17733class SubsCapsMenuItem extends TextTrackMenuItem {
17734  createEl(type, props, attrs) {
17735    const el = super.createEl(type, props, attrs);
17736    const parentSpan = el.querySelector('.vjs-menu-item-text');
17737    if (this.options_.track.kind === 'captions') {
17738      if (this.player_.options_.experimentalSvgIcons) {
17739        this.setIcon('captions', el);
17740      } else {
17741        parentSpan.appendChild(
17742          createEl(
17743            'span',
17744            {
17745              className: 'vjs-icon-placeholder',
17746            },
17747            {
17748              'aria-hidden': true,
17749            }
17750          )
17751        );
17752      }
17753      parentSpan.appendChild(
17754        createEl('span', {
17755          className: 'vjs-control-text',
17756          // space added as the text will visually flow with the
17757          // label
17758          textContent: ` ${this.localize('Captions')}`,
17759        })
17760      );
17761    }
17762    return el;
17763  }
17764}
17765Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
17766
17767/**
17768 * @file sub-caps-button.js
17769 */
17770
17771/** @import Player from '../../player' */
17772
17773/**
17774 * The button component for toggling and selecting captions and/or subtitles
17775 *
17776 * @extends TextTrackButton
17777 */
17778class SubsCapsButton extends TextTrackButton {
17779  /**
17780   * Creates an instance of this class.
17781   *
17782   * @param {Player} player
17783   *        The `Player` that this class should be attached to.
17784   *
17785   * @param {Object} [options]
17786   *        The key/value store of player options.
17787   *
17788   * @param {Function} [ready]
17789   *        The function to call when this component is ready.
17790   */
17791  constructor(player, options = {}) {
17792    super(player, options);
17793
17794    // Although North America uses "captions" in most cases for
17795    // "captions and subtitles" other locales use "subtitles"
17796    this.label_ = 'subtitles';
17797    this.setIcon('subtitles');
17798    if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
17799      this.label_ = 'captions';
17800      this.setIcon('captions');
17801    }
17802    this.menuButton_.controlText(toTitleCase$1(this.label_));
17803  }
17804
17805  /**
17806   * Builds the default DOM `className`.
17807   *
17808   * @return {string}
17809   *         The DOM `className` for this object.
17810   */
17811  buildCSSClass() {
17812    return `vjs-subs-caps-button ${super.buildCSSClass()}`;
17813  }
17814  buildWrapperCSSClass() {
17815    return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
17816  }
17817
17818  /**
17819   * Create caption/subtitles menu items
17820   *
17821   * @return {CaptionSettingsMenuItem[]}
17822   *         The array of current menu items.
17823   */
17824  createItems() {
17825    let items = [];
17826    if (
17827      !(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) &&
17828      this.player().getChild('textTrackSettings')
17829    ) {
17830      items.push(
17831        new CaptionSettingsMenuItem(this.player_, {
17832          kind: this.label_,
17833        })
17834      );
17835      this.hideThreshold_ += 1;
17836    }
17837    items = super.createItems(items, SubsCapsMenuItem);
17838    return items;
17839  }
17840}
17841
17842/**
17843 * `kind`s of TextTrack to look for to associate it with this menu.
17844 *
17845 * @type {array}
17846 * @private
17847 */
17848SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
17849
17850/**
17851 * The text that should display over the `SubsCapsButton`s controls.
17852 *
17853 *
17854 * @type {string}
17855 * @protected
17856 */
17857SubsCapsButton.prototype.controlText_ = 'Subtitles';
17858Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
17859
17860/**
17861 * @file audio-track-menu-item.js
17862 */
17863
17864/** @import Player from '../../player' */
17865
17866/**
17867 * An {@link AudioTrack} {@link MenuItem}
17868 *
17869 * @extends MenuItem
17870 */
17871class AudioTrackMenuItem extends MenuItem {
17872  /**
17873   * Creates an instance of this class.
17874   *
17875   * @param {Player} player
17876   *        The `Player` that this class should be attached to.
17877   *
17878   * @param {Object} [options]
17879   *        The key/value store of player options.
17880   */
17881  constructor(player, options) {
17882    const track = options.track;
17883    const tracks = player.audioTracks();
17884
17885    // Modify options for parent MenuItem class's init.
17886    options.label = track.label || track.language || 'Unknown';
17887    options.selected = track.enabled;
17888    super(player, options);
17889    this.track = track;
17890    this.addClass(`vjs-${track.kind}-menu-item`);
17891    const changeHandler = (...args) => {
17892      this.handleTracksChange.apply(this, args);
17893    };
17894    tracks.addEventListener('change', changeHandler);
17895    this.on('dispose', () => {
17896      tracks.removeEventListener('change', changeHandler);
17897    });
17898  }
17899  createEl(type, props, attrs) {
17900    const el = super.createEl(type, props, attrs);
17901    const parentSpan = el.querySelector('.vjs-menu-item-text');
17902    if (['main-desc', 'descriptions'].indexOf(this.options_.track.kind) >= 0) {
17903      parentSpan.appendChild(
17904        createEl(
17905          'span',
17906          {
17907            className: 'vjs-icon-placeholder',
17908          },
17909          {
17910            'aria-hidden': true,
17911          }
17912        )
17913      );
17914      parentSpan.appendChild(
17915        createEl('span', {
17916          className: 'vjs-control-text',
17917          textContent: ' ' + this.localize('Descriptions'),
17918        })
17919      );
17920    }
17921    return el;
17922  }
17923
17924  /**
17925   * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
17926   * for more detailed information on what a click can be.
17927   *
17928   * @param {Event} [event]
vendor: 4,223 bytes, lines 17929-18087
17929   *        The `keydown`, `tap`, or `click` event that caused this function to be
17930   *        called.
17931   *
17932   * @listens tap
17933   * @listens click
17934   */
17935  handleClick(event) {
17936    super.handleClick(event);
17937
17938    // the audio track list will automatically toggle other tracks
17939    // off for us.
17940    this.track.enabled = true;
17941
17942    // when native audio tracks are used, we want to make sure that other tracks are turned off
17943    if (this.player_.tech_.featuresNativeAudioTracks) {
17944      const tracks = this.player_.audioTracks();
17945      for (let i = 0; i < tracks.length; i++) {
17946        const track = tracks[i];
17947
17948        // skip the current track since we enabled it above
17949        if (track === this.track) {
17950          continue;
17951        }
17952        track.enabled = track === this.track;
17953      }
17954    }
17955  }
17956
17957  /**
17958   * Handle any {@link AudioTrack} change.
17959   *
17960   * @param {Event} [event]
17961   *        The {@link AudioTrackList#change} event that caused this to run.
17962   *
17963   * @listens AudioTrackList#change
17964   */
17965  handleTracksChange(event) {
17966    this.selected(this.track.enabled);
17967  }
17968}
17969Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
17970
17971/**
17972 * @file audio-track-button.js
17973 */
17974
17975/**
17976 * The base class for buttons that toggle specific {@link AudioTrack} types.
17977 *
17978 * @extends TrackButton
17979 */
17980class AudioTrackButton extends TrackButton {
17981  /**
17982   * Creates an instance of this class.
17983   *
17984   * @param {Player} player
17985   *        The `Player` that this class should be attached to.
17986   *
17987   * @param {Object} [options={}]
17988   *        The key/value store of player options.
17989   */
17990  constructor(player, options = {}) {
17991    options.tracks = player.audioTracks();
17992    super(player, options);
17993    this.setIcon('audio');
17994  }
17995
17996  /**
17997   * Builds the default DOM `className`.
17998   *
17999   * @return {string}
18000   *         The DOM `className` for this object.
18001   */
18002  buildCSSClass() {
18003    return `vjs-audio-button ${super.buildCSSClass()}`;
18004  }
18005  buildWrapperCSSClass() {
18006    return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
18007  }
18008
18009  /**
18010   * Create a menu item for each audio track
18011   *
18012   * @param {AudioTrackMenuItem[]} [items=[]]
18013   *        An array of existing menu items to use.
18014   *
18015   * @return {AudioTrackMenuItem[]}
18016   *         An array of menu items
18017   */
18018  createItems(items = []) {
18019    // if there's only one audio track, there no point in showing it
18020    this.hideThreshold_ = 1;
18021    const tracks = this.player_.audioTracks();
18022    for (let i = 0; i < tracks.length; i++) {
18023      const track = tracks[i];
18024      items.push(
18025        new AudioTrackMenuItem(this.player_, {
18026          track,
18027          // MenuItem is selectable
18028          selectable: true,
18029          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18030          multiSelectable: false,
18031        })
18032      );
18033    }
18034    return items;
18035  }
18036}
18037
18038/**
18039 * The text that should display over the `AudioTrackButton`s controls. Added for localization.
18040 *
18041 * @type {string}
18042 * @protected
18043 */
18044AudioTrackButton.prototype.controlText_ = 'Audio Track';
18045Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
18046
18047/**
18048 * @file playback-rate-menu-item.js
18049 */
18050
18051/** @import Player from '../../player' */
18052
18053/**
18054 * The specific menu item type for selecting a playback rate.
18055 *
18056 * @extends MenuItem
18057 */
18058class PlaybackRateMenuItem extends MenuItem {
18059  /**
18060   * Creates an instance of this class.
18061   *
18062   * @param {Player} player
18063   *        The `Player` that this class should be attached to.
18064   *
18065   * @param {Object} [options]
18066   *        The key/value store of player options.
18067   */
18068  constructor(player, options) {
18069    const label = options.rate;
18070    const rate = parseFloat(label, 10);
18071
18072    // Modify options for parent MenuItem class's init.
18073    options.label = label;
18074    options.selected = rate === player.playbackRate();
18075    options.selectable = true;
18076    options.multiSelectable = false;
18077    super(player, options);
18078    this.label = label;
18079    this.rate = rate;
18080    this.on(player, 'ratechange', (e) => this.update(e));
18081  }
18082
18083  /**
18084   * This gets called when an `PlaybackRateMenuItem` is "clicked". See
18085   * {@link ClickableComponent} for more detailed information on what a click can be.
18086   *
18087   * @param {Event} [event]
18088   *        The `keydown`, `tap`, or `click` event that caused this function to be
18089   *        called.
18090   *
18091   * @listens tap
18092   * @listens click
18093   */
18094  handleClick(event) {
18095    super.handleClick();
18096    this.player().playbackRate(this.rate);
18097  }
18098
18099  /**
18100   * Update the PlaybackRateMenuItem when the playbackrate changes.
18101   *
18102   * @param {Event} [event]
18103   *        The `ratechange` event that caused this function to run.
18104   *
18105   * @listens Player#ratechange
18106   */
18107  update(event) {
18108    this.selected(this.player().playbackRate() === this.rate);
18109  }
18110}
18111
18112/**
18113 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18114 *
18115 * @type {string}
18116 * @private
18117 */
18118PlaybackRateMenuItem.prototype.contentElType = 'button';
18119Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18120
18121/**
18122 * @file playback-rate-menu-button.js
18123 */
18124
18125/** @import Player from '../../player' */
18126
18127/**
18128 * The component for controlling the playback rate.
18129 *
18130 * @extends MenuButton
18131 */
18132class PlaybackRateMenuButton extends MenuButton {
18133  /**
18134   * Creates an instance of this class.
18135   *
18136   * @param {Player} player
18137   *        The `Player` that this class should be attached to.
18138   *
18139   * @param {Object} [options]
18140   *        The key/value store of player options.
18141   */
18142  constructor(player, options) {
18143    super(player, options);
18144    this.menuButton_.el_.setAttribute('aria-describedby', this.labelElId_);
18145    this.updateVisibility();
18146    this.updateLabel();
18147    this.on(player, 'loadstart', (e) => this.updateVisibility(e));
18148    this.on(player, 'ratechange', (e) => this.updateLabel(e));
18149    this.on(player, 'playbackrateschange', (e) => this.handlePlaybackRateschange(e));
18150  }
18151
18152  /**
18153   * Create the `Component`'s DOM element
18154   *
18155   * @return {Element}
18156   *         The element that was created.
18157   */
18158  createEl() {
18159    const el = super.createEl();
18160    this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
18161    this.labelEl_ = createEl('div', {
18162      className: 'vjs-playback-rate-value',
18163      id: this.labelElId_,
18164      textContent: '1x',
18165    });
18166    el.appendChild(this.labelEl_);
18167    return el;
18168  }
18169  dispose() {
18170    this.labelEl_ = null;
18171    super.dispose();
18172  }
18173
18174  /**
18175   * Builds the default DOM `className`.
18176   *
18177   * @return {string}
18178   *         The DOM `className` for this object.
18179   */
18180  buildCSSClass() {
18181    return `vjs-playback-rate ${super.buildCSSClass()}`;
18182  }
18183  buildWrapperCSSClass() {
18184    return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
18185  }
18186
18187  /**
18188   * Create the list of menu items. Specific to each subclass.
18189   *
18190   */
18191  createItems() {
18192    const rates = this.playbackRates();
18193    const items = [];
18194    for (let i = rates.length - 1; i >= 0; i--) {
18195      items.push(
18196        new PlaybackRateMenuItem(this.player(), {
18197          rate: rates[i] + 'x',
18198        })
18199      );
18200    }
18201    return items;
18202  }
18203
18204  /**
18205   * On playbackrateschange, update the menu to account for the new items.
18206   *
18207   * @listens Player#playbackrateschange
18208   */
18209  handlePlaybackRateschange(event) {
18210    this.update();
18211  }
18212
18213  /**
18214   * Get possible playback rates
18215   *
18216   * @return {Array}
18217   *         All possible playback rates
18218   */
18219  playbackRates() {
18220    const player = this.player();
18221    return (player.playbackRates && player.playbackRates()) || [];
18222  }
18223
18224  /**
18225   * Get whether playback rates is supported by the tech
18226   * and an array of playback rates exists
18227   *
18228   * @return {boolean}
18229   *         Whether changing playback rate is supported
18230   */
18231  playbackRateSupported() {
18232    return (
18233      this.player().tech_ &&
18234      this.player().tech_.featuresPlaybackRate &&
18235      this.playbackRates() &&
18236      this.playbackRates().length > 0
18237    );
18238  }
18239
18240  /**
18241   * Hide playback rate controls when they're no playback rate options to select
18242   *
18243   * @param {Event} [event]
18244   *        The event that caused this function to run.
18245   *
18246   * @listens Player#loadstart
18247   */
18248  updateVisibility(event) {
18249    if (this.playbackRateSupported()) {
18250      this.removeClass('vjs-hidden');
18251    } else {
18252      this.addClass('vjs-hidden');
18253    }
18254  }
18255
18256  /**
18257   * Update button label when rate changed
18258   *
18259   * @param {Event} [event]
18260   *        The event that caused this function to run.
18261   *
18262   * @listens Player#ratechange
18263   */
18264  updateLabel(event) {
18265    if (this.playbackRateSupported()) {
18266      this.labelEl_.textContent = this.player().playbackRate() + 'x';
18267    }
18268  }
18269}
18270
18271/**
18272 * The text that should display over the `PlaybackRateMenuButton`s controls.
18273 *
18274 * Added for localization.
18275 *
18276 * @type {string}
18277 * @protected
18278 */
18279PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18280Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18281
18282/**
18283 * @file spacer.js
18284 */
18285
18286/**
18287 * Just an empty spacer element that can be used as an append point for plugins, etc.
18288 * Also can be used to create space between elements when necessary.
18289 *
18290 * @extends Component
18291 */
18292class Spacer extends Component$1 {
18293  /**
18294   * Builds the default DOM `className`.
18295   *
18296   * @return {string}
18297   *         The DOM `className` for this object.
18298   */
18299  buildCSSClass() {
18300    return `vjs-spacer ${super.buildCSSClass()}`;
18301  }
18302
18303  /**
18304   * Create the `Component`'s DOM element
18305   *
18306   * @return {Element}
18307   *         The element that was created.
18308   */
18309  createEl(tag = 'div', props = {}, attributes = {}) {
18310    if (!props.className) {
18311      props.className = this.buildCSSClass();
18312    }
18313    return super.createEl(tag, props, attributes);
18314  }
18315}
18316Component$1.registerComponent('Spacer', Spacer);
18317
18318/**
18319 * @file custom-control-spacer.js
18320 */
18321
18322/**
18323 * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18324 *
18325 * @extends Spacer
18326 */
18327class CustomControlSpacer extends Spacer {
18328  /**
18329   * Builds the default DOM `className`.
18330   *
18331   * @return {string}
18332   *         The DOM `className` for this object.
18333   */
18334  buildCSSClass() {
18335    return `vjs-custom-control-spacer ${super.buildCSSClass()}`;
18336  }
18337
18338  /**
18339   * Create the `Component`'s DOM element
18340   *
18341   * @return {Element}
18342   *         The element that was created.
18343   */
18344  createEl() {
18345    return super.createEl('div', {
18346      className: this.buildCSSClass(),
18347      // No-flex/table-cell mode requires there be some content
18348      // in the cell to fill the remaining space of the table.
18349      textContent: '\u00a0',
18350    });
18351  }
18352}
18353Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
18354
18355/**
18356 * @file control-bar.js
18357 */
18358
18359/**
18360 * Container of main controls.
18361 *
18362 * @extends Component
18363 */
18364class ControlBar extends Component$1 {
18365  /**
18366   * Create the `Component`'s DOM element
18367   *
18368   * @return {Element}
18369   *         The element that was created.
18370   */
18371  createEl() {
18372    return super.createEl('div', {
18373      className: 'vjs-control-bar',
18374      dir: 'ltr',
18375    });
18376  }
18377}
18378
18379/**
18380 * Default options for `ControlBar`
18381 *
18382 * @type {Object}
18383 * @private
18384 */
18385ControlBar.prototype.options_ = {
18386  children: [
18387    'playToggle',
18388    'skipBackward',
18389    'skipForward',
18390    'volumePanel',
18391    'currentTimeDisplay',
18392    'timeDivider',
18393    'durationDisplay',
18394    'progressControl',
18395    'liveDisplay',
18396    'seekToLive',
18397    'remainingTimeDisplay',
18398    'customControlSpacer',
18399    'playbackRateMenuButton',
18400    'chaptersButton',
18401    'descriptionsButton',
18402    'subsCapsButton',
18403    'audioTrackButton',
18404    'pictureInPictureToggle',
18405    'fullscreenToggle',
18406  ],
18407};
18408Component$1.registerComponent('ControlBar', ControlBar);
18409
18410/**
18411 * @file error-display.js
18412 */
18413
18414/** @import Player from './player' */
18415
18416/**
18417 * A display that indicates an error has occurred. This means that the video
18418 * is unplayable.
18419 *
18420 * @extends ModalDialog
18421 */
18422class ErrorDisplay extends ModalDialog {
18423  /**
18424   * Creates an instance of this class.
18425   *
18426   * @param  {Player} player
18427   *         The `Player` that this class should be attached to.
18428   *
18429   * @param  {Object} [options]
18430   *         The key/value store of player options.
18431   */
18432  constructor(player, options) {
18433    super(player, options);
18434    this.on(player, 'error', (e) => {
18435      this.open(e);
18436    });
18437  }
18438
18439  /**
18440   * Builds the default DOM `className`.
18441   *
18442   * @return {string}
18443   *         The DOM `className` for this object.
18444   *
18445   * @deprecated Since version 5.
18446   */
18447  buildCSSClass() {
18448    return `vjs-error-display ${super.buildCSSClass()}`;
18449  }
18450
18451  /**
18452   * Gets the localized error message based on the `Player`s error.
18453   *
18454   * @return {string}
18455   *         The `Player`s error message localized or an empty string.
18456   */
18457  content() {
18458    const error = this.player().error();
18459    return error ? this.localize(error.message) : '';
18460  }
18461}
18462
18463/**
18464 * The default options for an `ErrorDisplay`.
18465 *
18466 * @private
18467 */
18468ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, {
18469  pauseOnOpen: false,
18470  fillAlways: true,
18471  temporary: false,
18472  uncloseable: true,
18473});
18474Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
18475
18476/** @import Player from './player' */
18477/** @import { ContentDescriptor } from  '../utils/dom' */
18478
18479/**
18480 * Creates DOM element of 'select' & its options.
18481 *
18482 * @extends Component
18483 */
18484class TextTrackSelect extends Component$1 {
18485  /**
18486   * Creates an instance of this class.
18487   *
18488   * @param {Player} player
18489   *        The `Player` that this class should be attached to.
18490   *
18491   * @param {Object} [options]
18492   *        The key/value store of player options.
18493   *
18494   * @param {ContentDescriptor} [options.content=undefined]
18495   *        Provide customized content for this modal.
18496   *
18497   * @param {string} [options.legendId]
18498   *        A text with part of an string to create atribute of aria-labelledby.
18499   *
18500   * @param {string} [options.id]
18501   *        A text with part of an string to create atribute of aria-labelledby.
18502   *
18503   * @param {Array} [options.SelectOptions]
18504   *        Array that contains the value & textContent of for each of the
18505   *        options elements.
18506   */
18507  constructor(player, options = {}) {
18508    super(player, options);
18509    this.el_.setAttribute('aria-labelledby', this.selectLabelledbyIds);
18510  }
18511
18512  /**
18513   * Create the `TextTrackSelect`'s DOM element
18514   *
18515   * @return {Element}
18516   *         The DOM element that gets created.
18517   */
18518  createEl() {
18519    this.selectLabelledbyIds = [this.options_.legendId, this.options_.labelId].join(' ').trim();
18520
18521    // Create select & inner options
18522    const selectoptions = createEl(
18523      'select',
18524      {
18525        id: this.options_.id,
18526      },
18527      {},
18528      this.options_.SelectOptions.map((optionText) => {
18529        // Constructs an id for the <option>.
18530        // For the colour settings that have two <selects> with a <label> each, generates an id based off the label value
18531        // For font size/family and edge style with one <select> and no <label>, generates an id with a guid
18532        const optionId =
18533          (this.options_.labelId ? this.options_.labelId : `vjs-track-option-${newGUID()}`) +
18534          '-' +
18535          optionText[1].replace(/\W+/g, '');
18536        const option = createEl('option', {
18537          id: optionId,
18538          value: this.localize(optionText[0]),
18539          textContent: this.localize(optionText[1]),
18540        });
18541        option.setAttribute('aria-labelledby', `${this.selectLabelledbyIds} ${optionId}`);
18542        return option;
18543      })
18544    );
18545    return selectoptions;
18546  }
18547}
18548Component$1.registerComponent('TextTrackSelect', TextTrackSelect);
18549
18550/** @import Player from './player' */
18551/** @import { ContentDescriptor } from '../utils/dom' */
18552
18553/**
18554 * Creates fieldset section of 'TextTrackSettings'.
18555 * Manganes two versions of fieldsets, one for type of 'colors'
18556 * & the other for 'font', Component adds diferent DOM elements
18557 * to that fieldset  depending on the type.
18558 *
18559 * @extends Component
18560 */
18561class TextTrackFieldset extends Component$1 {
18562  /**
18563   * Creates an instance of this class.
18564   *
18565   * @param {Player} player
18566   *        The `Player` that this class should be attached to.
18567   *
18568   * @param {Object} [options]
18569   *        The key/value store of player options.
18570   *
18571   * @param {ContentDescriptor} [options.content=undefined]
18572   *        Provide customized content for this modal.
18573   *
18574   * @param {string} [options.legendId]
18575   *        A text with part of an string to create atribute of aria-labelledby.
18576   *        It passes to 'TextTrackSelect'.
18577   *
18578   * @param {string} [options.id]
18579   *        A text with part of an string to create atribute of aria-labelledby.
18580   *        It passes to 'TextTrackSelect'.
18581   *
18582   * @param {string} [options.legendText]
18583   *        A text to use as the text content of the legend element.
18584   *
18585   * @param {Array} [options.selects]
18586   *        Array that contains the selects that are use to create 'selects'
18587   *        components.
18588   *
18589   * @param {Array} [options.SelectOptions]
18590   *        Array that contains the value & textContent of for each of the
18591   *        options elements, it passes to 'TextTrackSelect'.
18592   *
18593   * @param {string} [options.type]
18594   *        Conditions if some DOM elements will be added to the fieldset
18595   *        component.
18596   *
18597   * @param {Object} [options.selectConfigs]
18598   *        Object with the following properties that are the selects configurations:
18599   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
18600   *        fontPercent, textOpacity, windowColor, windowOpacity.
18601   *        These properties are use to configure the 'TextTrackSelect' Component.
18602   */
18603  constructor(player, options = {}) {
18604    super(player, options);
18605
18606    // Add Components & DOM Elements
18607    const legendElement = createEl('legend', {
18608      textContent: this.localize(this.options_.legendText),
18609      id: this.options_.legendId,
18610    });
18611    this.el().appendChild(legendElement);
18612    const selects = this.options_.selects;
18613
18614    // Iterate array of selects to create 'selects' components
18615    for (const i of selects) {
18616      const selectConfig = this.options_.selectConfigs[i];
18617      const selectClassName = selectConfig.className;
18618      const id = selectConfig.id.replace('%s', this.options_.id_);
18619      let span = null;
18620      const guid = `vjs_select_${newGUID()}`;
18621
18622      // Conditionally create span to add on the component
18623      if (this.options_.type === 'colors') {
18624        span = createEl('span', {
18625          className: selectClassName,
18626        });
18627        const label = createEl('label', {
18628          id,
18629          className: 'vjs-label',
18630          textContent: this.localize(selectConfig.label),
18631        });
18632        label.setAttribute('for', guid);
18633        span.appendChild(label);
18634      }
18635      const textTrackSelect = new TextTrackSelect(player, {
18636        SelectOptions: selectConfig.options,
18637        legendId: this.options_.legendId,
18638        id: guid,
18639        labelId: id,
18640      });
18641      this.addChild(textTrackSelect);
18642
18643      // Conditionally append to 'select' component to conditionally created span
18644      if (this.options_.type === 'colors') {
18645        span.appendChild(textTrackSelect.el());
18646        this.el().appendChild(span);
18647      }
18648    }
18649  }
18650
18651  /**
18652   * Create the `TextTrackFieldset`'s DOM element
18653   *
18654   * @return {Element}
18655   *         The DOM element that gets created.
18656   */
18657  createEl() {
18658    const el = createEl('fieldset', {
18659      // Prefixing classes of elements within a player with "vjs-"
18660      // is a convention used in Video.js.
18661      className: this.options_.className,
18662    });
18663    return el;
18664  }
18665}
18666Component$1.registerComponent('TextTrackFieldset', TextTrackFieldset);
18667
18668/** @import Player from './player' */
18669/** @import { ContentDescriptor } from  '../utils/dom' */
18670
18671/**
18672 * The component 'TextTrackSettingsColors' displays a set of 'fieldsets'
18673 * using the component 'TextTrackFieldset'.
18674 *
18675 * @extends Component
18676 */
18677class TextTrackSettingsColors extends Component$1 {
18678  /**
18679   * Creates an instance of this class.
18680   *
18681   * @param {Player} player
18682   *        The `Player` that this class should be attached to.
18683   *
18684   * @param {Object} [options]
18685   *        The key/value store of player options.
18686   *
18687   * @param {ContentDescriptor} [options.content=undefined]
18688   *        Provide customized content for this modal.
18689   *
18690   * @param {Array} [options.fieldSets]
18691   *        Array that contains the configurations for the selects.
18692   *
18693   * @param {Object} [options.selectConfigs]
18694   *        Object with the following properties that are the select confugations:
18695   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
18696   *        fontPercent, textOpacity, windowColor, windowOpacity.
18697   *        it passes to 'TextTrackFieldset'.
18698   */
18699  constructor(player, options = {}) {
18700    super(player, options);
18701    const id_ = this.options_.textTrackComponentid;
18702
18703    // createElFgColor_
18704    const ElFgColorFieldset = new TextTrackFieldset(player, {
18705      id_,
18706      legendId: `captions-text-legend-${id_}`,
18707      legendText: this.localize('Text'),
18708      className: 'vjs-fg vjs-track-setting',
18709      selects: this.options_.fieldSets[0],
18710      selectConfigs: this.options_.selectConfigs,
18711      type: 'colors',
18712    });
18713    this.addChild(ElFgColorFieldset);
18714
18715    // createElBgColor_
18716    const ElBgColorFieldset = new TextTrackFieldset(player, {
18717      id_,
18718      legendId: `captions-background-${id_}`,
18719      legendText: this.localize('Text Background'),
18720      className: 'vjs-bg vjs-track-setting',
18721      selects: this.options_.fieldSets[1],
18722      selectConfigs: this.options_.selectConfigs,
18723      type: 'colors',
18724    });
18725    this.addChild(ElBgColorFieldset);
18726
18727    // createElWinColor_
18728    const ElWinColorFieldset = new TextTrackFieldset(player, {
18729      id_,
18730      legendId: `captions-window-${id_}`,
18731      legendText: this.localize('Caption Area Background'),
18732      className: 'vjs-window vjs-track-setting',
18733      selects: this.options_.fieldSets[2],
18734      selectConfigs: this.options_.selectConfigs,
18735      type: 'colors',
18736    });
18737    this.addChild(ElWinColorFieldset);
18738  }
18739
18740  /**
18741   * Create the `TextTrackSettingsColors`'s DOM element
18742   *
18743   * @return {Element}
18744   *         The DOM element that gets created.
18745   */
18746  createEl() {
18747    const el = createEl('div', {
18748      className: 'vjs-track-settings-colors',
18749    });
18750    return el;
18751  }
18752}
18753Component$1.registerComponent('TextTrackSettingsColors', TextTrackSettingsColors);
18754
18755/** @import Player from './player' */
18756/** @import { ContentDescriptor } from  '../utils/dom' */
18757
18758/**
18759 * The component 'TextTrackSettingsFont' displays a set of 'fieldsets'
18760 * using the component 'TextTrackFieldset'.
18761 *
18762 * @extends Component
18763 */
18764class TextTrackSettingsFont extends Component$1 {
18765  /**
18766   * Creates an instance of this class.
18767   *
18768   * @param {Player} player
18769   *        The `Player` that this class should be attached to.
18770   *
18771   * @param {Object} [options]
18772   *        The key/value store of player options.
18773   *
18774   * @param {ContentDescriptor} [options.content=undefined]
18775   *        Provide customized content for this modal.
18776   *
18777   * @param {Array} [options.fieldSets]
18778   *        Array that contains the configurations for the selects.
18779   *
18780   * @param {Object} [options.selectConfigs]
18781   *        Object with the following properties that are the select confugations:
18782   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
18783   *        fontPercent, textOpacity, windowColor, windowOpacity.
18784   *        it passes to 'TextTrackFieldset'.
18785   */
18786  constructor(player, options = {}) {
18787    super(player, options);
18788    const id_ = this.options_.textTrackComponentid;
18789    const ElFgColorFieldset = new TextTrackFieldset(player, {
18790      id_,
18791      legendId: `captions-font-size-${id_}`,
18792      legendText: 'Font Size',
18793      className: 'vjs-font-percent vjs-track-setting',
18794      selects: this.options_.fieldSets[0],
18795      selectConfigs: this.options_.selectConfigs,
18796      type: 'font',
18797    });
18798    this.addChild(ElFgColorFieldset);
18799    const ElBgColorFieldset = new TextTrackFieldset(player, {
18800      id_,
18801      legendId: `captions-edge-style-${id_}`,
18802      legendText: this.localize('Text Edge Style'),
18803      className: 'vjs-edge-style vjs-track-setting',
18804      selects: this.options_.fieldSets[1],
18805      selectConfigs: this.options_.selectConfigs,
18806      type: 'font',
18807    });
18808    this.addChild(ElBgColorFieldset);
18809    const ElWinColorFieldset = new TextTrackFieldset(player, {
18810      id_,
18811      legendId: `captions-font-family-${id_}`,
18812      legendText: this.localize('Font Family'),
18813      className: 'vjs-font-family vjs-track-setting',
18814      selects: this.options_.fieldSets[2],
18815      selectConfigs: this.options_.selectConfigs,
18816      type: 'font',
18817    });
18818    this.addChild(ElWinColorFieldset);
18819  }
18820
18821  /**
18822   * Create the `TextTrackSettingsFont`'s DOM element
18823   *
18824   * @return {Element}
18825   *         The DOM element that gets created.
18826   */
18827  createEl() {
18828    const el = createEl('div', {
18829      className: 'vjs-track-settings-font',
18830    });
18831    return el;
18832  }
18833}
18834Component$1.registerComponent('TextTrackSettingsFont', TextTrackSettingsFont);
18835
18836/**
18837 * Buttons of reset & done that modal 'TextTrackSettings'
18838 * uses as part of its content.
18839 *
18840 * 'Reset': Resets all settings on 'TextTrackSettings'.
18841 * 'Done': Closes 'TextTrackSettings' modal.
18842 *
18843 * @extends Component
18844 */
18845class TrackSettingsControls extends Component$1 {
18846  constructor(player, options = {}) {
18847    super(player, options);
18848
18849    // Create DOM elements
18850    const defaultsDescription = this.localize('restore all settings to the default values');
18851    const resetButton = new Button(player, {
18852      controlText: defaultsDescription,
18853      className: 'vjs-default-button',
18854    });
18855    resetButton.el().classList.remove('vjs-control', 'vjs-button');
18856    resetButton.el().textContent = this.localize('Reset');
18857    this.addChild(resetButton);
18858    const doneButton = new Button(player, {
18859      controlText: defaultsDescription,
18860      className: 'vjs-done-button',
18861    });
18862
18863    // Remove unrequired style classes
18864    doneButton.el().classList.remove('vjs-control', 'vjs-button');
18865    doneButton.el().textContent = this.localize('Done');
18866    this.addChild(doneButton);
18867  }
18868
18869  /**
18870   * Create the `TrackSettingsControls`'s DOM element
18871   *
18872   * @return {Element}
18873   *         The DOM element that gets created.
18874   */
18875  createEl() {
18876    const el = createEl('div', {
18877      className: 'vjs-track-settings-controls',
18878    });
18879    return el;
18880  }
18881}
18882Component$1.registerComponent('TrackSettingsControls', TrackSettingsControls);
18883
18884/**
18885 * @file text-track-settings.js
18886 */
18887
18888/** @import Player from '../player' */
18889
18890const LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
18891const COLOR_BLACK = ['#000', 'Black'];
18892const COLOR_BLUE = ['#00F', 'Blue'];
18893const COLOR_CYAN = ['#0FF', 'Cyan'];
18894const COLOR_GREEN = ['#0F0', 'Green'];
18895const COLOR_MAGENTA = ['#F0F', 'Magenta'];
18896const COLOR_RED = ['#F00', 'Red'];
18897const COLOR_WHITE = ['#FFF', 'White'];
18898const COLOR_YELLOW = ['#FF0', 'Yellow'];
18899const OPACITY_OPAQUE = ['1', 'Opaque'];
18900const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18901const OPACITY_TRANS = ['0', 'Transparent'];
18902
18903// Configuration for the various <select> elements in the DOM of this component.
18904//
18905// Possible keys include:
18906//
18907// `default`:
18908//   The default option index. Only needs to be provided if not zero.
18909// `parser`:
18910//   A function which is used to parse the value from the selected option in
18911//   a customized way.
18912// `selector`:
18913//   The selector used to find the associated <select> element.
18914const selectConfigs = {
18915  backgroundColor: {
18916    selector: '.vjs-bg-color > select',
18917    id: 'captions-background-color-%s',
18918    label: 'Color',
18919    options: [
18920      COLOR_BLACK,
18921      COLOR_WHITE,
18922      COLOR_RED,
18923      COLOR_GREEN,
18924      COLOR_BLUE,
18925      COLOR_YELLOW,
18926      COLOR_MAGENTA,
18927      COLOR_CYAN,
18928    ],
18929    className: 'vjs-bg-color',
18930  },
18931  backgroundOpacity: {
18932    selector: '.vjs-bg-opacity > select',
18933    id: 'captions-background-opacity-%s',
18934    label: 'Opacity',
18935    options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS],
18936    className: 'vjs-bg-opacity vjs-opacity',
18937  },
18938  color: {
18939    selector: '.vjs-text-color > select',
18940    id: 'captions-foreground-color-%s',
18941    label: 'Color',
18942    options: [
18943      COLOR_WHITE,
18944      COLOR_BLACK,
18945      COLOR_RED,
18946      COLOR_GREEN,
18947      COLOR_BLUE,
18948      COLOR_YELLOW,
18949      COLOR_MAGENTA,
18950      COLOR_CYAN,
18951    ],
18952    className: 'vjs-text-color',
18953  },
18954  edgeStyle: {
18955    selector: '.vjs-edge-style > select',
18956    id: '',
18957    label: 'Text Edge Style',
18958    options: [
18959      ['none', 'None'],
18960      ['raised', 'Raised'],
18961      ['depressed', 'Depressed'],
18962      ['uniform', 'Uniform'],
18963      ['dropshadow', 'Drop shadow'],
18964    ],
18965  },
18966  fontFamily: {
18967    selector: '.vjs-font-family > select',
18968    id: '',
18969    label: 'Font Family',
18970    options: [
18971      ['proportionalSansSerif', 'Proportional Sans-Serif'],
18972      ['monospaceSansSerif', 'Monospace Sans-Serif'],
18973      ['proportionalSerif', 'Proportional Serif'],
18974      ['monospaceSerif', 'Monospace Serif'],
18975      ['casual', 'Casual'],
18976      ['script', 'Script'],
18977      ['small-caps', 'Small Caps'],
18978    ],
18979  },
18980  fontPercent: {
18981    selector: '.vjs-font-percent > select',
18982    id: '',
18983    label: 'Font Size',
18984    options: [
18985      ['0.50', '50%'],
18986      ['0.75', '75%'],
18987      ['1.00', '100%'],
18988      ['1.25', '125%'],
18989      ['1.50', '150%'],
18990      ['1.75', '175%'],
18991      ['2.00', '200%'],
18992      ['3.00', '300%'],
18993      ['4.00', '400%'],
18994    ],
18995    default: 2,
18996    parser: (v) => (v === '1.00' ? null : Number(v)),
18997  },
18998  textOpacity: {
18999    selector: '.vjs-text-opacity > select',
19000    id: 'captions-foreground-opacity-%s',
19001    label: 'Opacity',
19002    options: [OPACITY_OPAQUE, OPACITY_SEMI],
19003    className: 'vjs-text-opacity vjs-opacity',
19004  },
19005  // Options for this object are defined below.
19006  windowColor: {
19007    selector: '.vjs-window-color > select',
19008    id: 'captions-window-color-%s',
19009    label: 'Color',
19010    className: 'vjs-window-color',
19011  },
19012  // Options for this object are defined below.
19013  windowOpacity: {
19014    selector: '.vjs-window-opacity > select',
19015    id: 'captions-window-opacity-%s',
19016    label: 'Opacity',
19017    options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE],
19018    className: 'vjs-window-opacity vjs-opacity',
19019  },
19020};
19021selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
19022
19023/**
19024 * Get the actual value of an option.
19025 *
19026 * @param  {string} value
19027 *         The value to get
19028 *
19029 * @param  {Function} [parser]
19030 *         Optional function to adjust the value.
19031 *
19032 * @return {*}
19033 *         - Will be `undefined` if no value exists
19034 *         - Will be `undefined` if the given value is "none".
19035 *         - Will be the actual value otherwise.
19036 *
19037 * @private
19038 */
19039function parseOptionValue(value, parser) {
19040  if (parser) {
19041    value = parser(value);
19042  }
19043  if (value && value !== 'none') {
19044    return value;
19045  }
19046}
19047
19048/**
19049 * Gets the value of the selected <option> element within a <select> element.
19050 *
19051 * @param  {Element} el
19052 *         the element to look in
19053 *
19054 * @param  {Function} [parser]
19055 *         Optional function to adjust the value.
19056 *
19057 * @return {*}
19058 *         - Will be `undefined` if no value exists
19059 *         - Will be `undefined` if the given value is "none".
19060 *         - Will be the actual value otherwise.
19061 *
19062 * @private
19063 */
19064function getSelectedOptionValue(el, parser) {
19065  const value = el.options[el.options.selectedIndex].value;
19066  return parseOptionValue(value, parser);
19067}
19068
19069/**
19070 * Sets the selected <option> element within a <select> element based on a
19071 * given value.
19072 *
19073 * @param {Element} el
19074 *        The element to look in.
19075 *
19076 * @param {string} value
19077 *        the property to look on.
19078 *
19079 * @param {Function} [parser]
19080 *        Optional function to adjust the value before comparing.
19081 *
19082 * @private
19083 */
19084function setSelectedOption(el, value, parser) {
19085  if (!value) {
19086    return;
19087  }
19088  for (let i = 0; i < el.options.length; i++) {
19089    if (parseOptionValue(el.options[i].value, parser) === value) {
19090      el.selectedIndex = i;
19091      break;
19092    }
19093  }
19094}
19095
19096/**
19097 * Manipulate Text Tracks settings.
19098 *
19099 * @extends ModalDialog
19100 */
19101class TextTrackSettings extends ModalDialog {
19102  /**
19103   * Creates an instance of this class.
19104   *
19105   * @param {Player} player
19106   *         The `Player` that this class should be attached to.
19107   *
19108   * @param {Object} [options]
19109   *         The key/value store of player options.
19110   */
19111  constructor(player, options) {
19112    options.temporary = false;
19113    super(player, options);
19114    this.updateDisplay = this.updateDisplay.bind(this);
19115
19116    // fill the modal and pretend we have opened it
19117    this.fill();
19118    this.hasBeenOpened_ = this.hasBeenFilled_ = true;
19119    this.renderModalComponents(player);
19120    this.endDialog = createEl('p', {
19121      className: 'vjs-control-text',
19122      textContent: this.localize('End of dialog window.'),
19123    });
19124    this.el().appendChild(this.endDialog);
19125    this.setDefaults();
19126
19127    // Grab `persistTextTrackSettings` from the player options if not passed in child options
19128    if (options.persistTextTrackSettings === undefined) {
19129      this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
19130    }
19131    this.bindFunctionsToSelectsAndButtons();
19132    if (this.options_.persistTextTrackSettings) {
19133      this.restoreSettings();
19134    }
19135  }
19136  renderModalComponents(player) {
19137    const textTrackSettingsColors = new TextTrackSettingsColors(player, {
19138      textTrackComponentid: this.id_,
19139      selectConfigs,
19140      fieldSets: [
19141        ['color', 'textOpacity'],
19142        ['backgroundColor', 'backgroundOpacity'],
19143        ['windowColor', 'windowOpacity'],
19144      ],
19145    });
19146    this.addChild(textTrackSettingsColors);
19147    const textTrackSettingsFont = new TextTrackSettingsFont(player, {
19148      textTrackComponentid: this.id_,
19149      selectConfigs,
19150      fieldSets: [['fontPercent'], ['edgeStyle'], ['fontFamily']],
19151    });
19152    this.addChild(textTrackSettingsFont);
vendor: 4,608 bytes, lines 19153-19318
19153    const trackSettingsControls = new TrackSettingsControls(player);
19154    this.addChild(trackSettingsControls);
19155  }
19156  bindFunctionsToSelectsAndButtons() {
19157    this.on(this.$('.vjs-done-button'), ['click', 'tap'], () => {
19158      this.saveSettings();
19159      this.close();
19160    });
19161    this.on(this.$('.vjs-default-button'), ['click', 'tap'], () => {
19162      this.setDefaults();
19163      this.updateDisplay();
19164    });
19165    each(selectConfigs, (config) => {
19166      this.on(this.$(config.selector), 'change', this.updateDisplay);
19167    });
19168  }
19169  dispose() {
19170    this.endDialog = null;
19171    super.dispose();
19172  }
19173  label() {
19174    return this.localize('Caption Settings Dialog');
19175  }
19176  description() {
19177    return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
19178  }
19179  buildCSSClass() {
19180    return super.buildCSSClass() + ' vjs-text-track-settings';
19181  }
19182
19183  /**
19184   * Gets an object of text track settings (or null).
19185   *
19186   * @return {Object}
19187   *         An object with config values parsed from the DOM or localStorage.
19188   */
19189  getValues() {
19190    return reduce(
19191      selectConfigs,
19192      (accum, config, key) => {
19193        const value = getSelectedOptionValue(this.$(config.selector), config.parser);
19194        if (value !== undefined) {
19195          accum[key] = value;
19196        }
19197        return accum;
19198      },
19199      {}
19200    );
19201  }
19202
19203  /**
19204   * Sets text track settings from an object of values.
19205   *
19206   * @param {Object} values
19207   *        An object with config values parsed from the DOM or localStorage.
19208   */
19209  setValues(values) {
19210    each(selectConfigs, (config, key) => {
19211      setSelectedOption(this.$(config.selector), values[key], config.parser);
19212    });
19213  }
19214
19215  /**
19216   * Sets all `<select>` elements to their default values.
19217   */
19218  setDefaults() {
19219    each(selectConfigs, (config) => {
19220      const index = config.hasOwnProperty('default') ? config.default : 0;
19221      this.$(config.selector).selectedIndex = index;
19222    });
19223  }
19224
19225  /**
19226   * Restore texttrack settings from localStorage
19227   */
19228  restoreSettings() {
19229    let values;
19230    try {
19231      values = JSON.parse(window__default['default'].localStorage.getItem(LOCAL_STORAGE_KEY$1));
19232    } catch (err) {
19233      log$1.warn(err);
19234    }
19235    if (values) {
19236      this.setValues(values);
19237    }
19238  }
19239
19240  /**
19241   * Save text track settings to localStorage
19242   */
19243  saveSettings() {
19244    if (!this.options_.persistTextTrackSettings) {
19245      return;
19246    }
19247    const values = this.getValues();
19248    try {
19249      if (Object.keys(values).length) {
19250        window__default['default'].localStorage.setItem(
19251          LOCAL_STORAGE_KEY$1,
19252          JSON.stringify(values)
19253        );
19254      } else {
19255        window__default['default'].localStorage.removeItem(LOCAL_STORAGE_KEY$1);
19256      }
19257    } catch (err) {
19258      log$1.warn(err);
19259    }
19260  }
19261
19262  /**
19263   * Update display of text track settings
19264   */
19265  updateDisplay() {
19266    const ttDisplay = this.player_.getChild('textTrackDisplay');
19267    if (ttDisplay) {
19268      ttDisplay.updateDisplay();
19269    }
19270  }
19271
19272  /**
19273   * Repopulate dialog with new localizations on languagechange
19274   */
19275  handleLanguagechange() {
19276    this.fill();
19277    this.renderModalComponents(this.player_);
19278    this.bindFunctionsToSelectsAndButtons();
19279  }
19280}
19281Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
19282
19283/**
19284 * @file resize-manager.js
19285 */
19286
19287/**
19288 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19289 *
19290 * 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}.
19291 *
19292 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19293 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19294 *
19295 * @example <caption>How to disable the resize manager</caption>
19296 * const player = videojs('#vid', {
19297 *   resizeManager: false
19298 * });
19299 *
19300 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19301 *
19302 * @extends Component
19303 */
19304class ResizeManager extends Component$1 {
19305  /**
19306   * Create the ResizeManager.
19307   *
19308   * @param {Object} player
19309   *        The `Player` that this class should be attached to.
19310   *
19311   * @param {Object} [options]
19312   *        The key/value store of ResizeManager options.
19313   *
19314   * @param {Object} [options.ResizeObserver]
19315   *        A polyfill for ResizeObserver can be passed in here.
19316   *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19317   */
19318  constructor(player, options) {
19319    let RESIZE_OBSERVER_AVAILABLE =
19320      options.ResizeObserver || window__default['default'].ResizeObserver;
19321
19322    // if `null` was passed, we want to disable the ResizeObserver
19323    if (options.ResizeObserver === null) {
19324      RESIZE_OBSERVER_AVAILABLE = false;
19325    }
19326
19327    // Only create an element when ResizeObserver isn't available
19328    const options_ = merge$1(
19329      {
19330        createEl: !RESIZE_OBSERVER_AVAILABLE,
19331        reportTouchActivity: false,
19332      },
19333      options
19334    );
19335    super(player, options_);
19336    this.ResizeObserver = options.ResizeObserver || window__default['default'].ResizeObserver;
19337    this.loadListener_ = null;
19338    this.resizeObserver_ = null;
19339    this.debouncedHandler_ = debounce(
19340      () => {
19341        this.resizeHandler();
19342      },
19343      100,
19344      false,
19345      this
19346    );
19347    if (RESIZE_OBSERVER_AVAILABLE) {
19348      this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
19349      this.resizeObserver_.observe(player.el());
19350    } else {
19351      this.loadListener_ = () => {
19352        if (!this.el_ || !this.el_.contentWindow) {
19353          return;
19354        }
19355        const debouncedHandler_ = this.debouncedHandler_;
19356        let unloadListener_ = (this.unloadListener_ = function () {
19357          off(this, 'resize', debouncedHandler_);
19358          off(this, 'unload', unloadListener_);
19359          unloadListener_ = null;
19360        });
19361
19362        // safari and edge can unload the iframe before resizemanager dispose
19363        // we have to dispose of event handlers correctly before that happens
19364        on(this.el_.contentWindow, 'unload', unloadListener_);
19365        on(this.el_.contentWindow, 'resize', debouncedHandler_);
19366      };
19367      this.one('load', this.loadListener_);
19368    }
19369  }
19370  createEl() {
19371    return super.createEl(
19372      'iframe',
19373      {
19374        className: 'vjs-resize-manager',
19375        tabIndex: -1,
19376        title: this.localize('No content'),
19377      },
19378      {
19379        'aria-hidden': 'true',
19380      }
19381    );
19382  }
19383
19384  /**
19385   * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19386   *
19387   * @fires Player#playerresize
19388   */
19389  resizeHandler() {
19390    /**
19391     * Called when the player size has changed
19392     *
19393     * @event Player#playerresize
19394     * @type {Event}
19395     */
19396    // make sure player is still around to trigger
19397    // prevents this from causing an error after dispose
19398    if (!this.player_ || !this.player_.trigger) {
19399      return;
19400    }
19401    this.player_.trigger('playerresize');
19402  }
19403  dispose() {
19404    if (this.debouncedHandler_) {
19405      this.debouncedHandler_.cancel();
19406    }
19407    if (this.resizeObserver_) {
19408      if (this.player_.el()) {
19409        this.resizeObserver_.unobserve(this.player_.el());
19410      }
19411      this.resizeObserver_.disconnect();
19412    }
19413    if (this.loadListener_) {
19414      this.off('load', this.loadListener_);
19415    }
19416    if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
19417      this.unloadListener_.call(this.el_.contentWindow);
19418    }
19419    this.ResizeObserver = null;
19420    this.resizeObserver = null;
19421    this.debouncedHandler_ = null;
19422    this.loadListener_ = null;
19423    super.dispose();
19424  }
19425}
19426Component$1.registerComponent('ResizeManager', ResizeManager);
19427
19428/** @import Player from './player' */
19429
19430const defaults$1 = {
19431  trackingThreshold: 20,
19432  liveTolerance: 15,
19433};
19434
19435/*
19436  track when we are at the live edge, and other helpers for live playback */
19437
19438/**
19439 * A class for checking live current time and determining when the player
19440 * is at or behind the live edge.
19441 */
19442class LiveTracker extends Component$1 {
19443  /**
19444   * Creates an instance of this class.
19445   *
19446   * @param {Player} player
19447   *        The `Player` that this class should be attached to.
19448   *
19449   * @param {Object} [options]
19450   *        The key/value store of player options.
19451   *
19452   * @param {number} [options.trackingThreshold=20]
19453   *        Number of seconds of live window (seekableEnd - seekableStart) that
19454   *        media needs to have before the liveui will be shown.
19455   *
19456   * @param {number} [options.liveTolerance=15]
19457   *        Number of seconds behind live that we have to be
19458   *        before we will be considered non-live. Note that this will only
19459   *        be used when playing at the live edge. This allows large seekable end
19460   *        changes to not effect whether we are live or not.
19461   */
19462  constructor(player, options) {
19463    // LiveTracker does not need an element
19464    const options_ = merge$1(defaults$1, options, {
19465      createEl: false,
19466    });
19467    super(player, options_);
19468    this.trackLiveHandler_ = () => this.trackLive_();
19469    this.handlePlay_ = (e) => this.handlePlay(e);
19470    this.handleFirstTimeupdate_ = (e) => this.handleFirstTimeupdate(e);
19471    this.handleSeeked_ = (e) => this.handleSeeked(e);
19472    this.seekToLiveEdge_ = (e) => this.seekToLiveEdge(e);
19473    this.reset_();
19474    this.on(this.player_, 'durationchange', (e) => this.handleDurationchange(e));
19475    // we should try to toggle tracking on canplay as native playback engines, like Safari
19476    // may not have the proper values for things like seekableEnd until then
19477    this.on(this.player_, 'canplay', () => this.toggleTracking());
19478  }
19479
19480  /**
19481   * all the functionality for tracking when seek end changes
19482   * and for tracking how far past seek end we should be
19483   */
19484  trackLive_() {
19485    const seekable = this.player_.seekable();
19486
19487    // skip undefined seekable
19488    if (!seekable || !seekable.length) {
19489      return;
19490    }
19491    const newTime = Number(window__default['default'].performance.now().toFixed(4));
vendor: 1,414 bytes, lines 19492-19535
19492    const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
19493    this.lastTime_ = newTime;
19494    this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
19495    const liveCurrentTime = this.liveCurrentTime();
19496    const currentTime = this.player_.currentTime();
19497
19498    // we are behind live if any are true
19499    // 1. the player is paused
19500    // 2. the user seeked to a location 2 seconds away from live
19501    // 3. the difference between live and current time is greater
19502    //    liveTolerance which defaults to 15s
19503    let isBehind =
19504      this.player_.paused() ||
19505      this.seekedBehindLive_ ||
19506      Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
19507
19508    // we cannot be behind if
19509    // 1. until we have not seen a timeupdate yet
19510    // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
19511    if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
19512      isBehind = false;
19513    }
19514    if (isBehind !== this.behindLiveEdge_) {
19515      this.behindLiveEdge_ = isBehind;
19516      this.trigger('liveedgechange');
19517    }
19518  }
19519
19520  /**
19521   * handle a durationchange event on the player
19522   * and start/stop tracking accordingly.
19523   */
19524  handleDurationchange() {
19525    this.toggleTracking();
19526  }
19527
19528  /**
19529   * start/stop tracking
19530   */
19531  toggleTracking() {
19532    if (
19533      this.player_.duration() === Infinity &&
19534      this.liveWindow() >= this.options_.trackingThreshold
19535    
vendor: 6,595 bytes, lines 19535-19785
19535) {
19536      if (this.player_.options_.liveui) {
19537        this.player_.addClass('vjs-liveui');
19538      }
19539      this.startTracking();
19540    } else {
19541      this.player_.removeClass('vjs-liveui');
19542      this.stopTracking();
19543    }
19544  }
19545
19546  /**
19547   * start tracking live playback
19548   */
19549  startTracking() {
19550    if (this.isTracking()) {
19551      return;
19552    }
19553
19554    // If we haven't seen a timeupdate, we need to check whether playback
19555    // began before this component started tracking. This can happen commonly
19556    // when using autoplay.
19557    if (!this.timeupdateSeen_) {
19558      this.timeupdateSeen_ = this.player_.hasStarted();
19559    }
19560    this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
19561    this.trackLive_();
19562    this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19563    if (!this.timeupdateSeen_) {
19564      this.one(this.player_, 'play', this.handlePlay_);
19565      this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19566    } else {
19567      this.on(this.player_, 'seeked', this.handleSeeked_);
19568    }
19569  }
19570
19571  /**
19572   * handle the first timeupdate on the player if it wasn't already playing
19573   * when live tracker started tracking.
19574   */
19575  handleFirstTimeupdate() {
19576    this.timeupdateSeen_ = true;
19577    this.on(this.player_, 'seeked', this.handleSeeked_);
19578  }
19579
19580  /**
19581   * Keep track of what time a seek starts, and listen for seeked
19582   * to find where a seek ends.
19583   */
19584  handleSeeked() {
19585    const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
19586    this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
19587    this.nextSeekedFromUser_ = false;
19588    this.trackLive_();
19589  }
19590
19591  /**
19592   * handle the first play on the player, and make sure that we seek
19593   * right to the live edge.
19594   */
19595  handlePlay() {
19596    this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
19597  }
19598
19599  /**
19600   * Stop tracking, and set all internal variables to
19601   * their initial value.
19602   */
19603  reset_() {
19604    this.lastTime_ = -1;
19605    this.pastSeekEnd_ = 0;
19606    this.lastSeekEnd_ = -1;
19607    this.behindLiveEdge_ = true;
19608    this.timeupdateSeen_ = false;
19609    this.seekedBehindLive_ = false;
19610    this.nextSeekedFromUser_ = false;
19611    this.clearInterval(this.trackingInterval_);
19612    this.trackingInterval_ = null;
19613    this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19614    this.off(this.player_, 'seeked', this.handleSeeked_);
19615    this.off(this.player_, 'play', this.handlePlay_);
19616    this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19617    this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
19618  }
19619
19620  /**
19621   * The next seeked event is from the user. Meaning that any seek
19622   * > 2s behind live will be considered behind live for real and
19623   * liveTolerance will be ignored.
19624   */
19625  nextSeekedFromUser() {
19626    this.nextSeekedFromUser_ = true;
19627  }
19628
19629  /**
19630   * stop tracking live playback
19631   */
19632  stopTracking() {
19633    if (!this.isTracking()) {
19634      return;
19635    }
19636    this.reset_();
19637    this.trigger('liveedgechange');
19638  }
19639
19640  /**
19641   * A helper to get the player seekable end
19642   * so that we don't have to null check everywhere
19643   *
19644   * @return {number}
19645   *         The furthest seekable end or Infinity.
19646   */
19647  seekableEnd() {
19648    const seekable = this.player_.seekable();
19649    const seekableEnds = [];
19650    let i = seekable ? seekable.length : 0;
19651    while (i--) {
19652      seekableEnds.push(seekable.end(i));
19653    }
19654
19655    // grab the furthest seekable end after sorting, or if there are none
19656    // default to Infinity
19657    return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
19658  }
19659
19660  /**
19661   * A helper to get the player seekable start
19662   * so that we don't have to null check everywhere
19663   *
19664   * @return {number}
19665   *         The earliest seekable start or 0.
19666   */
19667  seekableStart() {
19668    const seekable = this.player_.seekable();
19669    const seekableStarts = [];
19670    let i = seekable ? seekable.length : 0;
19671    while (i--) {
19672      seekableStarts.push(seekable.start(i));
19673    }
19674
19675    // grab the first seekable start after sorting, or if there are none
19676    // default to 0
19677    return seekableStarts.length ? seekableStarts.sort()[0] : 0;
19678  }
19679
19680  /**
19681   * Get the live time window aka
19682   * the amount of time between seekable start and
19683   * live current time.
19684   *
19685   * @return {number}
19686   *         The amount of seconds that are seekable in
19687   *         the live video.
19688   */
19689  liveWindow() {
19690    const liveCurrentTime = this.liveCurrentTime();
19691
19692    // if liveCurrenTime is Infinity then we don't have a liveWindow at all
19693    if (liveCurrentTime === Infinity) {
19694      return 0;
19695    }
19696    return liveCurrentTime - this.seekableStart();
19697  }
19698
19699  /**
19700   * Determines if the player is live, only checks if this component
19701   * is tracking live playback or not
19702   *
19703   * @return {boolean}
19704   *         Whether liveTracker is tracking
19705   */
19706  isLive() {
19707    return this.isTracking();
19708  }
19709
19710  /**
19711   * Determines if currentTime is at the live edge and won't fall behind
19712   * on each seekableendchange
19713   *
19714   * @return {boolean}
19715   *         Whether playback is at the live edge
19716   */
19717  atLiveEdge() {
19718    return !this.behindLiveEdge();
19719  }
19720
19721  /**
19722   * get what we expect the live current time to be
19723   *
19724   * @return {number}
19725   *         The expected live current time
19726   */
19727  liveCurrentTime() {
19728    return this.pastSeekEnd() + this.seekableEnd();
19729  }
19730
19731  /**
19732   * The number of seconds that have occurred after seekable end
19733   * changed. This will be reset to 0 once seekable end changes.
19734   *
19735   * @return {number}
19736   *         Seconds past the current seekable end
19737   */
19738  pastSeekEnd() {
19739    const seekableEnd = this.seekableEnd();
19740    if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
19741      this.pastSeekEnd_ = 0;
19742    }
19743    this.lastSeekEnd_ = seekableEnd;
19744    return this.pastSeekEnd_;
19745  }
19746
19747  /**
19748   * If we are currently behind the live edge, aka currentTime will be
19749   * behind on a seekableendchange
19750   *
19751   * @return {boolean}
19752   *         If we are behind the live edge
19753   */
19754  behindLiveEdge() {
19755    return this.behindLiveEdge_;
19756  }
19757
19758  /**
19759   * Whether live tracker is currently tracking or not.
19760   */
19761  isTracking() {
19762    return typeof this.trackingInterval_ === 'number';
19763  }
19764
19765  /**
19766   * Seek to the live edge if we are behind the live edge
19767   */
19768  seekToLiveEdge() {
19769    this.seekedBehindLive_ = false;
19770    if (this.atLiveEdge()) {
19771      return;
19772    }
19773    this.nextSeekedFromUser_ = false;
19774    this.player_.currentTime(this.liveCurrentTime());
19775  }
19776
19777  /**
19778   * Dispose of liveTracker
19779   */
19780  dispose() {
19781    this.stopTracking();
19782    super.dispose();
19783  }
19784}
19785Component
19785$1.registerComponent('LiveTracker', LiveTracker);
19786
19787/**
19788 * Displays an element over the player which contains an optional title and
19789 * description for the current content.
19790 *
19791 * Much of the code for this component originated in the now obsolete
19792 * videojs-dock plugin: https://github.com/brightcove/videojs-dock/
19793 *
19794 * @extends Component
19795 */
19796class TitleBar extends Component$1 {
19797  constructor(player, options) {
19798    super(player, options);
19799    this.on('statechanged', (e) => this.updateDom_());
19800    this.updateDom_();
19801  }
19802
19803  /**
19804   * Create the `TitleBar`'s DOM element
19805   *
19806   * @return {Element}
19807   *         The element that was created.
19808   */
19809  createEl() {
19810    this.els = {
19811      title: createEl('div', {
19812        className: 'vjs-title-bar-title',
19813        id: `vjs-title-bar-title-${newGUID()}`,
19814      }),
19815      description: createEl('div', {
19816        className: 'vjs-title-bar-description',
19817        id: `vjs-title-bar-description-${newGUID()}`,
19818      }),
19819    };
19820    return createEl(
19821      'div',
19822      {
19823        className: 'vjs-title-bar',
19824      },
19825      {},
19826      values(this.els)
19827    );
19828  }
19829
19830  /**
19831   * Updates the DOM based on the component's state object.
19832   */
19833  updateDom_() {
19834    const tech = this.player_.tech_;
19835    const techEl = tech && tech.el_;
19836    const techAriaAttrs = {
19837      title: 'aria-labelledby',
19838      description: 'aria-describedby',
19839    };
19840    ['title', 'description'].forEach((k) => {
19841      const value = this.state[k];
19842      const el = this.els[k];
19843      const techAriaAttr = techAriaAttrs[k];
19844      emptyEl(el);
19845      if (value) {
19846        textContent(el, value);
19847      }
19848
19849      // If there is a tech element available, update its ARIA attributes
19850      // according to whether a title and/or description have been provided.
19851      if (techEl) {
19852        techEl.removeAttribute(techAriaAttr);
19853        if (value) {
19854          techEl.setAttribute(techAriaAttr, el.id);
19855        }
19856      }
19857    });
19858    if (this.state.title || this.state.description) {
19859      this.show();
19860    } else {
19861      this.hide();
19862    }
19863  }
19864
19865  /**
19866   * Update the contents of the title bar component with new title and
19867   * description text.
19868   *
19869   * If both title and description are missing, the title bar will be hidden.
19870   *
19871   * If either title or description are present, the title bar will be visible.
19872   *
19873   * NOTE: Any previously set value will be preserved. To unset a previously
19874   * set value, you must pass an empty string or null.
19875   *
19876   * For example:
19877   *
19878   * ```
19879   * update({title: 'foo', description: 'bar'}) // title: 'foo', description: 'bar'
19880   * update({description: 'bar2'}) // title: 'foo', description: 'bar2'
19881   * update({title: ''}) // title: '', description: 'bar2'
19882   * update({title: 'foo', description: null}) // title: 'foo', description: null
19883   * ```
19884   *
19885   * @param  {Object} [options={}]
19886   *         An options object. When empty, the title bar will be hidden.
19887   *
19888   * @param  {string} [options.title]
19889   *         A title to display in the title bar.
19890   *
19891   * @param  {string} [options.description]
19892   *         A description to display in the title bar.
19893   */
19894  update(options) {
19895    this.setState(options);
19896  }
19897
19898  /**
19899   * Dispose the component.
19900   */
19901  dispose() {
19902    const tech = this.player_.tech_;
19903    const techEl = tech && tech.el_;
19904    if (techEl) {
19905      techEl.removeAttribute('aria-labelledby');
19906      techEl.removeAttribute('aria-describedby');
19907    }
19908    super.dispose();
19909    this.els = null;
19910  }
19911}
19912Component$1.registerComponent('TitleBar', TitleBar);
19913
19914/** @import Player from './player' */
19915
19916/**
19917 * @typedef {object} TransientButtonOptions
19918 * @property {string} [controlText] Control text, usually visible for these buttons
19919 * @property {number} [initialDisplay=4000] Time in ms that button should initially remain visible
19920 * @property {Array<'top'|'neartop'|'bottom'|'left'|'right'>} [position] Array of position strings to add basic styles for positioning
19921 * @property {string} [className] Class(es) to add
19922 * @property {boolean} [takeFocus=false] Whether element sohuld take focus when shown
19923 * @property {Function} [clickHandler] Function called on button activation
19924 */
19925
19926/** @type {TransientButtonOptions} */
19927const defaults = {
19928  initialDisplay: 4000,
19929  position: [],
19930  takeFocus: false,
19931};
19932
19933/**
19934 * A floating transient button.
19935 * It's recommended to insert these buttons _before_ the control bar with the this argument to `addChild`
19936 * for a logical tab order.
19937 *
19938 * @example
19939 * ```
19940 * player.addChild(
19941 *   'TransientButton',
19942 *   options,
19943 *   player.children().indexOf(player.getChild("ControlBar"))
19944 * )
19945 * ```
19946 *
19947 * @extends Button
19948 */
19949class TransientButton extends Button {
19950  /**
19951   * TransientButton constructor
19952   *
19953   * @param {Player} player The button's player
19954   * @param {TransientButtonOptions} options Options for the transient button
19955   */
19956  constructor(player, options) {
19957    options = merge$1(defaults, options);
19958    super(player, options);
19959    this.controlText(options.controlText);
19960    this.hide();
19961
19962    // When shown, the float button will be visible even if the user is inactive.
19963    // Clear this if there is any interaction.
19964    this.on(this.player_, ['useractive', 'userinactive'], (e) => {
19965      this.removeClass('force-display');
19966    });
19967  }
19968
19969  /**
19970   * Return CSS class including position classes
19971   *
19972   * @return {string} CSS class list
19973   */
19974  buildCSSClass() {
19975    return `vjs-transient-button focus-visible ${this.options_.position.map((c) =>
19975 `vjs-${c}`).join(' ')}`;
19976  }
19977
19978  /**
19979   * Create the button element
19980   *
19981   * @return {HTMLButtonElement} The button element
19982   */
19983  createEl() {
19984    /** @type HTMLButtonElement */
19985    const el = createEl(
19986      'button',
19987      {},
19988      {
19989        type: 'button',
19990        class: this.buildCSSClass(),
19991      },
19992      createEl('span')
19993    );
19994    this.controlTextEl_ = el.querySelector('span');
19995    return el;
19996  }
19997
19998  /**
19999   * Show the button. The button will remain visible for the `initialDisplay` time, default 4s,
20000   * and when there is user activity.
20001   */
20002  show() {
20003    super.show();
20004    this.addClass('force-display');
20005    if (this.options_.takeFocus) {
20006      this.el().focus({
20007        preventScroll: true,
20008      });
20009    }
20010    this.forceDisplayTimeout = this.player_.setTimeout(() => {
20011      this.removeClass('force-display');
20012    }, this.options_.initialDisplay);
20013  }
20014
20015  /**
20016   * Hide the display, even if during the `initialDisplay` time.
20017   */
20018  hide() {
20019    this.removeClass('force-display');
20020    super.hide();
20021  }
20022
20023  /**
20024   * Dispose the component
20025   */
20026  dispose() {
20027    this.player_.clearTimeout(this.forceDisplayTimeout);
20028    super.dispose();
20029  }
20030}
20031Component$1.registerComponent('TransientButton', TransientButton);
20032
20033/** @import Html5 from './html5' */
20034
20035/**
20036 * This function is used to fire a sourceset when there is something
20037 * similar to `mediaEl.load()` being called. It will try to find the source via
20038 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20039 * with the source that was found or empty string if we cannot know. If it cannot
20040 * find a source then `sourceset` will not be fired.
20041 *
20042 * @param {Html5} tech
20043 *        The tech object that sourceset was setup on
20044 *
20045 * @return {boolean}
20046 *         returns false if the sourceset was not fired and true otherwise.
20047 */
20048const sourcesetLoad = (tech) => {
20049  const el = tech.el();
20050
20051  // if `el.src` is set, that source will be loaded.
20052  if (el.hasAttribute('src')) {
20053    tech.triggerSourceset(el.src);
20054    return true;
20055  }
20056
20057  /**
20058   * Since there isn't a src property on the media element, source elements will be used for
20059   * implementing the source selection algorithm. This happens asynchronously and
20060   * for most cases were there is more than one source we cannot tell what source will
20061   * be loaded, without re-implementing the source selection algorithm. At this time we are not
20062   * going to do that. There are three special cases that we do handle here though:
20063   *
20064   * 1. If there are no sources, do not fire `sourceset`.
20065   * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20066   * 3. If there is more than one `<source>` but all of them have the same `src` url.
20067   *    That will be our src.
20068   */
20069  const sources = tech.$$('source');
20070  const srcUrls = [];
20071  let src = '';
20072
20073  // if there are no sources, do not fire sourceset
20074  if (!sources.length) {
20075    return false;
20076  }
20077
20078  // only count valid/non-duplicate source elements
20079  for (let i = 0; i < sources.length; i++) {
20080    const url = sources[i].src;
20081    if (url && srcUrls.indexOf(url) === -1) {
20082      srcUrls.push(url);
20083    }
20084  }
20085
20086  // there were no valid sources
20087  if (!srcUrls.length) {
20088    return false;
20089  }
20090
20091  // there is only one valid source element url
20092  // use that
20093  if (srcUrls.length === 1) {
20094    src = srcUrls[0];
20095  }
20096  tech.triggerSourceset(src);
20097  return true;
20098};
20099
20100/**
20101 * our implementation of an `innerHTML` descriptor for browsers
20102 * that do not have one.
20103 */
20104const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
20105  get() {
20106    return this.cloneNode(true).innerHTML;
20107  },
20108  set(v) {
20109    // make a dummy node to use innerHTML on
20110    const dummy = document__default['default'].createElement(this.nodeName.toLowerCase());
20111
20112    // set innerHTML to the value provided
20113    dummy.innerHTML = v;
20114
20115    // make a document fragment to hold the nodes from dummy
20116    const docFrag = document__default['default'].createDocumentFragment();
20117
20118    // copy all of the nodes created by the innerHTML on dummy
20119    // to the document fragment
20120    while (dummy.childNodes.length) {
20121      docFrag.appendChild(dummy.childNodes[0]);
20122    }
20123
20124    // remove content
20125    this.innerText = '';
20126
20127    // now we add all of that html in one by appending the
20128    // document fragment. This is how innerHTML does it.
20129    window__default['default'].Element.prototype.appendChild.call(this, docFrag);
20130
20131    // then return the result that innerHTML's setter would
20132    return this.innerHTML;
20133  },
20134});
20135
20136/**
20137 * Get a property descriptor given a list of priorities and the
20138 * property to get.
20139 */
20140const getDescriptor = (priority, prop) => {
20141  let descriptor = {};
20142  for (let i = 0; i < priority.length; i++) {
20143    descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
20144    if (descriptor && descriptor.set && descriptor.get) {
20145      break;
20146    }
20147  }
20148  descriptor.enumerable = true;
20149  descriptor.configurable = true;
20150  return descriptor;
20151};
20152const getInnerHTMLDescriptor = (tech) =>
20153  getDescriptor(
20154    [
20155      tech.el(),
20156      window__default['default'].HTMLMediaElement.prototype,
20157      window__default['default'].Element.prototype,
20158      innerHTMLDescriptorPolyfill,
20159    ],
20160    'innerHTML'
20161  );
20162
20163/**
20164 * Patches browser internal functions so that we can tell synchronously
20165 * if a `<source>` was appended to the media element. For some reason this
20166 * causes a `sourceset` if the the media element is ready and has no source.
20167 * This happens when:
20168 * - The page has just loaded and the media element does not have a source.
20169 * - The media element was emptied of all sources, then `load()` was called.
20170 *
20171 * It does this by patching the following functions/properties when they are supported:
20172 *
20173 * - `append()` - can be used to add a `<source>` element to the media element
20174 * - `appendChild()` - can be used to add a `<source>` element to the media element
20175 * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
20176 * - `innerHTML` -  can be used to add a `<source>` element to the media element
20177 *
20178 * @param {Html5} tech
20179 *        The tech object that sourceset is being setup on.
20180 */
20181const firstSourceWatch = function (tech) {
20182  const el = tech.el();
20183
20184  // make sure firstSourceWatch isn't setup twice.
20185  if (el.resetSourceWatch_) {
20186    return;
20187  }
20188  const old = {};
20189  const innerDescriptor = getInnerHTMLDescriptor(tech);
20190  const appendWrapper =
20191    (appendFn) =>
20192    (...args) => {
20193      const retval = appendFn.apply(el, args);
20194      sourcesetLoad(tech);
20195      return retval;
20196    };
20197  ['append', 'appendChild', 'insertAdjacentHTML'].forEach((k) => {
20198    if (!el[k]) {
20199      return;
20200    }
20201
20202    // store the old function
20203    old[k] = el[k];
20204
20205    // call the old function with a sourceset if a source
20206    // was loaded
20207    el[k] = appendWrapper(old[k]);
20208  });
20209  Object.defineProperty(
20210    el,
20211    'innerHTML',
20212    merge$1(innerDescriptor, {
20213      set: appendWrapper(innerDescriptor.set),
20214    })
20215  );
20216  el.resetSourceWatch_ = () => {
20217    el.resetSourceWatch_ = null;
20218    Object.keys(old).forEach((k) => {
20219      el[k] = old[k];
20220    });
20221    Object.defineProperty(el, 'innerHTML', innerDescriptor);
20222  };
20223
20224  // on the first sourceset, we need to revert our changes
20225  tech.one('sourceset', el.resetSourceWatch_);
20226};
20227
20228/**
20229 * our implementation of a `src` descriptor for browsers
20230 * that do not have one
20231 */
20232const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
20233  get() {
20234    if (this.hasAttribute('src')) {
20235      return getAbsoluteURL(
20236        window__default['default'].Element.prototype.getAttribute.call(this, 'src')
20237      );
20238    }
20239    return '';
20240  },
20241  set(v) {
20242    window__default['default'].Element.prototype.setAttribute.call(this, 'src', v);
20243    return v;
20244  },
20245});
20246const getSrcDescriptor = (tech) =>
20247  getDescriptor(
20248    [tech.el(), window__default['default'].HTMLMediaElement.prototype, srcDescriptorPolyfill],
20249    'src'
20250  );
20251
20252/**
20253 * setup `sourceset` handling on the `Html5` tech. This function
20254 * patches the following element properties/functions:
20255 *
20256 * - `src` - to determine when `src` is set
20257 * - `setAttribute()` - to determine when `src` is set
20258 * - `load()` - this re-triggers the source selection algorithm, and can
20259 *              cause a sourceset.
20260 *
20261 * If there is no source when we are adding `sourceset` support or during a `load()`
20262 * we also patch the functions listed in `firstSourceWatch`.
20263 *
20264 * @param {Html5} tech
20265 *        The tech to patch
20266 */
20267const setupSourceset = function (tech) {
20268  if (!tech.featuresSourceset) {
20269    return;
20270  }
20271  const el = tech.el();
20272
20273  // make sure sourceset isn't setup twice.
20274  if (el.resetSourceset_) {
20275    return;
20276  }
20277  const srcDescriptor = getSrcDescriptor(tech);
20278  const oldSetAttribute = el.setAttribute;
20279  const oldLoad = el.load;
20280  Object.defineProperty(
20281    el,
20282    'src',
20283    merge$1(srcDescriptor, {
20284      set: (v) => {
20285        const retval = srcDescriptor.set.call(el, v);
20286
20287        // we use the getter here to get the actual value set on src
20288        tech.triggerSourceset(el.src);
20289        return retval;
20290      },
20291    })
20292  );
20293  el.setAttribute = (n, v) => {
20294    const retval = oldSetAttribute.call(el, n, v);
20295    if (/src/i.test(n)) {
20296      tech.triggerSourceset(el.src);
20297    }
20298    return retval;
20299  };
20300  el.load = () => {
20301    const retval = oldLoad.call(el);
20302
20303    // if load was called, but there was no source to fire
20304    // sourceset on. We have to watch for a source append
20305    // as that can trigger a `sourceset` when the media element
20306    // has no source
20307    if (!sourcesetLoad(tech)) {
20308      tech.triggerSourceset('');
20309      firstSourceWatch(tech);
20310    }
20311    return retval;
20312  };
20313  if (el.currentSrc) {
20314    tech.triggerSourceset(el.currentSrc);
20315  } else if (!sourcesetLoad(tech)) {
20316    firstSourceWatch(tech);
20317  }
20318  el.resetSourceset_ = () => {
20319    el.resetSourceset_ = null;
20320    el.load = oldLoad;
20321    el.setAttribute = oldSetAttribute;
20322    Object.defineProperty(el, 'src', srcDescriptor);
20323    if (el.resetSourceWatch_) {
20324      el.resetSourceWatch_();
20325    }
20326  };
20327};
20328
20329/**
20330 * @file html5.js
20331 */
20332
20333/**
20334 * HTML5 Media Controller - Wrapper for HTML5 Media API
20335 *
20336 * @mixes Tech~SourceHandlerAdditions
20337 * @extends Tech
20338 */
20339class Html5 extends Tech {
20340  /**
20341   * Create an instance of this Tech.
20342   *
20343   * @param {Object} [options]
20344   *        The key/value store of player options.
20345   *
20346   * @param {Function} [ready]
20347   *        Callback function to call when the `HTML5` Tech is ready.
20348   */
20349  constructor(options, ready) {
20350    super(options, ready);
20351    const source = options.source;
20352    let crossoriginTracks = false;
20353    this.featuresVideoFrameCallback =
20354      this.featuresVideoFrameCallback && this.el_.tagName === 'VIDEO';
20355
20356    // Set the source if one is provided
20357    // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
20358    // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
20359    // anyway so the error gets fired.
20360    if (
20361      source &&
20362      (this.el_.currentSrc !== source.src || (options.tag && options.tag.
vendor: 6,602 bytes, lines 20362-20536
20362initNetworkState_ === 3))
20363    ) {
20364      this.setSource(source);
20365    } else {
20366      this.handleLateInit_(this.el_);
20367    }
20368
20369    // setup sourceset after late sourceset/init
20370    if (options.enableSourceset) {
20371      this.setupSourcesetHandling_();
20372    }
20373    this.isScrubbing_ = false;
20374    if (this.el_.hasChildNodes()) {
20375      const nodes = this.el_.childNodes;
20376      let nodesLength = nodes.length;
20377      const removeNodes = [];
20378      while (nodesLength--) {
20379        const node = nodes[nodesLength];
20380        const nodeName = node.nodeName.toLowerCase();
20381        if (nodeName === 'track') {
20382          if (!this.featuresNativeTextTracks) {
20383            // Empty video tag tracks so the built-in player doesn't use them also.
20384            // This may not be fast enough to stop HTML5 browsers from reading the tags
20385            // so we'll need to turn off any default tracks if we're manually doing
20386            // captions and subtitles. videoElement.textTracks
20387            removeNodes.push(node);
20388          } else {
20389            // store HTMLTrackElement and TextTrack to remote list
20390            this.remoteTextTrackEls().addTrackElement_(node);
20391            this.remoteTextTracks().addTrack(node.track);
20392            this.textTracks().addTrack(node.track);
20393            if (
20394              !crossoriginTracks &&
20395              !this.el_.hasAttribute('crossorigin') &&
20396              isCrossOrigin(node.src)
20397            ) {
20398              crossoriginTracks = true;
20399            }
20400          }
20401        }
20402      }
20403      for (let i = 0; i < removeNodes.length; i++) {
20404        this.el_.removeChild(removeNodes[i]);
20405      }
20406    }
20407    this.proxyNativeTracks_();
20408    if (this.featuresNativeTextTracks && crossoriginTracks) {
20409      log$1.warn(
20410        "Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.\n" +
20411          'This may prevent text tracks from loading.'
20412      );
20413    }
20414
20415    // prevent iOS Safari from disabling metadata text tracks during native playback
20416    this.restoreMetadataTracksInIOSNativePlayer_();
20417
20418    // Determine if native controls should be used
20419    // Our goal should be to get the custom controls on mobile solid everywhere
20420    // so we can remove this all together. Right now this will block custom
20421    // controls on touch enabled laptops like the Chrome Pixel
20422    if ((TOUCH_ENABLED || IS_IPHONE) && options.nativeControlsForTouch === true) {
20423      this.setControls(true);
20424    }
20425
20426    // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
20427    // into a `fullscreenchange` event
20428    this.proxyWebkitFullscreen_();
20429    this.triggerReady();
20430  }
20431
20432  /**
20433   * Dispose of `HTML5` media element and remove all tracks.
20434   */
20435  dispose() {
20436    if (this.el_ && this.el_.resetSourceset_) {
20437      this.el_.resetSourceset_();
20438    }
20439    Html5.disposeMediaElement(this.el_);
20440    this.options_ = null;
20441
20442    // tech will handle clearing of the emulated track list
20443    super.dispose();
20444  }
20445
20446  /**
20447   * Modify the media element so that we can detect when
20448   * the source is changed. Fires `sourceset` just after the source has changed
20449   */
20450  setupSourcesetHandling_() {
20451    setupSourceset(this);
20452  }
20453
20454  /**
20455   * When a captions track is enabled in the iOS Safari native player, all other
20456   * tracks are disabled (including metadata tracks), which nulls all of their
20457   * associated cue points. This will restore metadata tracks to their pre-fullscreen
20458   * state in those cases so that cue points are not needlessly lost.
20459   *
20460   * @private
20461   */
20462  restoreMetadataTracksInIOSNativePlayer_() {
20463    const textTracks = this.textTracks();
20464    let metadataTracksPreFullscreenState;
20465
20466    // captures a snapshot of every metadata track's current state
20467    const takeMetadataTrackSnapshot = () => {
20468      metadataTracksPreFullscreenState = [];
20469      for (let i = 0; i < textTracks.length; i++) {
20470        const track = textTracks[i];
20471        if (track.kind === 'metadata') {
20472          metadataTracksPreFullscreenState.push({
20473            track,
20474            storedMode: track.mode,
20475          });
20476        }
20477      }
20478    };
20479
20480    // snapshot each metadata track's initial state, and update the snapshot
20481    // each time there is a track 'change' event
20482    takeMetadataTrackSnapshot();
20483    textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20484    this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));
20485    const restoreTrackMode = () => {
20486      for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
20487        const storedTrack = metadataTracksPreFullscreenState[i];
20488        if (
20489          storedTrack.track.mode === 'disabled' &&
20490          storedTrack.track.mode !== storedTrack.storedMode
20491        ) {
20492          storedTrack.track.mode = storedTrack.storedMode;
20493        }
20494      }
20495      // we only want this handler to be executed on the first 'change' event
20496      textTracks.removeEventListener('change', restoreTrackMode);
20497    };
20498
20499    // when we enter fullscreen playback, stop updating the snapshot and
20500    // restore all track modes to their pre-fullscreen state
20501    this.on('webkitbeginfullscreen', () => {
20502      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20503
20504      // remove the listener before adding it just in case it wasn't previously removed
20505      textTracks.removeEventListener('change', restoreTrackMode);
20506      textTracks.addEventListener('change', restoreTrackMode);
20507    });
20508
20509    // start updating the snapshot again after leaving fullscreen
20510    this.on('webkitendfullscreen', () => {
20511      // remove the listener before adding it just in case it wasn't previously removed
20512      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20513      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20514
20515      // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
20516      textTracks.removeEventListener('change', restoreTrackMode);
20517    });
20518  }
20519
20520  /**
20521   * Attempt to force override of tracks for the given type
20522   *
20523   * @param {string} type - Track type to override, possible values include 'Audio',
20524   * 'Video', and 'Text'.
20525   * @param {boolean} override - If set to true native audio/video will be overridden,
20526   * otherwise native audio/video will potentially be used.
20527   * @private
20528   */
20529  overrideNative_(type, override) {
20530    // If there is no behavioral change don't add/remove listeners
20531    if (override !== this[`featuresNative${type}Tracks`]) {
20532      return;
20533    }
20534    const lowerCaseType = type.toLowerCase();
20535    if (this[`${lowerCaseType}TracksListeners_`]) {
20536      Object.keys(this[`${lowerCaseType}
20536TracksListeners_`]).forEach((eventName) => {
20537        const elTracks = this.el()[`${lowerCaseType}Tracks`];
20538        elTracks.removeEventListener(
20539          eventName,
20540          this[`${lowerCaseType}TracksListeners_`][eventName]
20541        );
20542      });
20543    }
20544    this[`featuresNative${type}Tracks`] = !override;
20545    this[`${lowerCaseType}TracksListeners_`] = null;
20546    this.proxyNativeTracksForType_(lowerCaseType);
20547  }
20548
20549  /**
20550   * Attempt to force override of native audio tracks.
20551   *
20552   * @param {boolean} override - If set to true native audio will be overridden,
20553   * otherwise native audio will potentially be used.
20554   */
20555  overrideNativeAudioTracks(override) {
20556    this.overrideNative_('Audio', override);
20557  }
20558
20559  /**
20560   * Attempt to force override of native video tracks.
20561   *
20562   * @param {boolean} override - If set to true native video will be overridden,
20563   * otherwise native video will potentially be used.
20564   */
20565  overrideNativeVideoTracks(override) {
20566    this.overrideNative_('Video', override);
20567  }
20568
20569  /**
20570   * Proxy native track list events for the given type to our track
20571   * lists if the browser we are playing in supports that type of track list.
20572   *
20573   * @param {string} name - Track type; values include 'audio', 'video', and 'text'
20574   * @private
20575   */
20576  proxyNativeTracksForType_(name) {
20577    const props = NORMAL[name];
20578    const elTracks = this.el()[props.getterName];
20579    const techTracks = this[props.getterName]();
20580    if (
20581      !this[`featuresNative${props.capitalName}Tracks`] ||
20582      !elTracks ||
20583      !elTracks.addEventListener
20584    ) {
20585      return;
20586    }
20587    const listeners = {
20588      change: (e) => {
20589        const event = {
20590          type: 'change',
20591          target: techTracks,
20592          currentTarget: techTracks,
20593          srcElement: techTracks,
20594        };
20595        techTracks.trigger(event);
20596
20597        // if we are a text track change event, we should also notify the
20598        // remote text track list. This can potentially cause a false positive
20599        // if we were to get a change event on a non-remote track and
20600        // we triggered the event on the remote text track list which doesn't
20601        // contain that track. However, best practices mean looping through the
20602        // list of tracks and searching for the appropriate mode value, so,
20603        // this shouldn't pose an issue
20604        if (name === 'text') {
20605          this[REMOTE.remoteText.getterName]().trigger(event);
20606        }
20607      },
20608      addtrack(e) {
20609        techTracks.addTrack(e.track);
20610      },
20611      removetrack(e) {
20612        techTracks.removeTrack(e.track);
20613      },
20614    };
20615    const removeOldTracks = function () {
20616      const removeTracks = [];
20617      for (let i = 0; i < techTracks.length; i++) {
20618        let found = false;
20619        for (let j = 0; j < elTracks.length; j++) {
20620          if (elTracks[j] === techTracks[i]) {
20621            found = true;
20622            break;
20623          }
20624        }
20625        if (!found) {
20626          removeTracks.push(techTracks[i]);
20627        }
20628      }
20629      while (removeTracks.length) {
20630        techTracks.removeTrack(removeTracks.shift());
20631      }
20632    };
20633    this[props.getterName + 'Listeners_'] = listeners;
20634    Object.keys(listeners).forEach((eventName) => {
20635      const listener = listeners[eventName];
20636      elTracks.addEventListener(eventName, listener);
20637      this.on('dispose', (e) => elTracks.removeEventListener(eventName, listener));
20638    });
20639
20640    // Remove (native) tracks that are not used anymore
20641    this.on('loadstart', removeOldTracks);
20642    this.on('dispose', (e) => this.off('loadstart', removeOldTracks));
20643  }
20644
20645  /**
20646   * Proxy all native track list events to our track lists if the browser we are playing
20647   * in supports that type of track list.
20648   *
20649   * @private
20650   */
20651  proxyNativeTracks_() {
20652    NORMAL.names.forEach((name) => {
20653      this.proxyNativeTracksForType_(name);
20654    });
20655  }
20656
20657  /**
20658   * Create the `Html5` Tech's DOM element.
20659   *
20660   * @return {Element}
20661   *         The element that gets created.
20662   */
20663  createEl() {
20664    let el = this.options_.tag;
20665
20666    // Check if this browser supports moving the element into the box.
20667    // On the iPhone video will break if you move the element,
20668    // So we have to create a brand new element.
20669    // If we ingested the player div, we do not need to move the media element.
20670    if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
20671      // If the original tag is still there, clone and remove it.
20672      if (el) {
20673        const clone = el.cloneNode(true);
20674        if (el.parentNode) {
20675          el.parentNode.insertBefore(clone, el);
20676        }
20677        Html5.disposeMediaElement(el);
20678        el = clone;
20679      } else {
20680        el = document__default['default'].createElement('video');
20681
20682        // determine if native controls should be used
20683        const tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
20684        const attributes = merge$1({}, tagAttributes);
20685        if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
20686          delete attributes.controls;
20687        }
20688        setAttributes(
20689          el,
20690          Object.assign(attributes, {
20691            id: this.options_.techId,
20692            class: 'vjs-tech',
20693          })
20694        );
20695      }
20696      el.playerId = this.options_.playerId;
20697    }
20698    if (typeof this.options_.preload !== 'undefined') {
20699      setAttribute(el, 'preload', this.options_.preload);
20700    }
20701    if (this.options_.disablePictureInPicture !== undefined) {
20702      el.disablePictureInPicture = this.options_.disablePictureInPicture;
20703    }
20704
20705    // Update specific tag settings, in case they were overridden
20706    // `autoplay` has to be *last* so that `muted` and `playsinline` are present
20707    // when iOS/Safari or other browsers attempt to autoplay.
20708    const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
20709    for (let i = 0; i < settingsAttrs.length; i++) {
20710      const attr = settingsAttrs[i];
20711      const value = this.options_[attr];
20712      if (typeof value !== 'undefined') {
20713        if (value) {
20714          setAttribute(el, attr, attr);
20715        } else {
20716          removeAttribute(el, attr);
20717        }
20718        el[attr] = value;
20719      }
20720    }
20721    return el;
20722  }
20723
20724  /**
20725   * This will be triggered if the loadstart event has already fired, before videojs was
20726   * ready. Two known examples of when this can happen are:
20727   * 1. If we're loading the playback object after it has started loading
20728   * 2. The media is already playing the (often with autoplay on) then
20729   *
20730   * This function will fire another loadstart so that videojs can catchup.
20731   *
20732   * @fires Tech#loadstart
20733   *
20734   * @return {undefined}
20735   *         returns nothing.
20736   */
20737  handleLateInit_(el) {
20738    if (el.networkState === 0 || el.networkState === 3) {
20739      // The video element hasn't started loading the source yet
20740      // or didn't find a source
20741      return;
20742    }
20743    if (el.readyState === 0) {
20744      // NetworkState is set synchronously BUT loadstart is fired at the
20745      // end of the current stack, usually before setInterval(fn, 0).
20746      // So at this point we know loadstart may have already fired or is
20747      // about to fire, and either way the player hasn't seen it yet.
20748      // We don't want to fire loadstart prematurely here and cause a
20749      // double loadstart so we'll wait and see if it happens between now
20750      // and the next loop, and fire it if not.
20751      // HOWEVER, we also want to make sure it fires before loadedmetadata
20752      // which could also happen between now and the next loop, so we'll
20753      // watch for that also.
20754      let loadstartFired = false;
vendor: 9,400 bytes, lines 20755-21068
20755      const setLoadstartFired = function () {
20756        loadstartFired = true;
20757      };
20758      this.on('loadstart', setLoadstartFired);
20759      const triggerLoadstart = function () {
20760        // We did miss the original loadstart. Make sure the player
20761        // sees loadstart before loadedmetadata
20762        if (!loadstartFired) {
20763          this.trigger('loadstart');
20764        }
20765      };
20766      this.on('loadedmetadata', triggerLoadstart);
20767      this.ready(function () {
20768        this.off('loadstart', setLoadstartFired);
20769        this.off('loadedmetadata', triggerLoadstart);
20770        if (!loadstartFired) {
20771          // We did miss the original native loadstart. Fire it now.
20772          this.trigger('loadstart');
20773        }
20774      });
20775      return;
20776    }
20777
20778    // From here on we know that loadstart already fired and we missed it.
20779    // The other readyState events aren't as much of a problem if we double
20780    // them, so not going to go to as much trouble as loadstart to prevent
20781    // that unless we find reason to.
20782    const eventsToTrigger = ['loadstart'];
20783
20784    // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
20785    eventsToTrigger.push('loadedmetadata');
20786
20787    // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
20788    if (el.readyState >= 2) {
20789      eventsToTrigger.push('loadeddata');
20790    }
20791
20792    // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
20793    if (el.readyState >= 3) {
20794      eventsToTrigger.push('canplay');
20795    }
20796
20797    // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
20798    if (el.readyState >= 4) {
20799      eventsToTrigger.push('canplaythrough');
20800    }
20801
20802    // We still need to give the player time to add event listeners
20803    this.ready(function () {
20804      eventsToTrigger.forEach(function (type) {
20805        this.trigger(type);
20806      }, this);
20807    });
20808  }
20809
20810  /**
20811   * Set whether we are scrubbing or not.
20812   * This is used to decide whether we should use `fastSeek` or not.
20813   * `fastSeek` is used to provide trick play on Safari browsers.
20814   *
20815   * @param {boolean} isScrubbing
20816   *                  - true for we are currently scrubbing
20817   *                  - false for we are no longer scrubbing
20818   */
20819  setScrubbing(isScrubbing) {
20820    this.isScrubbing_ = isScrubbing;
20821  }
20822
20823  /**
20824   * Get whether we are scrubbing or not.
20825   *
20826   * @return {boolean} isScrubbing
20827   *                  - true for we are currently scrubbing
20828   *                  - false for we are no longer scrubbing
20829   */
20830  scrubbing() {
20831    return this.isScrubbing_;
20832  }
20833
20834  /**
20835   * Set current time for the `HTML5` tech.
20836   *
20837   * @param {number} seconds
20838   *        Set the current time of the media to this.
20839   */
20840  setCurrentTime(seconds) {
20841    try {
20842      if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
20843        this.el_.fastSeek(seconds);
20844      } else {
20845        this.el_.currentTime = seconds;
20846      }
20847    } catch (e) {
20848      log$1(e, 'Video is not ready. (Video.js)');
20849      // this.warning(VideoJS.warnings.videoNotReady);
20850    }
20851  }
20852
20853  /**
20854   * Get the current duration of the HTML5 media element.
20855   *
20856   * @return {number}
20857   *         The duration of the media or 0 if there is no duration.
20858   */
20859  duration() {
20860    // Android Chrome will report duration as Infinity for VOD HLS until after
20861    // playback has started, which triggers the live display erroneously.
20862    // Return NaN if playback has not started and trigger a durationupdate once
20863    // the duration can be reliably known.
20864    if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
20865      // Wait for the first `timeupdate` with currentTime > 0 - there may be
20866      // several with 0
20867      const checkProgress = () => {
20868        if (this.el_.currentTime > 0) {
20869          // Trigger durationchange for genuinely live video
20870          if (this.el_.duration === Infinity) {
20871            this.trigger('durationchange');
20872          }
20873          this.off('timeupdate', checkProgress);
20874        }
20875      };
20876      this.on('timeupdate', checkProgress);
20877      return NaN;
20878    }
20879    return this.el_.duration || NaN;
20880  }
20881
20882  /**
20883   * Get the current width of the HTML5 media element.
20884   *
20885   * @return {number}
20886   *         The width of the HTML5 media element.
20887   */
20888  width() {
20889    return this.el_.offsetWidth;
20890  }
20891
20892  /**
20893   * Get the current height of the HTML5 media element.
20894   *
20895   * @return {number}
20896   *         The height of the HTML5 media element.
20897   */
20898  height() {
20899    return this.el_.offsetHeight;
20900  }
20901
20902  /**
20903   * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
20904   * `fullscreenchange` event.
20905   *
20906   * @private
20907   * @fires fullscreenchange
20908   * @listens webkitendfullscreen
20909   * @listens webkitbeginfullscreen
20910   * @listens webkitbeginfullscreen
20911   */
20912  proxyWebkitFullscreen_() {
20913    if (!('webkitDisplayingFullscreen' in this.el_)) {
20914      return;
20915    }
20916    const endFn = function () {
20917      this.trigger('fullscreenchange', {
20918        isFullscreen: false,
20919      });
20920      // Safari will sometimes set controls on the videoelement when existing fullscreen.
20921      if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
20922        this.el_.controls = false;
20923      }
20924    };
20925    const beginFn = function () {
20926      if (
20927        'webkitPresentationMode' in this.el_ &&
20928        this.el_.webkitPresentationMode !== 'picture-in-picture'
20929      ) {
20930        this.one('webkitendfullscreen', endFn);
20931        this.trigger('fullscreenchange', {
20932          isFullscreen: true,
20933          // set a flag in case another tech triggers fullscreenchange
20934          nativeIOSFullscreen: true,
20935        });
20936      }
20937    };
20938    this.on('webkitbeginfullscreen', beginFn);
20939    this.on('dispose', () => {
20940      this.off('webkitbeginfullscreen', beginFn);
20941      this.off('webkitendfullscreen', endFn);
20942    });
20943  }
20944
20945  /**
20946   * Check if fullscreen is supported on the video el.
20947   *
20948   * @return {boolean}
20949   *         - True if fullscreen is supported.
20950   *         - False if fullscreen is not supported.
20951   */
20952  supportsFullScreen() {
20953    return typeof this.el_.webkitEnterFullScreen === 'function';
20954  }
20955
20956  /**
20957   * Request that the `HTML5` Tech enter fullscreen.
20958   */
20959  enterFullScreen() {
20960    const video = this.el_;
20961    if (video.paused && video.networkState <= video.HAVE_METADATA) {
20962      // attempt to prime the video element for programmatic access
20963      // this isn't necessary on the desktop but shouldn't hurt
20964      silencePromise(this.el_.play());
20965
20966      // playing and pausing synchronously during the transition to fullscreen
20967      // can get iOS ~6.1 devices into a play/pause loop
20968      this.setTimeout(function () {
20969        video.pause();
20970        try {
20971          video.webkitEnterFullScreen();
20972        } catch (e) {
20973          this.trigger('fullscreenerror', e);
20974        }
20975      }, 0);
20976    } else {
20977      try {
20978        video.webkitEnterFullScreen();
20979      } catch (e) {
20980        this.trigger('fullscreenerror', e);
20981      }
20982    }
20983  }
20984
20985  /**
20986   * Request that the `HTML5` Tech exit fullscreen.
20987   */
20988  exitFullScreen() {
20989    if (!this.el_.webkitDisplayingFullscreen) {
20990      this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
20991      return;
20992    }
20993    this.el_.webkitExitFullScreen();
20994  }
20995
20996  /**
20997   * Create a floating video window always on top of other windows so that users may
20998   * continue consuming media while they interact with other content sites, or
20999   * applications on their device.
21000   *
21001   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
21002   *
21003   * @return {Promise}
21004   *         A promise with a Picture-in-Picture window.
21005   */
21006  requestPictureInPicture() {
21007    return this.el_.requestPictureInPicture();
21008  }
21009
21010  /**
21011   * Native requestVideoFrameCallback if supported by browser/tech, or fallback
21012   * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
21013   * Needs to be checked later than the constructor
21014   * This will be a false positive for clear sources loaded after a Fairplay source
21015   *
21016   * @param {function} cb function to call
21017   * @return {number} id of request
21018   */
21019  requestVideoFrameCallback(cb) {
21020    if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
21021      return this.el_.requestVideoFrameCallback(cb);
21022    }
21023    return super.requestVideoFrameCallback(cb);
21024  }
21025
21026  /**
21027   * Native or fallback requestVideoFrameCallback
21028   *
21029   * @param {number} id request id to cancel
21030   */
21031  cancelVideoFrameCallback(id) {
21032    if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
21033      this.el_.cancelVideoFrameCallback(id);
21034    } else {
21035      super.cancelVideoFrameCallback(id);
21036    }
21037  }
21038
21039  /**
21040   * A getter/setter for the `Html5` Tech's source object.
21041   * > Note: Please use {@link Html5#setSource}
21042   *
21043   * @param {Tech~SourceObject} [src]
21044   *        The source object you want to set on the `HTML5` techs element.
21045   *
21046   * @return {Tech~SourceObject|undefined}
21047   *         - The current source object when a source is not passed in.
21048   *         - undefined when setting
21049   *
21050   * @deprecated Since version 5.
21051   */
21052  src(src) {
21053    if (src === undefined) {
21054      return this.el_.src;
21055    }
21056
21057    // Setting src through `src` instead of `setSrc` will be deprecated
21058    this.setSrc(src);
21059  }
21060
21061  /**
21062   * Add a <source> element to the <video> element.
21063   *
21064   * @param {string} srcUrl
21065   *        The URL of the video source.
21066   *
21067   * @param {string} [mimeType]
21068   *        The MIME type of the video source. Optional but recommended.
vendor: 10,748 bytes, lines 21069-21432
21069   *
21070   * @return {boolean}
21071   *         Returns true if the source element was successfully added, false otherwise.
21072   */
21073  addSourceElement(srcUrl, mimeType) {
21074    if (!srcUrl) {
21075      log$1.error('Invalid source URL.');
21076      return false;
21077    }
21078    const sourceAttributes = {
21079      src: srcUrl,
21080    };
21081    if (mimeType) {
21082      sourceAttributes.type = mimeType;
21083    }
21084    const sourceElement = createEl('source', {}, sourceAttributes);
21085    this.el_.appendChild(sourceElement);
21086    return true;
21087  }
21088
21089  /**
21090   * Remove a <source> element from the <video> element by its URL.
21091   *
21092   * @param {string} srcUrl
21093   *        The URL of the source to remove.
21094   *
21095   * @return {boolean}
21096   *         Returns true if the source element was successfully removed, false otherwise.
21097   */
21098  removeSourceElement(srcUrl) {
21099    if (!srcUrl) {
21100      log$1.error('Source URL is required to remove the source element.');
21101      return false;
21102    }
21103    const sourceElements = this.el_.querySelectorAll('source');
21104    for (const sourceElement of sourceElements) {
21105      if (sourceElement.src === srcUrl) {
21106        this.el_.removeChild(sourceElement);
21107        return true;
21108      }
21109    }
21110    log$1.warn(`No matching source element found with src: ${srcUrl}`);
21111    return false;
21112  }
21113
21114  /**
21115   * Reset the tech by removing all sources and then calling
21116   * {@link Html5.resetMediaElement}.
21117   */
21118  reset() {
21119    Html5.resetMediaElement(this.el_);
21120  }
21121
21122  /**
21123   * Get the current source on the `HTML5` Tech. Falls back to returning the source from
21124   * the HTML5 media element.
21125   *
21126   * @return {Tech~SourceObject}
21127   *         The current source object from the HTML5 tech. With a fallback to the
21128   *         elements source.
21129   */
21130  currentSrc() {
21131    if (this.currentSource_) {
21132      return this.currentSource_.src;
21133    }
21134    return this.el_.currentSrc;
21135  }
21136
21137  /**
21138   * Set controls attribute for the HTML5 media Element.
21139   *
21140   * @param {string} val
21141   *        Value to set the controls attribute to
21142   */
21143  setControls(val) {
21144    this.el_.controls = !!val;
21145  }
21146
21147  /**
21148   * Create and returns a remote {@link TextTrack} object.
21149   *
21150   * @param {string} kind
21151   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
21152   *
21153   * @param {string} [label]
21154   *        Label to identify the text track
21155   *
21156   * @param {string} [language]
21157   *        Two letter language abbreviation
21158   *
21159   * @return {TextTrack}
21160   *         The TextTrack that gets created.
21161   */
21162  addTextTrack(kind, label, language) {
21163    if (!this.featuresNativeTextTracks) {
21164      return super.addTextTrack(kind, label, language);
21165    }
21166    return this.el_.addTextTrack(kind, label, language);
21167  }
21168
21169  /**
21170   * Creates either native TextTrack or an emulated TextTrack depending
21171   * on the value of `featuresNativeTextTracks`
21172   *
21173   * @param {Object} options
21174   *        The object should contain the options to initialize the TextTrack with.
21175   *
21176   * @param {string} [options.kind]
21177   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
21178   *
21179   * @param {string} [options.label]
21180   *        Label to identify the text track
21181   *
21182   * @param {string} [options.language]
21183   *        Two letter language abbreviation.
21184   *
21185   * @param {boolean} [options.default]
21186   *        Default this track to on.
21187   *
21188   * @param {string} [options.id]
21189   *        The internal id to assign this track.
21190   *
21191   * @param {string} [options.src]
21192   *        A source url for the track.
21193   *
21194   * @return {HTMLTrackElement}
21195   *         The track element that gets created.
21196   */
21197  createRemoteTextTrack(options) {
21198    if (!this.featuresNativeTextTracks) {
21199      return super.createRemoteTextTrack(options);
21200    }
21201    const htmlTrackElement = document__default['default'].createElement('track');
21202    if (options.kind) {
21203      htmlTrackElement.kind = options.kind;
21204    }
21205    if (options.label) {
21206      htmlTrackElement.label = options.label;
21207    }
21208    if (options.language || options.srclang) {
21209      htmlTrackElement.srclang = options.language || options.srclang;
21210    }
21211    if (options.default) {
21212      htmlTrackElement.default = options.default;
21213    }
21214    if (options.id) {
21215      htmlTrackElement.id = options.id;
21216    }
21217    if (options.src) {
21218      htmlTrackElement.src = options.src;
21219    }
21220    return htmlTrackElement;
21221  }
21222
21223  /**
21224   * Creates a remote text track object and returns an html track element.
21225   *
21226   * @param {Object} options The object should contain values for
21227   * kind, language, label, and src (location of the WebVTT file)
21228   * @param {boolean} [manualCleanup=false] if set to true, the TextTrack
21229   * will not be removed from the TextTrackList and HtmlTrackElementList
21230   * after a source change
21231   * @return {HTMLTrackElement} An Html Track Element.
21232   * This can be an emulated {@link HTMLTrackElement} or a native one.
21233   *
21234   */
21235  addRemoteTextTrack(options, manualCleanup) {
21236    const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
21237    if (this.featuresNativeTextTracks) {
21238      this.el().appendChild(htmlTrackElement);
21239    }
21240    return htmlTrackElement;
21241  }
21242
21243  /**
21244   * Remove remote `TextTrack` from `TextTrackList` object
21245   *
21246   * @param {TextTrack} track
21247   *        `TextTrack` object to remove
21248   */
21249  removeRemoteTextTrack(track) {
21250    super.removeRemoteTextTrack(track);
21251    if (this.featuresNativeTextTracks) {
21252      const tracks = this.$$('track');
21253      let i = tracks.length;
21254      while (i--) {
21255        if (track === tracks[i] || track === tracks[i].track) {
21256          this.el().removeChild(tracks[i]);
21257        }
21258      }
21259    }
21260  }
21261
21262  /**
21263   * Gets available media playback quality metrics as specified by the W3C's Media
21264   * Playback Quality API.
21265   *
21266   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
21267   *
21268   * @return {Object}
21269   *         An object with supported media playback quality metrics
21270   */
21271  getVideoPlaybackQuality() {
21272    if (typeof this.el().getVideoPlaybackQuality === 'function') {
21273      return this.el().getVideoPlaybackQuality();
21274    }
21275    const videoPlaybackQuality = {};
21276    if (
21277      typeof this.el().webkitDroppedFrameCount !== 'undefined' &&
21278      typeof this.el().webkitDecodedFrameCount !== 'undefined'
21279    ) {
21280      videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
21281      videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
21282    }
21283    if (window__default['default'].performance) {
21284      videoPlaybackQuality.creationTime = window__default['default'].performance.now();
21285    }
21286    return videoPlaybackQuality;
21287  }
21288}
21289
21290/* HTML5 Support Testing ---------------------------------------------------- */
21291
21292/**
21293 * Element for testing browser HTML5 media capabilities
21294 *
21295 * @type {Element}
21296 * @constant
21297 * @private
21298 */
21299defineLazyProperty(Html5, 'TEST_VID', function () {
21300  if (!isReal()) {
21301    return;
21302  }
21303  const video = document__default['default'].createElement('video');
21304  const track = document__default['default'].createElement('track');
21305  track.kind = 'captions';
21306  track.srclang = 'en';
21307  track.label = 'English';
21308  video.appendChild(track);
21309  return video;
21310});
21311
21312/**
21313 * Check if HTML5 media is supported by this browser/device.
21314 *
21315 * @return {boolean}
21316 *         - True if HTML5 media is supported.
21317 *         - False if HTML5 media is not supported.
21318 */
21319Html5.isSupported = function () {
21320  // IE with no Media Player is a LIAR! (#984)
21321  try {
21322    Html5.TEST_VID.volume = 0.5;
21323  } catch (e) {
21324    return false;
21325  }
21326  return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
21327};
21328
21329/**
21330 * Check if the tech can support the given type
21331 *
21332 * @param {string} type
21333 *        The mimetype to check
21334 * @return {string} 'probably', 'maybe', or '' (empty string)
21335 */
21336Html5.canPlayType = function (type) {
21337  return Html5.TEST_VID.canPlayType(type);
21338};
21339
21340/**
21341 * Check if the tech can support the given source
21342 *
21343 * @param {Object} srcObj
21344 *        The source object
21345 * @param {Object} options
21346 *        The options passed to the tech
21347 * @return {string} 'probably', 'maybe', or '' (empty string)
21348 */
21349Html5.canPlaySource = function (srcObj, options) {
21350  return Html5.canPlayType(srcObj.type);
21351};
21352
21353/**
21354 * Check if the volume can be changed in this browser/device.
21355 * Volume cannot be changed in a lot of mobile devices.
21356 * Specifically, it can't be changed from 1 on iOS.
21357 *
21358 * @return {boolean}
21359 *         - True if volume can be controlled
21360 *         - False otherwise
21361 */
21362Html5.canControlVolume = function () {
21363  // IE will error if Windows Media Player not installed #3315
21364  try {
21365    const volume = Html5.TEST_VID.volume;
21366    Html5.TEST_VID.volume = volume / 2 + 0.1;
21367    const canControl = volume !== Html5.TEST_VID.volume;
21368
21369    // With the introduction of iOS 15, there are cases where the volume is read as
21370    // changed but reverts back to its original state at the start of the next tick.
21371    // To determine whether volume can be controlled on iOS,
21372    // a timeout is set and the volume is checked asynchronously.
21373    // Since `features` doesn't currently work asynchronously, the value is manually set.
21374    if (canControl && IS_IOS) {
21375      window__default['default'].setTimeout(() => {
21376        if (Html5 && Html5.prototype) {
21377          Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
21378        }
21379      });
21380
21381      // default iOS to false, which will be updated in the timeout above.
21382      return false;
21383    }
21384    return canControl;
21385  } catch (e) {
21386    return false;
21387  }
21388};
21389
21390/**
21391 * Check if the volume can be muted in this browser/device.
21392 * Some devices, e.g. iOS, don't allow changing volume
21393 * but permits muting/unmuting.
21394 *
21395 * @return {boolean}
21396 *      - True if volume can be muted
21397 *      - False otherwise
21398 */
21399Html5.canMuteVolume = function () {
21400  try {
21401    const muted = Html5.TEST_VID.muted;
21402
21403    // in some versions of iOS muted property doesn't always
21404    // work, so we want to set both property and attribute
21405    Html5.TEST_VID.muted = !muted;
21406    if (Html5.TEST_VID.muted) {
21407      setAttribute(Html5.TEST_VID, 'muted', 'muted');
21408    } else {
21409      removeAttribute(Html5.TEST_VID, 'muted', 'muted');
21410    }
21411    return muted !== Html5.TEST_VID.muted;
21412  } catch (e) {
21413    return false;
21414  }
21415};
21416
21417/**
21418 * Check if the playback rate can be changed in this browser/device.
21419 *
21420 * @return {boolean}
21421 *         - True if playback rate can be controlled
21422 *         - False otherwise
21423 */
21424Html5.canControlPlaybackRate = function () {
21425  // Playback rate API is implemented in Android Chrome, but doesn't do anything
21426  // https://github.com/videojs/video.js/issues/3180
21427  if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
21428    return false;
21429  }
21430  // IE will error if Windows Media Player not installed #3315
21431  try {
21432    const playbackRate = Html5.TEST_VID.playbackRate;
21433    Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
21434    return playbackRate !== Html5.TEST_VID.playbackRate;
21435  } catch (e) {
21436    return false;
21437  }
21438};
21439
21440/**
21441 * Check if we can override a video/audio elements attributes, with
21442 * Object.defineProperty.
21443 *
21444 * @return {boolean}
21445 *         - True if builtin attributes can be overridden
21446 *         - False otherwise
21447 */
21448Html5.canOverrideAttributes = function () {
21449  // if we cannot overwrite the src/innerHTML property, there is no support
21450  // iOS 7 safari for instance cannot do this.
21451  try {
21452    const noop = () => {};
21453    Object.defineProperty(document__default['default'].createElement('video'), 'src', {
21454      get: noop,
21455      set: noop,
21456    });
21457    Object.defineProperty(document__default['default'].createElement('audio'), 'src', {
21458      get: noop,
21459      set: noop,
21460    });
21461    Object.defineProperty(document__default['default'].createElement('video'), 'innerHTML', {
21462      get: noop,
21463      set: noop,
21464    });
21465    Object.defineProperty(document__default['default'].createElement('audio'), 'innerHTML', {
21466      get: noop,
21467      set: noop,
21468    });
21469  } catch (e) {
21470    return false;
21471  }
21472  return true;
21473};
21474
21475/**
21476 * Check to see if native `TextTrack`s are supported by this browser/device.
21477 *
21478 * @return {boolean}
21479 *         - True if native `TextTrack`s are supported.
21480 *         - False otherwise
21481 */
21482Html5.supportsNativeTextTracks = function () {
21483  return IS_ANY_SAFARI || (IS_IOS && IS_CHROME);
21484};
21485
21486/**
21487 * Check to see if native `VideoTrack`s are supported by this browser/device
21488 *
21489 * @return {boolean}
21490 *        - True if native `VideoTrack`s are supported.
21491 *        - False otherwise
21492 */
21493Html5.supportsNativeVideoTracks = function () {
21494  return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
21495};
21496
21497/**
21498 * Check to see if native `AudioTrack`s are supported by this browser/device
21499 *
21500 * @return {boolean}
21501 *        - True if native `AudioTrack`s are supported.
21502 *        - False otherwise
21503 */
21504Html5.supportsNativeAudioTracks = function () {
21505  return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
21506};
21507
21508/**
21509 * An array of events available on the Html5 tech.
21510 *
21511 * @private
21512 * @type {Array}
21513 */
21514Html5.Events = [
21515  'loadstart',
21516  'suspend',
21517  'abort',
21518  'error',
21519  'emptied',
21520  'stalled',
21521  'loadedmetadata',
21522  'loadeddata',
21523  'canplay',
21524  'canplaythrough',
21525  'playing',
21526  'waiting',
21527  'seeking',
21528  'seeked',
21529  'ended',
21530  'durationchange',
21531  'timeupdate',
21532  'progress',
21533  'play',
21534  'pause',
21535  'ratechange',
21536  'resize',
21537  'volumechange',
21538];
21539
21540/**
21541 * Boolean indicating whether the `Tech` supports volume control.
21542 *
21543 * @type {boolean}
21544 * @default {@link Html5.canControlVolume}
21545 */
21546/**
21547 * Boolean indicating whether the `Tech` supports muting volume.
21548 *
21549 * @type {boolean}
21550 * @default {@link Html5.canMuteVolume}
21551 */
21552
21553/**
21554 * Boolean indicating whether the `Tech` supports changing the speed at which the media
21555 * plays. Examples:
21556 *   - Set player to play 2x (twice) as fast
21557 *   - Set player to play 0.5x (half) as fast
21558 *
21559 * @type {boolean}
21560 * @default {@link Html5.canControlPlaybackRate}
21561 */
21562
21563/**
21564 * Boolean indicating whether the `Tech` supports the `sourceset` event.
21565 *
21566 * @type {boolean}
21567 * @default
21568 */
21569/**
21570 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
21571 *
21572 * @type {boolean}
21573 * @default {@link Html5.supportsNativeTextTracks}
21574 */
21575/**
21576 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
21577 *
21578 * @type {boolean}
21579 * @default {@link Html5.supportsNativeVideoTracks}
21580 */
21581/**
21582 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
21583 *
21584 * @type {boolean}
21585 * @default {@link Html5.supportsNativeAudioTracks}
21586 */
21587[
21588  ['featuresMuteControl', 'canMuteVolume'],
21589  ['featuresPlaybackRate', 'canControlPlaybackRate'],
21590  ['featuresSourceset', 'canOverrideAttributes'],
21591  ['featuresNativeTextTracks', 'supportsNativeTextTracks'],
21592  ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'],
21593  ['featuresNativeAudioTracks', 'supportsNativeAudioTracks'],
21594].forEach(function ([key, fn]) {
21595  defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
21596});
21597Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
21598
21599/**
21600 * Boolean indicating whether the `HTML5` tech currently supports the media element
21601 * moving in the DOM. iOS breaks if you move the media element, so this is set this to
21602 * false there. Everywhere else this should be true.
21603 *
21604 * @type {boolean}
21605 * @default
21606 */
21607Html5.prototype.movingMediaElementInDOM = !IS_IOS;
21608
21609// TODO: Previous comment: No longer appears to be used. Can probably be removed.
21610//       Is this true?
21611/**
21612 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
21613 * when going into fullscreen.
21614 *
21615 * @type {boolean}
21616 * @default
21617 */
21618Html5.prototype.featuresFullscreenResize = true;
21619
21620/**
21621 * Boolean indicating whether the `HTML5` tech currently supports the progress event.
21622 * If this is false, manual `progress` events will be triggered instead.
21623 *
21624 * @type {boolean}
21625 * @default
21626 */
21627Html5.prototype.featuresProgressEvents = true;
21628
21629/**
21630 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
21631 * If this is false, manual `timeupdate` events will be triggered instead.
21632 *
21633 * @default
21634 */
21635Html5.prototype.featuresTimeupdateEvents = true;
21636
21637/**
21638 * Whether the HTML5 el supports `requestVideoFrameCallback`
21639 *
21640 * @type {boolean}
21641 */
21642Html5.prototype.featuresVideoFrameCallback = !!(
vendor: 5,248 bytes, lines 21643-21788
21643  Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback
21644);
21645Html5.disposeMediaElement = function (el) {
21646  if (!el) {
21647    return;
21648  }
21649  if (el.parentNode) {
21650    el.parentNode.removeChild(el);
21651  }
21652
21653  // remove any child track or source nodes to prevent their loading
21654  while (el.hasChildNodes()) {
21655    el.removeChild(el.firstChild);
21656  }
21657
21658  // remove any src reference. not setting `src=''` because that causes a warning
21659  // in firefox
21660  el.removeAttribute('src');
21661
21662  // force the media element to update its loading state by calling load()
21663  // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
21664  if (typeof el.load === 'function') {
21665    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21666    (function () {
21667      try {
21668        el.load();
21669      } catch (e) {
21670        // not supported
21671      }
21672    })();
21673  }
21674};
21675Html5.resetMediaElement = function (el) {
21676  if (!el) {
21677    return;
21678  }
21679  const sources = el.querySelectorAll('source');
21680  let i = sources.length;
21681  while (i--) {
21682    el.removeChild(sources[i]);
21683  }
21684
21685  // remove any src reference.
21686  // not setting `src=''` because that throws an error
21687  el.removeAttribute('src');
21688  if (typeof el.load === 'function') {
21689    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21690    (function () {
21691      try {
21692        el.load();
21693      } catch (e) {
21694        // satisfy linter
21695      }
21696    })();
21697  }
21698};
21699
21700/* Native HTML5 element property wrapping ----------------------------------- */
21701// Wrap native boolean attributes with getters that check both property and attribute
21702// The list is as followed:
21703// muted, defaultMuted, autoplay, controls, loop, playsinline
21704[
21705  /**
21706   * Get the value of `muted` from the media element. `muted` indicates
21707   * that the volume for the media should be set to silent. This does not actually change
21708   * the `volume` attribute.
21709   *
21710   * @method Html5#muted
21711   * @return {boolean}
21712   *         - True if the value of `volume` should be ignored and the audio set to silent.
21713   *         - False if the value of `volume` should be used.
21714   *
21715   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21716   */
21717  'muted',
21718  /**
21719   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
21720   * whether the media should start muted or not. Only changes the default state of the
21721   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
21722   * current state.
21723   *
21724   * @method Html5#defaultMuted
21725   * @return {boolean}
21726   *         - The value of `defaultMuted` from the media element.
21727   *         - True indicates that the media should start muted.
21728   *         - False indicates that the media should not start muted
21729   *
21730   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21731   */
21732  'defaultMuted',
21733  /**
21734   * Get the value of `autoplay` from the media element. `autoplay` indicates
21735   * that the media should start to play as soon as the page is ready.
21736   *
21737   * @method Html5#autoplay
21738   * @return {boolean}
21739   *         - The value of `autoplay` from the media element.
21740   *         - True indicates that the media should start as soon as the page loads.
21741   *         - False indicates that the media should not start as soon as the page loads.
21742   *
21743   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21744   */
21745  'autoplay',
21746  /**
21747   * Get the value of `controls` from the media element. `controls` indicates
21748   * whether the native media controls should be shown or hidden.
21749   *
21750   * @method Html5#controls
21751   * @return {boolean}
21752   *         - The value of `controls` from the media element.
21753   *         - True indicates that native controls should be showing.
21754   *         - False indicates that native controls should be hidden.
21755   *
21756   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
21757   */
21758  'controls',
21759  /**
21760   * Get the value of `loop` from the media element. `loop` indicates
21761   * that the media should return to the start of the media and continue playing once
21762   * it reaches the end.
21763   *
21764   * @method Html5#loop
21765   * @return {boolean}
21766   *         - The value of `loop` from the media element.
21767   *         - True indicates that playback should seek back to start once
21768   *           the end of a media is reached.
21769   *         - False indicates that playback should not loop back to the start when the
21770   *           end of the media is reached.
21771   *
21772   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21773   */
21774  'loop',
21775  /**
21776   * Get the value of `playsinline` from the media element. `playsinline` indicates
21777   * to the browser that non-fullscreen playback is preferred when fullscreen
21778   * playback is the native default, such as in iOS Safari.
21779   *
21780   * @method Html5#playsinline
21781   * @return {boolean}
21782   *         - The value of `playsinline` from the media element.
21783   *         - True indicates that the media should play inline.
21784   *         - False indicates that the media should not play inline.
21785   *
21786   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21787   */
21788  'playsinline',
21789].forEach(function (prop) {
21790  Html5.prototype[prop] = function () {
21791    return this.el_[prop] || this.el_.hasAttribute(prop);
21792  };
21793});
21794
21795// Wrap native boolean attributes with setters that set both property and attribute
21796// The list is as followed:
21797// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
21798// setControls is special-cased above
21799[
21800  /**
21801   * Set the value of `muted` on the media element. `muted` indicates that the current
21802   * audio level should be silent.
21803   *
21804   * @method Html5#setMuted
21805   * @param {boolean} muted
21806   *        - True if the audio should be set to silent
21807   *        - False otherwise
21808   *
21809   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21810   */
21811  'muted',
21812  /**
21813   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
21814   * audio level should be silent, but will only effect the muted level on initial playback..
21815   *
21816   * @method Html5.prototype.setDefaultMuted
21817   * @param {boolean} defaultMuted
21818   *        - True if the audio should be set to silent
21819   *        - False otherwise
21820   *
21821   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21822   */
21823  'defaultMuted',
21824  /**
21825   * Set the value of `autoplay` on the media element. `autoplay` indicates
21826   * that the media should start to play as soon as the page is ready.
21827   *
21828   * @method Html5#setAutoplay
21829   * @param {boolean} autoplay
21830   *         - True indicates that the media should start as soon as the page loads.
21831   *         - False indicates that the media should not start as soon as the page loads.
21832   *
21833   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21834   */
21835  'autoplay',
21836  /**
21837   * Set the value of `loop` on the media element. `loop` indicates
21838   * that the media should return to the start of the media and continue playing once
21839   * it reaches the end.
21840   *
21841   * @method Html5#setLoop
21842   * @param {boolean} loop
21843   *         - True indicates that playback should seek back to start once
21844   *           the end of a media is reached.
21845   *         - False indicates that playback should not loop back to the start when the
21846   *           end of the media is reached.
21847   *
21848   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21849   */
21850  'loop',
21851  /**
21852   * Set the value of `playsinline` from the media element. `playsinline` indicates
21853   * to the browser that non-fullscreen playback is preferred when fullscreen
21854   * playback is the native default, such as in iOS Safari.
21855   *
21856   * @method Html5#setPlaysinline
21857   * @param {boolean} playsinline
21858   *         - True indicates that the media should play inline.
21859   *         - False indicates that the media should not play inline.
21860   *
21861   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21862   */
21863  'playsinline',
21864].forEach(function (prop) {
21865  Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
21866    this.el_[prop] = v;
21867    if (v) {
21868      this.el_.setAttribute(prop, prop);
21869    } else {
21870      this.el_.removeAttribute(prop);
21871    }
21872  };
21873});
21874
21875// Wrap native properties with a getter
21876// The list is as followed
21877// paused, currentTime, buffered, volume, poster, preload, error, seeking
21878// seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
21879// played, networkState, readyState, videoWidth, videoHeight, crossOrigin
21880[
21881  /**
21882   * Get the value of `paused` from the media element. `paused` indicates whether the media element
21883   * is currently paused or not.
21884   *
21885   * @method Html5#paused
21886   * @return {boolean}
21887   *         The value of `paused` from the media element.
21888   *
21889   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
21890   */
21891  'paused',
21892  /**
21893   * Get the value of `currentTime` from the media element. `currentTime` indicates
21894   * the current second that the media is at in playback.
21895   *
21896   * @method Html5#currentTime
21897   * @return {number}
21898   *         The value of `currentTime` from the media element.
21899   *
21900   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
21901   */
21902  'currentTime',
21903  /**
21904   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
21905   * object that represents the parts of the media that are already downloaded and
21906   * available for playback.
21907   *
21908   * @method Html5#buffered
21909   * @return {TimeRange}
21910   *         The value of `buffered` from the media element.
21911   *
21912   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
21913   */
21914  'buffered',
21915  /**
21916   * Get the value of `volume` from the media element. `volume` indicates
21917   * the current playback volume of audio for a media. `volume` will be a value from 0
21918   * (silent) to 1 (loudest and default).
21919   *
21920   * @method Html5#volume
21921   * @return {number}
21922   *         The value of `volume` from the media element. Value will be between 0-1.
21923   *
21924   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21925   */
21926  'volume',
21927  /**
21928   * Get the value of `poster` from the media element. `poster` indicates
21929   * that the url of an image file that can/will be shown when no media data is available.
21930   *
21931   * @method Html5#poster
21932   * @return {string}
21933   *         The value of `poster` from the media element. Value will be a url to an
21934   *         image.
21935   *
21936   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
21937   */
21938  'poster',
21939  /**
21940   * Get the value of `preload` from the media element. `preload` indicates
21941   * what should download before the media is interacted with. It can have the following
21942   * values:
21943   * - none: nothing should be downloaded
21944   * - metadata: poster and the first few frames of the media may be downloaded to get
21945   *   media dimensions and other metadata
21946   * - auto: allow the media and metadata for the media to be downloaded before
21947   *    interaction
21948   *
21949   * @method Html5#preload
21950   * @return {string}
21951   *         The value of `preload` from the media element. Will be 'none', 'metadata',
21952   *         or 'auto'.
21953   *
21954   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21955   */
21956  'preload',
21957  /**
21958   * Get the value of the `error` from the media element. `error` indicates any
21959   * MediaError that may have occurred during playback. If error returns null there is no
21960   * current error.
21961   *
21962   * @method Html5#error
21963   * @return {MediaError|null}
21964   *         The value of `error` from the media element. Will be `MediaError` if there
21965   *         is a current error and null otherwise.
21966   *
21967   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
21968   */
21969  'error',
21970  /**
21971   * Get the value of `seeking` from the media element. `seeking` indicates whether the
21972   * media is currently seeking to a new position or not.
21973   *
21974   * @method Html5#seeking
21975   * @return {boolean}
21976   *         - The value of `seeking` from the media element.
21977   *         - True indicates that the media is currently seeking to a new position.
21978   *         - False indicates that the media is not seeking to a new position at this time.
21979   *
21980   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
21981   */
21982  'seeking',
21983  /**
21984   * Get the value of `seekable` from the media element. `seekable` returns a
21985   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
21986   *
21987   * @method Html5#seekable
21988   * @return {TimeRange}
21989   *         The value of `seekable` from the media element. A `TimeRange` object
21990   *         indicating the current ranges of time that can be seeked to.
21991   *
21992   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
21993   */
21994  'seekable',
21995  /**
21996   * Get the value of `ended` from the media element. `ended` indicates whether
21997   * the media has reached the end or not.
21998   *
21999   * @method Html5#ended
22000   * @return {boolean}
22001   *         - The value of `ended` from the media element.
22002   *         - True indicates that the media has ended.
22003   *         - False indicates that the media has not ended.
22004   *
22005   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
22006   */
22007  'ended',
22008  /**
22009   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22010   * the rate at which the media is currently playing back. Examples:
22011   *   - if playbackRate is set to 2, media will play twice as fast.
22012   *   - if playbackRate is set to 0.5, media will play half as fast.
22013   *
22014   * @method Html5#playbackRate
22015   * @return {number}
22016   *         The value of `playbackRate` from the media element. A number indicating
22017   *         the current playback speed of the media, where 1 is normal speed.
22018   *
22019   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22020   */
22021  'playbackRate',
22022  /**
22023   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
22024   * the rate at which the media is currently playing back. This value will not indicate the current
22025   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
22026   *
22027   * Examples:
22028   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
22029   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
22030   *
22031   * @method Html5.prototype.defaultPlaybackRate
22032   * @return {number}
22033   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22034   *         the current playback speed of the media, where 1 is normal speed.
22035   *
22036   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22037   */
22038  'defaultPlaybackRate',
22039  /**
22040   * Get the value of 'disablePictureInPicture' from the video element.
22041   *
22042   * @method Html5#disablePictureInPicture
22043   * @return {boolean} value
22044   *         - The value of `disablePictureInPicture` from the video element.
22045   *         - True indicates that the video can't be played in Picture-In-Picture mode
22046   *         - False indicates that the video can be played in Picture-In-Picture mode
22047   *
22048   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
22049   */
22050  'disablePictureInPicture',
22051  /**
22052   * Get the value of `played` from the media element. `played` returns a `TimeRange`
22053   * object representing points in the media timeline that have been played.
22054   *
22055   * @method Html5#played
22056   * @return {TimeRange}
22057   *         The value of `played` from the media element. A `TimeRange` object indicating
22058   *         the ranges of time that have been played.
22059   *
22060   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
22061   */
22062  'played',
22063  /**
22064   * Get the value of `networkState` from the media element. `networkState` indicates
22065   * the current network state. It returns an enumeration from the following list:
22066   * - 0: NETWORK_EMPTY
22067   * - 1: NETWORK_IDLE
22068   * - 2: NETWORK_LOADING
22069   * - 3: NETWORK_NO_SOURCE
22070   *
22071   * @method Html5#networkState
22072   * @return {number}
22073   *         The value of `networkState` from the media element. This will be a number
22074   *         from the list in the description.
22075   *
22076   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
22077   */
22078  'networkState',
22079  /**
22080   * Get the value of `readyState` from the media element. `readyState` indicates
22081   * the current state of the media element. It returns an enumeration from the
22082   * following list:
22083   * - 0: HAVE_NOTHING
22084   * - 1: HAVE_METADATA
22085   * - 2: HAVE_CURRENT_DATA
22086   * - 3: HAVE_FUTURE_DATA
22087   * - 4: HAVE_ENOUGH_DATA
22088   *
22089   * @method Html5#readyState
22090   * @return {number}
22091   *         The value of `readyState` from the media element. This will be a number
22092   *         from the list in the description.
22093   *
22094   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
22095   */
22096  'readyState',
22097  /**
22098   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
22099   * the current width of the video in css pixels.
22100   *
22101   * @method Html5#videoWidth
22102   * @return {number}
22103   *         The value of `videoWidth` from the video element. This will be a number
22104   *         in css pixels.
22105   *
22106   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22107   */
22108  'videoWidth',
22109  /**
22110   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
22111   * the current height of the video in css pixels.
22112   *
22113   * @method Html5#videoHeight
22114   * @return {number}
22115   *         The value of `videoHeight` from the video element. This will be a number
22116   *         in css pixels.
22117   *
22118   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22119   */
22120  'videoHeight',
22121  /**
22122   * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
22123   * to the browser that should sent the cookies along with the requests for the
22124   * different assets/playlists
22125   *
22126   * @method Html5#crossOrigin
22127   * @return {string}
22128   *         - anonymous indicates that the media should not sent cookies.
22129   *         - use-credentials indicates that the media should sent cookies along the requests.
22130   *
22131   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
22132   */
22133  'crossOrigin',
22134].forEach(function (prop) {
22135  Html5.prototype[prop] = function () {
22136    return this.el_[prop];
22137  };
22138});
22139
22140// Wrap native properties with a setter in this format:
22141// set + toTitleCase(name)
22142// The list is as follows:
22143// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
22144// setDisablePictureInPicture, setCrossOrigin
22145[
22146  /**
22147   * Set the value of `volume` on the media element. `volume` indicates the current
22148   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
22149   * so on.
22150   *
22151   * @method Html5#setVolume
22152   * @param {number} percentAsDecimal
22153   *        The volume percent as a decimal. Valid range is from 0-1.
22154   *
22155   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22156   */
22157  'volume',
22158  /**
22159   * Set the value of `src` on the media element. `src` indicates the current
22160   * {@link Tech~SourceObject} for the media.
22161   *
22162   * @method Html5#setSrc
22163   * @param {Tech~SourceObject} src
22164   *        The source object to set as the current source.
22165   *
22166   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
22167   */
22168  'src',
22169  /**
22170   * Set the value of `poster` on the media element. `poster` is the url to
22171   * an image file that can/will be shown when no media data is available.
22172   *
22173   * @method Html5#setPoster
22174   * @param {string} poster
22175   *        The url to an image that should be used as the `poster` for the media
22176   *        element.
22177   *
22178   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
22179   */
22180  'poster',
22181  /**
22182   * Set the value of `preload` on the media element. `preload` indicates
22183   * what should download before the media is interacted with. It can have the following
22184   * values:
22185   * - none: nothing should be downloaded
22186   * - metadata: poster and the first few frames of the media may be downloaded to get
22187   *   media dimensions and other metadata
22188   * - auto: allow the media and metadata for the media to be downloaded before
22189   *    interaction
22190   *
22191   * @method Html5#setPreload
22192   * @param {string} preload
22193   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
22194   *         or 'auto'.
22195   *
22196   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22197   */
22198  'preload',
22199  /**
22200   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
22201   * the rate at which the media should play back. Examples:
22202   *   - if playbackRate is set to 2, media will play twice as fast.
22203   *   - if playbackRate is set to 0.5, media will play half as fast.
22204   *
22205   * @method Html5#setPlaybackRate
22206   * @return {number}
22207   *         The value of `playbackRate` from the media element. A number indicating
22208   *         the current playback speed of the media, where 1 is normal speed.
22209   *
22210   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22211   */
22212  'playbackRate',
22213  /**
22214   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
22215   * the rate at which the media should play back upon initial startup. Changing this value
22216   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
22217   *
22218   * Example Values:
22219   *   - if playbackRate is set to 2, media will play twice as fast.
22220   *   - if playbackRate is set to 0.5, media will play half as fast.
22221   *
22222   * @method Html5.prototype.setDefaultPlaybackRate
22223   * @return {number}
22224   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22225   *         the current playback speed of the media, where 1 is normal speed.
22226   *
22227   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
22228   */
22229  'defaultPlaybackRate',
22230  /**
22231   * Prevents the browser from suggesting a Picture-in-Picture context menu
22232   * or to request Picture-in-Picture automatically in some cases.
22233   *
22234   * @method Html5#setDisablePictureInPicture
22235   * @param {boolean} value
22236   *         The true value will disable Picture-in-Picture mode.
22237   *
22238   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
22239   */
22240  'disablePictureInPicture',
22241  /**
22242   * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
22243   * to the browser that should sent the cookies along with the requests for the
22244   * different assets/playlists
22245   *
22246   * @method Html5#setCrossOrigin
22247   * @param {string} crossOrigin
22248   *         - anonymous indicates that the media should not sent cookies.
22249   *         - use-credentials indicates that the media should sent cookies along the requests.
22250   *
22251   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
22252   */
22253  'crossOrigin',
22254].forEach(function (prop) {
22255  Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
22256    this.el_[prop] = v;
22257  };
22258});
22259
22260// wrap native functions with a function
22261// The list is as follows:
22262// pause, load, play
22263[
22264  /**
22265   * A wrapper around the media elements `pause` function. This will call the `HTML5`
22266   * media elements `pause` function.
22267   *
22268   * @method Html5#pause
22269   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
22270   */
22271  'pause',
22272  /**
22273   * A wrapper around the media elements `load` function. This will call the `HTML5`s
22274   * media element `load` function.
22275   *
22276   * @method Html5#load
22277   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
22278   */
22279  'load',
22280  /**
22281   * A wrapper around the media elements `play` function. This will call the `HTML5`s
22282   * media element `play` function.
22283   *
22284   * @method Html5#play
22285   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
22286   */
22287  'play',
22288].forEach(function (prop) {
22289  Html5.prototype[prop] = function () {
22290    return this.el_[prop]();
22291  };
22292});
22293Tech.withSourceHandlers(Html5);
22294
22295/**
22296 * Native source handler for Html5, simply passes the source to the media element.
22297 *
22298 * @property {Tech~SourceObject} source
22299 *        The source object
22300 *
22301 * @property {Html5} tech
22302 *        The instance of the HTML5 tech.
22303 */
22304Html5.nativeSourceHandler = {};
22305
22306/**
22307 * Check if the media element can play the given mime type.
22308 *
22309 * @param {string} type
22310 *        The mimetype to check
22311 *
22312 * @return {string}
22313 *         'probably', 'maybe', or '' (empty string)
22314 */
22315Html5.nativeSourceHandler.canPlayType = function (type) {
22316  // IE without MediaPlayer throws an error (#519)
22317  try {
22318    return Html5.TEST_VID.canPlayType(type);
22319  } catch (e) {
22320    return '';
22321  }
22322};
22323
22324/**
22325 * Check if the media element can handle a source natively.
22326 *
22327 * @param {Tech~SourceObject} source
22328 *         The source object
22329 *
22330 * @param {Object} [options]
22331 *         Options to be passed to the tech.
22332 *
22333 * @return {string}
22334 *         'probably', 'maybe', or '' (empty string).
22335 */
22336Html5.nativeSourceHandler.canHandleSource = function (source, options) {
22337  // If a type was provided we should rely on that
22338  if (source.type) {
22339    return Html5.nativeSourceHandler.canPlayType(source.type);
22340
22341    // If no type, fall back to checking 'video/[EXTENSION]'
22342  } else if (source.src) {
22343    const ext = getFileExtension(source.src);
22344    return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
22345  }
22346  return '';
22347};
22348
22349/**
22350 * Pass the source to the native media element.
22351 *
22352 * @param {Tech~SourceObject} source
22353 *        The source object
22354 *
22355 * @param {Html5} tech
22356 *        The instance of the Html5 tech
22357 *
22358 * @param {Object} [options]
22359 *        The options to pass to the source
22360 */
22361Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
22362  tech.setSrc(source.src);
22363};
22364
22365/**
22366 * A noop for the native dispose function, as cleanup is not needed.
22367 */
22368Html5.nativeSourceHandler.dispose = function () {};
22369
22370// Register the native source handler
22371Html5.registerSourceHandler(Html5.nativeSourceHandler);
22372Tech.registerTech('Html5', Html5);
22373
22374/**
22375 * @file player.js
22376 */
22377
22378/** @import { TimeRange } from './utils/time' */
22379/** @import HtmlTrackElement from './tracks/html-track-element' */
22380
22381/**
22382 * @callback PlayerReadyCallback
22383 * @this     {Player}
22384 * @returns  {void}
22385 */
22386
22387// The following tech events are simply re-triggered
22388// on the player when they happen
22389const TECH_EVENTS_RETRIGGER = [
22390  /**
22391   * Fired while the user agent is downloading media data.
22392   *
22393   * @event Player#progress
22394   * @type {Event}
22395   */
22396  /**
22397   * Retrigger the `progress` event that was triggered by the {@link Tech}.
22398   *
22399   * @private
22400   * @method Player#handleTechProgress_
22401   * @fires Player#progress
22402   * @listens Tech#progress
22403   */
22404  'progress',
22405  /**
22406   * Fires when the loading of an audio/video is aborted.
22407   *
22408   * @event Player#abort
22409   * @type {Event}
22410   */
22411  /**
22412   * Retrigger the `abort` event that was triggered by the {@link Tech}.
22413   *
22414   * @private
22415   * @method Player#handleTechAbort_
22416   * @fires Player#abort
22417   * @listens Tech#abort
22418   */
22419  'abort',
22420  /**
22421   * Fires when the browser is intentionally not getting media data.
22422   *
22423   * @event Player#suspend
22424   * @type {Event}
22425   */
22426  /**
22427   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
22428   *
22429   * @private
22430   * @method Player#handleTechSuspend_
22431   * @fires Player#suspend
22432   * @listens Tech#suspend
22433   */
22434  'suspend',
22435  /**
22436   * Fires when the current playlist is empty.
22437   *
22438   * @event Player#emptied
22439   * @type {Event}
22440   */
22441  /**
22442   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
22443   *
22444   * @private
22445   * @method Player#handleTechEmptied_
22446   * @fires Player#emptied
22447   * @listens Tech#emptied
22448   */
22449  'emptied',
22450  /**
22451   * Fires when the browser is trying to get media data, but data is not available.
22452   *
22453   * @event Player#stalled
22454   * @type {Event}
22455   */
22456  /**
22457   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
22458   *
22459   * @private
22460   * @method Player#handleTechStalled_
22461   * @fires Player#stalled
22462   * @listens Tech#stalled
22463   */
22464  'stalled',
22465  /**
22466   * Fires when the browser has loaded meta data for the audio/video.
22467   *
22468   * @event Player#loadedmetadata
22469   * @type {Event}
22470   */
22471  /**
22472   * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
22473   *
22474   * @private
22475   * @method Player#handleTechLoadedmetadata_
22476   * @fires Player#loadedmetadata
22477   * @listens Tech#loadedmetadata
22478   */
22479  'loadedmetadata',
22480  /**
22481   * Fires when the browser has loaded the current frame of the audio/video.
22482   *
22483   * @event Player#loadeddata
22484   * @type {event}
22485   */
22486  /**
22487   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
22488   *
22489   * @private
22490   * @method Player#handleTechLoaddeddata_
22491   * @fires Player#loadeddata
22492   * @listens Tech#loadeddata
22493   */
22494  'loadeddata',
22495  /**
22496   * Fires when the current playback position has changed.
22497   *
22498   * @event Player#timeupdate
22499   * @type {event}
22500   */
22501  /**
22502   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
22503   *
22504   * @private
22505   * @method Player#handleTechTimeUpdate_
22506   * @fires Player#timeupdate
22507   * @listens Tech#timeupdate
22508   */
22509  'timeupdate',
22510  /**
22511   * Fires when the video's intrinsic dimensions change
22512   *
22513   * @event Player#resize
22514   * @type {event}
22515   */
22516  /**
22517   * Retrigger the `resize` event that was triggered by the {@link Tech}.
22518   *
22519   * @private
22520   * @method Player#handleTechResize_
22521   * @fires Player#resize
22522   * @listens Tech#resize
22523   */
22524  'resize',
22525  /**
22526   * Fires when the volume has been changed
22527   *
22528   * @event Player#volumechange
22529   * @type {event}
22530   */
22531  /**
22532   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
22533   *
22534   * @private
22535   * @method Player#handleTechVolumechange_
22536   * @fires Player#volumechange
22537   * @listens Tech#volumechange
22538   */
22539  'volumechange',
22540  /**
22541   * Fires when the text track has been changed
22542   *
22543   * @event Player#texttrackchange
22544   * @type {event}
22545   */
22546  /**
22547   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
22548   *
22549   * @private
22550   * @method Player#handleTechTexttrackchange_
22551   * @fires Player#texttrackchange
22552   * @listens Tech#texttrackchange
22553   */
22554  'texttrackchange',
22555];
22556
22557// events to queue when playback rate is zero
22558// this is a hash for the sole purpose of mapping non-camel-cased event names
22559// to camel-cased function names
22560const TECH_EVENTS_QUEUE = {
22561  canplay: 'CanPlay',
22562  canplaythrough: 'CanPlayThrough',
22563  playing: 'Playing',
22564  seeked: 'Seeked',
22565};
22566const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
22567const BREAKPOINT_CLASSES = {};
22568
22569// grep: vjs-layout-tiny
22570// grep: vjs-layout-x-small
22571// grep: vjs-layout-small
22572// grep: vjs-layout-medium
22573// grep: vjs-layout-large
22574// grep: vjs-layout-x-large
22575// grep: vjs-layout-huge
22576BREAKPOINT_ORDER.forEach((k) => {
22577  const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
22578  BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
22579});
22580const DEFAULT_BREAKPOINTS = {
22581  tiny: 210,
22582  xsmall: 320,
22583  small: 425,
22584  medium: 768,
22585  large: 1440,
22586  xlarge: 2560,
22587  huge: Infinity,
22588};
22589
22590/**
22591 * An instance of the `Player` class is created when any of the Video.js setup methods
22592 * are used to initialize a video.
22593 *
22594 * After an instance has been created it can be accessed globally in three ways:
22595 * 1. By calling `videojs.getPlayer('example_video_1');`
22596 * 2. By calling `videojs('example_video_1');` (not recommended)
22597 * 2. By using it directly via `videojs.players.example_video_1;`
22598 *
22599 * @extends Component
22600 * @global
22601 */
22602class Player extends Component$1 {
22603  /**
22604   * Create an instance of this class.
22605   *
22606   * @param {Element} tag
22607   *        The original video DOM element used for configuring options.
22608   *
22609   * @param {Object} [options]
22610   *        Object of option names and values.
22611   *
22612   * @param {PlayerReadyCallback} [ready]
22613   *        Ready callback function.
22614   */
22615  constructor(tag, options, ready) {
22616    // Make sure tag ID exists
22617    // also here.. probably better
22618    tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
22619
22620    // Set Options
22621    // The options argument overrides options set in the video tag
22622    // which overrides globally set options.
22623    // This latter part coincides with the load order
22624    // (tag must exist before Player)
22625    options = Object.assign(Player.getTagSettings(tag), options);
22626
22627    // Delay the initialization of children because we need to set up
22628    // player properties first, and can't use `this` before `super()`
22629    options.initChildren = false;
22630
22631    // Same with creating the element
22632    options.createEl = false;
22633
22634    // don't auto mixin the evented mixin
22635    options.evented = false;
22636
22637    // we don't want the player to report touch activity on itself
22638    // see enableTouchActivity in Component
22639    options.reportTouchActivity = false;
22640
22641    // If language is not set, get the closest lang attribute
22642    if (!options.language) {
22643      const closest = tag.closest('[lang]');
22644      if (closest) {
22645        options.language = closest.getAttribute('lang');
22646      }
22647    }
22648
22649    // Run base component initializing with new options
22650    super(null, options, ready);
22651
22652    // Create bound methods for document listeners.
22653    this.boundDocumentFullscreenChange_ = (e) => this.documentFullscreenChange_(e);
22654    this.boundFullWindowOnEscKey_ = (e) => this.fullWindowOnEscKey(e);
22655    this.boundUpdateStyleEl_ = (e) => this.updateStyleEl_(e);
22656    this.boundApplyInitTime_ = (e) => this.applyInitTime_(e);
22657    this.boundUpdateCurrentBreakpoint_ = (e) => this.updateCurrentBreakpoint_(e);
22658    this.boundHandleTechClick_ = (e) => this.handleTechClick_(e);
22659    this.boundHandleTechDoubleClick_ = (e) => this.handleTechDoubleClick_(e);
22660    this.boundHandleTechTouchStart_ = (e) => this.handleTechTouchStart_(e);
22661    this.boundHandleTechTouchMove_ = (e) => this.handleTechTouchMove_(e);
22662    this.boundHandleTechTouchEnd_ = (e) => this.handleTechTouchEnd_(e);
22663    this.boundHandleTechTap_ = (e) => this.handleTechTap_(e);
22664    this.boundUpdatePlayerHeightOnAudioOnlyMode_ = (e) =>
22665      this.updatePlayerHeightOnAudioOnlyMode_(e);
22666
22667    // default isFullscreen_ to false
22668    this.isFullscreen_ = false;
vendor: 9,467 bytes, lines 22669-22951
22669
22670    // create logger
22671    this.log = createLogger(this.id_);
22672
22673    // Hold our own reference to fullscreen api so it can be mocked in tests
22674    this.fsApi_ = FullscreenApi;
22675
22676    // Tracks when a tech changes the poster
22677    this.isPosterFromTech_ = false;
22678
22679    // Holds callback info that gets queued when playback rate is zero
22680    // and a seek is happening
22681    this.queuedCallbacks_ = [];
22682
22683    // Turn off API access because we're loading a new tech that might load asynchronously
22684    this.isReady_ = false;
22685
22686    // Init state hasStarted_
22687    this.hasStarted_ = false;
22688
22689    // Init state userActive_
22690    this.userActive_ = false;
22691
22692    // Init debugEnabled_
22693    this.debugEnabled_ = false;
22694
22695    // Init state audioOnlyMode_
22696    this.audioOnlyMode_ = false;
22697
22698    // Init state audioPosterMode_
22699    this.audioPosterMode_ = false;
22700
22701    // Init state audioOnlyCache_
22702    this.audioOnlyCache_ = {
22703      controlBarHeight: null,
22704      playerHeight: null,
22705      hiddenChildren: [],
22706    };
22707
22708    // if the global option object was accidentally blown away by
22709    // someone, bail early with an informative error
22710    if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
22711      throw new Error(
22712        'No techOrder specified. Did you overwrite ' +
22713          'videojs.options instead of just changing the ' +
22714          'properties you want to override?'
22715      );
22716    }
22717
22718    // Store the original tag used to set options
22719    this.tag = tag;
22720
22721    // Store the tag attributes used to restore html5 element
22722    this.tagAttributes = tag && getAttributes(tag);
22723
22724    // Update current language
22725    this.language(this.options_.language);
22726
22727    // Update Supported Languages
22728    if (options.languages) {
22729      // Normalise player option languages to lowercase
22730      const languagesToLower = {};
22731      Object.getOwnPropertyNames(options.languages).forEach(function (name) {
22732        languagesToLower[name.toLowerCase()] = options.languages[name];
22733      });
22734      this.languages_ = languagesToLower;
22735    } else {
22736      this.languages_ = Player.prototype.options_.languages;
22737    }
22738    this.resetCache_();
22739
22740    // Set poster
22741    /** @type string */
22742    this.poster_ = options.poster || '';
22743
22744    // Set controls
22745    /** @type {boolean} */
22746    this.controls_ = !!options.controls;
22747
22748    // Original tag settings stored in options
22749    // now remove immediately so native controls don't flash.
22750    // May be turned back on by HTML5 tech if nativeControlsForTouch is true
22751    tag.controls = false;
22752    tag.removeAttribute('controls');
22753    this.changingSrc_ = false;
22754    this.playCallbacks_ = [];
22755    this.playTerminatedQueue_ = [];
22756
22757    // the attribute overrides the option
22758    if (tag.hasAttribute('autoplay')) {
22759      this.autoplay(true);
22760    } else {
22761      // otherwise use the setter to validate and
22762      // set the correct value.
22763      this.autoplay(this.options_.autoplay);
22764    }
22765
22766    // check plugins
22767    if (options.plugins) {
22768      Object.keys(options.plugins).forEach((name) => {
22769        if (typeof this[name] !== 'function') {
22770          throw new Error(`plugin "${name}" does not exist`);
22771        }
22772      });
22773    }
22774
22775    /*
22776     * Store the internal state of scrubbing
22777     *
22778     * @private
22779     * @return {Boolean} True if the user is scrubbing
22780     */
22781    this.scrubbing_ = false;
22782    this.el_ = this.createEl();
22783
22784    // Make this an evented object and use `el_` as its event bus.
22785    evented(this, {
22786      eventBusKey: 'el_',
22787    });
22788
22789    // listen to document and player fullscreenchange handlers so we receive those events
22790    // before a user can receive them so we can update isFullscreen appropriately.
22791    // make sure that we listen to fullscreenchange events before everything else to make sure that
22792    // our isFullscreen method is updated properly for internal components as well as external.
22793    if (this.fsApi_.requestFullscreen) {
22794      on(
22795        document__default['default'],
22796        this.fsApi_.fullscreenchange,
22797        this.boundDocumentFullscreenChange_
22798      );
22799      this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22800    }
22801    if (this.fluid_) {
22802      this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
22803    }
22804    // We also want to pass the original player options to each component and plugin
22805    // as well so they don't need to reach back into the player for options later.
22806    // We also need to do another copy of this.options_ so we don't end up with
22807    // an infinite loop.
22808    const playerOptionsCopy = merge$1(this.options_);
22809
22810    // Load plugins
22811    if (options.plugins) {
22812      Object.keys(options.plugins).forEach((name) => {
22813        this[name](options.plugins[name]);
22814      });
22815    }
22816
22817    // Enable debug mode to fire debugon event for all plugins.
22818    if (options.debug) {
22819      this.debug(true);
22820    }
22821    this.options_.playerOptions = playerOptionsCopy;
22822    this.middleware_ = [];
22823    this.playbackRates(options.playbackRates);
22824    if (options.experimentalSvgIcons) {
22825      // Add SVG Sprite to the DOM
22826      const parser = new window__default['default'].DOMParser();
22827      const parsedSVG = parser.parseFromString(icons, 'image/svg+xml');
22828      const errorNode = parsedSVG.querySelector('parsererror');
22829      if (errorNode) {
22830        log$1.warn('Failed to load SVG Icons. Falling back to Font Icons.');
22831        this.options_.experimentalSvgIcons = null;
22832      } else {
22833        const sprite = parsedSVG.documentElement;
22834        sprite.style.display = 'none';
22835        this.el_.appendChild(sprite);
22836        this.addClass('vjs-svg-icons-enabled');
22837      }
22838    }
22839    this.initChildren();
22840
22841    // Set isAudio based on whether or not an audio tag was used
22842    this.isAudio(tag.nodeName.toLowerCase() === 'audio');
22843
22844    // Update controls className. Can't do this when the controls are initially
22845    // set because the element doesn't exist yet.
22846    if (this.controls()) {
22847      this.addClass('vjs-controls-enabled');
22848    } else {
22849      this.addClass('vjs-controls-disabled');
22850    }
22851
22852    // Set ARIA label and region role depending on player type
22853    this.el_.setAttribute('role', 'region');
22854    if (this.isAudio()) {
22855      this.el_.setAttribute('aria-label', this.localize('Audio Player'));
22856    } else {
22857      this.el_.setAttribute('aria-label', this.localize('Video Player'));
22858    }
22859    if (this.isAudio()) {
22860      this.addClass('vjs-audio');
22861    }
22862
22863    // Check if spatial navigation is enabled in the options.
22864    // If enabled, instantiate the SpatialNavigation class.
22865    if (options.spatialNavigation && options.spatialNavigation.enabled) {
22866      this.spatialNavigation = new SpatialNavigation(this);
22867      this.addClass('vjs-spatial-navigation-enabled');
22868    }
22869
22870    // TODO: Make this smarter. Toggle user state between touching/mousing
22871    // using events, since devices can have both touch and mouse events.
22872    // TODO: Make this check be performed again when the window switches between monitors
22873    // (See https://github.com/videojs/video.js/issues/5683)
22874    if (TOUCH_ENABLED) {
22875      this.addClass('vjs-touch-enabled');
22876    }
22877
22878    // iOS Safari has broken hover handling
22879    if (!IS_IOS) {
22880      this.addClass('vjs-workinghover');
22881    }
22882
22883    // Make player easily findable by ID
22884    Player.players[this.id_] = this;
22885
22886    // Add a major version class to aid css in plugins
22887    const majorVersion = version$6.split('.')[0];
22888    this.addClass(`vjs-v${majorVersion}`);
22889
22890    // When the player is first initialized, trigger activity so components
22891    // like the control bar show themselves if needed
22892    this.userActive(true);
22893    this.reportUserActivity();
22894    this.one('play', (e) => this.listenForUserActivity_(e));
22895    this.on('keydown', (e) => this.handleKeyDown(e));
22896    this.on('languagechange', (e) => this.handleLanguagechange(e));
22897    this.breakpoints(this.options_.breakpoints);
22898    this.responsive(this.options_.responsive);
22899
22900    // Calling both the audio mode methods after the player is fully
22901    // setup to be able to listen to the events triggered by them
22902    this.on('ready', () => {
22903      // Calling the audioPosterMode method first so that
22904      // the audioOnlyMode can take precedence when both options are set to true
22905      this.audioPosterMode(this.options_.audioPosterMode);
22906      this.audioOnlyMode(this.options_.audioOnlyMode);
22907    });
22908  }
22909
22910  /**
22911   * Destroys the video player and does any necessary cleanup.
22912   *
22913   * This is especially helpful if you are dynamically adding and removing videos
22914   * to/from the DOM.
22915   *
22916   * @fires Player#dispose
22917   */
22918  dispose() {
22919    /**
22920     * Called when the player is being disposed of.
22921     *
22922     * @event Player#dispose
22923     * @type {Event}
22924     */
22925    this.trigger('dispose');
22926    // prevent dispose from being called twice
22927    this.off('dispose');
22928
22929    // Make sure all player-specific document listeners are unbound. This is
22930    off(
22931      document__default['default'],
22932      this.fsApi_.fullscreenchange,
22933      this.boundDocumentFullscreenChange_
22934    );
22935    off(document__default['default'], 'keydown', this.boundFullWindowOnEscKey_);
22936    if (this.styleEl_ && this.styleEl_.parentNode) {
22937      this.styleEl_.parentNode.removeChild(this.styleEl_);
22938      this.styleEl_ = null;
22939    }
22940
22941    // Kill reference to this player
22942    Player.players[this.id_] = null;
22943    if (this.tag && this.tag.player) {
22944      this.tag.player = null;
22945    }
22946    if (this.el_ && this.el_.player) {
22947      this.el_.player = null;
22948    }
22949    if (this.tech_) {
22950      this.tech_.dispose();
22951      this.isPosterFromTech_ = false;
22952      this.poster_ = '';
22953    }
22954    if (this.playerElIngest_) {
22955      this.playerElIngest_ = null;
22956    }
22957    if (this.tag) {
22958      this.tag = null;
22959    }
22960    clearCacheForPlayer(this);
22961
22962    // remove all event handlers for track lists
22963    // all tracks and track listeners are removed on
22964    // tech dispose
22965    ALL.names.forEach((name) => {
22966      const props = ALL[name];
22967      const list = this[props.getterName]();
22968
22969      // if it is not a native list
22970      // we have to manually remove event listeners
22971      if (list && list.off) {
22972        list.off();
22973      }
22974    });
22975
22976    // the actual .el_ is removed here, or replaced if
22977    super.dispose({
22978      restoreEl: this.options_.restoreEl,
22979    });
22980  }
22981
22982  /**
22983   * Create the `Player`'s DOM element.
22984   *
22985   * @return {Element}
22986   *         The DOM element that gets created.
22987   */
22988  createEl() {
22989    let tag = this.tag;
22990    let el;
22991    let playerElIngest = (this.playerElIngest_ =
22992      tag.parentNode &&
22993      tag.parentNode.hasAttribute &&
22994      tag.parentNode.hasAttribute('data-vjs-player'));
22995    const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
22996    if (playerElIngest) {
22997      el = this.el_ = tag.parentNode;
22998    } else if (!divEmbed) {
22999      el = this.el_ = super.createEl('div');
23000    }
23001
23002    // Copy over all the attributes from the tag, including ID and class
23003    // ID will now reference player box, not the video tag
23004    const attrs = getAttributes(tag);
23005    if (divEmbed) {
23006      el = this.el_ = tag;
23007      tag = this.tag = document__default['default'].createElement('video');
23008      while (el.children.length) {
23009        tag.appendChild(el.firstChild);
23010      }
23011      if (!hasClass(el, 'video-js')) {
23012        addClass(el, 'video-js');
23013      }
23014      el.appendChild(tag);
23015      playerElIngest = this.playerElIngest_ = el;
23016      // move properties over from our custom `video-js` element
23017      // to our new `video` element. This will move things like
23018      // `src` or `controls` that were set via js before the player
23019      // was initialized.
23020      Object.keys(el).forEach((k) => {
23021        try {
23022          tag[k] = el[k];
23023        } catch (e) {
23024          // we got a a property like outerHTML which we can't actually copy, ignore it
23025        }
23026      });
23027    }
23028
23029    // set tabindex to -1 to remove the video element from the focus order
23030    tag.setAttribute('tabindex', '-1');
23031    attrs.tabindex = '-1';
23032
23033    // Workaround for #4583 on Chrome (on Windows) with JAWS.
23034    // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
23035    // Note that we can't detect if JAWS is being used, but this ARIA attribute
23036    // doesn't change behavior of Chrome if JAWS is not being used
23037    if (IS_CHROME && IS_WINDOWS) {
23038      tag.setAttribute('role', 'application');
23039      attrs.role = 'application';
23040    }
23041
23042    // Remove width/height attrs from tag so CSS can make it 100% width/height
23043    tag.removeAttribute('width');
23044    tag.removeAttribute('height');
23045    if ('width' in attrs) {
23046      delete attrs.width;
23047    }
23048    if ('height' in attrs) {
23049      delete attrs.height;
23050    }
23051    Object.getOwnPropertyNames(attrs).forEach(function (attr) {
23052      // don't copy over the class attribute to the player element when we're in a div embed
23053      // the class is already set up properly in the divEmbed case
23054      // and we want to make sure that the `video-js` class doesn't get lost
23055      if (!(divEmbed && attr === 'class')) {
23056        el.setAttribute(attr, attrs[attr]);
23057      }
23058      if (divEmbed) {
23059        tag.setAttribute(attr, attrs[attr]);
23060      }
23061    });
23062
23063    // Update tag id/class for use as HTML5 playback tech
23064    // Might think we should do this after embedding in container so .vjs-tech class
23065    // doesn't flash 100% width/height, but class only applies with .video-js parent
23066    tag.playerId = tag.id;
23067    tag.id += '_html5_api';
23068    tag.className = 'vjs-tech';
23069
23070    // Make player findable on elements
23071    tag.player = el.player = this;
23072    // Default state of video is paused
23073    this.addClass('vjs-paused');
23074    const deviceClassNames = [
23075      'IS_SMART_TV',
23076      'IS_TIZEN',
23077      'IS_WEBOS',
23078      'IS_ANDROID',
23079      'IS_IPAD',
23080      'IS_IPHONE',
23081      'IS_CHROMECAST_RECEIVER',
23082    ]
23083      .filter((key) => browser[key])
23084      .map((key) => {
23085        return 'vjs-device-' + key.substring(3).toLowerCase().replace(/\_/g, '-');
23086      });
23087    this.addClass(...deviceClassNames);
23088
23089    // Add a style element in the player that we'll use to set the width/height
23090    // of the player in a way that's still overridable by CSS, just like the
23091    // video element
23092    if (window__default['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) {
23093      this.styleEl_ = createStyleElement('vjs-styles-dimensions');
23094      const defaultsStyleEl = $('.vjs-styles-defaults');
23095      const head = $('head');
23096      head.insertBefore(
23097        this.styleEl_,
23098        defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild
23099      );
23100    }
23101    this.fill_ = false;
vendor: 8,027 bytes, lines 23102-23357
23102    this.fluid_ = false;
23103
23104    // Pass in the width/height/aspectRatio options which will update the style el
23105    this.width(this.options_.width);
23106    this.height(this.options_.height);
23107    this.fill(this.options_.fill);
23108    this.fluid(this.options_.fluid);
23109    this.aspectRatio(this.options_.aspectRatio);
23110    // support both crossOrigin and crossorigin to reduce confusion and issues around the name
23111    this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
23112
23113    // Hide any links within the video/audio tag,
23114    // because IE doesn't hide them completely from screen readers.
23115    const links = tag.getElementsByTagName('a');
23116    for (let i = 0; i < links.length; i++) {
23117      const linkEl = links.item(i);
23118      addClass(linkEl, 'vjs-hidden');
23119      linkEl.setAttribute('hidden', 'hidden');
23120    }
23121
23122    // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
23123    // keep track of the original for later so we can know if the source originally failed
23124    tag.initNetworkState_ = tag.networkState;
23125
23126    // Wrap video tag in div (el/box) container
23127    if (tag.parentNode && !playerElIngest) {
23128      tag.parentNode.insertBefore(el, tag);
23129    }
23130
23131    // insert the tag as the first child of the player element
23132    // then manually add it to the children array so that this.addChild
23133    // will work properly for other components
23134    //
23135    // Breaks iPhone, fixed in HTML5 setup.
23136    prependTo(tag, el);
23137    this.children_.unshift(tag);
23138
23139    // Set lang attr on player to ensure CSS :lang() in consistent with player
23140    // if it's been set to something different to the doc
23141    this.el_.setAttribute('lang', this.language_);
23142    this.el_.setAttribute('translate', 'no');
23143    this.el_ = el;
23144    return el;
23145  }
23146
23147  /**
23148   * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
23149   * sets the `crossOrigin` property on the `<video>` tag to control the CORS
23150   * behavior.
23151   *
23152   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
23153   *
23154   * @param {string|null} [value]
23155   *        The value to set the `Player`'s crossOrigin to. If an argument is
23156   *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
23157   *
23158   * @return {string|null|undefined}
23159   *         - The current crossOrigin value of the `Player` when getting.
23160   *         - undefined when setting
23161   */
23162  crossOrigin(value) {
23163    // `null` can be set to unset a value
23164    if (typeof value === 'undefined') {
23165      return this.techGet_('crossOrigin');
23166    }
23167    if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
23168      log$1.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
23169      return;
23170    }
23171    this.techCall_('setCrossOrigin', value);
23172    if (this.posterImage) {
23173      this.posterImage.crossOrigin(value);
23174    }
23175    return;
23176  }
23177
23178  /**
23179   * A getter/setter for the `Player`'s width. Returns the player's configured value.
23180   * To get the current width use `currentWidth()`.
23181   *
23182   * @param {number|string} [value]
23183   *        CSS value to set the `Player`'s width to.
23184   *
23185   * @return {number|undefined}
23186   *         - The current width of the `Player` when getting.
23187   *         - Nothing when setting
23188   */
23189  width(value) {
23190    return this.dimension('width', value);
23191  }
23192
23193  /**
23194   * A getter/setter for the `Player`'s height. Returns the player's configured value.
23195   * To get the current height use `currentheight()`.
23196   *
23197   * @param {number|string} [value]
23198   *        CSS value to set the `Player`'s height to.
23199   *
23200   * @return {number|undefined}
23201   *         - The current height of the `Player` when getting.
23202   *         - Nothing when setting
23203   */
23204  height(value) {
23205    return this.dimension('height', value);
23206  }
23207
23208  /**
23209   * A getter/setter for the `Player`'s width & height.
23210   *
23211   * @param {string} dimension
23212   *        This string can be:
23213   *        - 'width'
23214   *        - 'height'
23215   *
23216   * @param {number|string} [value]
23217   *        Value for dimension specified in the first argument.
23218   *
23219   * @return {number}
23220   *         The dimension arguments value when getting (width/height).
23221   */
23222  dimension(dimension, value) {
23223    const privDimension = dimension + '_';
23224    if (value === undefined) {
23225      return this[privDimension] || 0;
23226    }
23227    if (value === '' || value === 'auto') {
23228      // If an empty string is given, reset the dimension to be automatic
23229      this[privDimension] = undefined;
23230      this.updateStyleEl_();
23231      return;
23232    }
23233    const parsedVal = parseFloat(value);
23234    if (isNaN(parsedVal)) {
23235      log$1.error(`Improper value "${value}" supplied for for ${dimension}`);
23236      return;
23237    }
23238    this[privDimension] = parsedVal;
23239    this.updateStyleEl_();
23240  }
23241
23242  /**
23243   * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
23244   *
23245   * Turning this on will turn off fill mode.
23246   *
23247   * @param {boolean} [bool]
23248   *        - A value of true adds the class.
23249   *        - A value of false removes the class.
23250   *        - No value will be a getter.
23251   *
23252   * @return {boolean|undefined}
23253   *         - The value of fluid when getting.
23254   *         - `undefined` when setting.
23255   */
23256  fluid(bool) {
23257    if (bool === undefined) {
23258      return !!this.fluid_;
23259    }
23260    this.fluid_ = !!bool;
23261    if (isEvented(this)) {
23262      this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23263    }
23264    if (bool) {
23265      this.addClass('vjs-fluid');
23266      this.fill(false);
23267      addEventedCallback(this, () => {
23268        this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23269      });
23270    } else {
23271      this.removeClass('vjs-fluid');
23272    }
23273    this.updateStyleEl_();
23274  }
23275
23276  /**
23277   * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
23278   *
23279   * Turning this on will turn off fluid mode.
23280   *
23281   * @param {boolean} [bool]
23282   *        - A value of true adds the class.
23283   *        - A value of false removes the class.
23284   *        - No value will be a getter.
23285   *
23286   * @return {boolean|undefined}
23287   *         - The value of fluid when getting.
23288   *         - `undefined` when setting.
23289   */
23290  fill(bool) {
23291    if (bool === undefined) {
23292      return !!this.fill_;
23293    }
23294    this.fill_ = !!bool;
23295    if (bool) {
23296      this.addClass('vjs-fill');
23297      this.fluid(false);
23298    } else {
23299      this.removeClass('vjs-fill');
23300    }
23301  }
23302
23303  /**
23304   * Get/Set the aspect ratio
23305   *
23306   * @param {string} [ratio]
23307   *        Aspect ratio for player
23308   *
23309   * @return {string|undefined}
23310   *         returns the current aspect ratio when getting
23311   */
23312
23313  /**
23314   * A getter/setter for the `Player`'s aspect ratio.
23315   *
23316   * @param {string} [ratio]
23317   *        The value to set the `Player`'s aspect ratio to.
23318   *
23319   * @return {string|undefined}
23320   *         - The current aspect ratio of the `Player` when getting.
23321   *         - undefined when setting
23322   */
23323  aspectRatio(ratio) {
23324    if (ratio === undefined) {
23325      return this.aspectRatio_;
23326    }
23327
23328    // Check for width:height format
23329    if (!/^\d+\:\d+$/.test(ratio)) {
23330      throw new Error(
23331        'Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'
23332      );
23333    }
23334    this.aspectRatio_ = ratio;
23335
23336    // We're assuming if you set an aspect ratio you want fluid mode,
23337    // because in fixed mode you could calculate width and height yourself.
23338    this.fluid(true);
23339    this.updateStyleEl_();
23340  }
23341
23342  /**
23343   * Update styles of the `Player` element (height, width and aspect ratio).
23344   *
23345   * @private
23346   * @listens Tech#loadedmetadata
23347   */
23348  updateStyleEl_() {
23349    if (window__default['default'].VIDEOJS_NO_DYNAMIC_STYLE === true) {
23350      const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
23351      const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
23352      const techEl = this.tech_ && this.tech_.el();
23353      if (techEl) {
23354        if (width >= 0) {
23355          techEl.width = width;
23356        }
23357        if (height >= 0) {
vendor: 4,279 bytes, lines 23358-23484
23358          techEl.height = height;
23359        }
23360      }
23361      return;
23362    }
23363    let width;
23364    let height;
23365    let aspectRatio;
23366    let idClass;
23367
23368    // The aspect ratio is either used directly or to calculate width and height.
23369    if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
23370      // Use any aspectRatio that's been specifically set
23371      aspectRatio = this.aspectRatio_;
23372    } else if (this.videoWidth() > 0) {
23373      // Otherwise try to get the aspect ratio from the video metadata
23374      aspectRatio = this.videoWidth() + ':' + this.videoHeight();
23375    } else {
23376      // Or use a default. The video element's is 2:1, but 16:9 is more common.
23377      aspectRatio = '16:9';
23378    }
23379
23380    // Get the ratio as a decimal we can use to calculate dimensions
23381    const ratioParts = aspectRatio.split(':');
23382    const ratioMultiplier = ratioParts[1] / ratioParts[0];
23383    if (this.width_ !== undefined) {
23384      // Use any width that's been specifically set
23385      width = this.width_;
23386    } else if (this.height_ !== undefined) {
23387      // Or calculate the width from the aspect ratio if a height has been set
23388      width = this.height_ / ratioMultiplier;
23389    } else {
23390      // Or use the video's metadata, or use the video el's default of 300
23391      width = this.videoWidth() || 300;
23392    }
23393    if (this.height_ !== undefined) {
23394      // Use any height that's been specifically set
23395      height = this.height_;
23396    } else {
23397      // Otherwise calculate the height from the ratio and the width
23398      height = width * ratioMultiplier;
23399    }
23400
23401    // Ensure the CSS class is valid by starting with an alpha character
23402    if (/^[^a-zA-Z]/.test(this.id())) {
23403      idClass = 'dimensions-' + this.id();
23404    } else {
23405      idClass = this.id() + '-dimensions';
23406    }
23407
23408    // Ensure the right class is still on the player for the style element
23409    this.addClass(idClass);
23410    setTextContent(
23411      this.styleEl_,
23412      `
23413      .${idClass} {
23414        width: ${width}px;
23415        height: ${height}px;
23416      }
23417
23418      .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
23419        padding-top: ${ratioMultiplier * 100}%;
23420      }
23421    `
23422    );
23423  }
23424
23425  /**
23426   * Load/Create an instance of playback {@link Tech} including element
23427   * and API methods. Then append the `Tech` element in `Player` as a child.
23428   *
23429   * @param {string} techName
23430   *        name of the playback technology
23431   *
23432   * @param {string} source
23433   *        video source
23434   *
23435   * @private
23436   */
23437  loadTech_(techName, source) {
23438    // Pause and remove current playback technology
23439    if (this.tech_) {
23440      this.unloadTech_();
23441    }
23442    const titleTechName = toTitleCase$1(techName);
23443    const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
23444
23445    // get rid of the HTML5 video tag as soon as we are using another tech
23446    if (titleTechName !== 'Html5' && this.tag) {
23447      Tech.getTech('Html5').disposeMediaElement(this.tag);
23448      this.tag.player = null;
23449      this.tag = null;
23450    }
23451    this.techName_ = titleTechName;
23452
23453    // Turn off API access because we're loading a new tech that might load asynchronously
23454    this.isReady_ = false;
23455    let autoplay = this.autoplay();
23456
23457    // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
23458    // because the player is going to handle autoplay on `loadstart`
23459    if (
23460      typeof this.autoplay() === 'string' ||
23461      (this.autoplay() === true && this.options_.normalizeAutoplay)
23462    ) {
23463      autoplay = false;
23464    }
23465
23466    // Grab tech-specific options from player options and add source and parent element to use.
23467    const techOptions = {
23468      source,
23469      autoplay,
23470      nativeControlsForTouch: this.options_.nativeControlsForTouch,
23471      playerId: this.id(),
23472      techId: `${this.id()}_${camelTechName}_api`,
23473      playsinline: this.options_.playsinline,
23474      preload: this.options_.preload,
23475      loop: this.options_.loop,
23476      disablePictureInPicture: this.options_.disablePictureInPicture,
23477      muted: this.options_.muted,
23478      poster: this.poster(),
23479      language: this.language(),
23480      playerElIngest: this.playerElIngest_ || false,
23481      'vtt.js': this.options_['vtt.js'],
23482      canOverridePoster: !!this.options_.techCanOverridePoster,
23483      enableSourceset: this.options_.enableSourceset,
23484    };
23485    ALL.names.forEach((name) => {
23486      const props = ALL[name];
23487      techOptions[props.getterName] = this[props.privateName];
23488    });
23489    Object.assign(techOptions, this.options_[titleTechName]);
23490    Object.assign(techOptions, this.options_[camelTechName]);
23491    Object.assign(techOptions, this.options_[techName.toLowerCase()]);
23492    if (this.tag) {
23493      techOptions.tag = this.tag;
23494    }
23495    if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
23496      techOptions.startTime = this.cache_.currentTime;
23497    }
23498
23499    // Initialize tech instance
23500    const TechClass = Tech.getTech(techName);
23501    if (!TechClass) {
23502      throw new Error(
23503        `No Tech named '${titleTechName}' exists! '${titleTechName}' should be registered using videojs.registerTech()'`
23504      );
23505    }
23506    this.tech_ = new TechClass(techOptions);
23507
23508    // player.triggerReady is always async, so don't need this to be async
23509    this.tech_.ready(bind_(this, this.handleTechReady_), true);
23510    textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
23511
23512    // Listen to all HTML5-defined events and trigger them on the player
23513    TECH_EVENTS_RETRIGGER.forEach((event) => {
23514      this.on(this.tech_, event, (e) => this[`handleTech${toTitleCase$1(event)}_`](e));
23515    });
23516    Object.keys(TECH_EVENTS_QUEUE).forEach((event) => {
23517      this.on(this.tech_, event, (eventObj) => {
23518        if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
23519          this.queuedCallbacks_.push({
23520            callback: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
23521            event: eventObj,
23522          });
23523          return;
23524        }
23525        this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
23526      });
23527    });
23528    this.on(this.tech_, 'loadstart', (e) => this.handleTechLoadStart_(e));
23529    this.on(this.tech_, 'sourceset', (e) => this.handleTechSourceset_(e));
23530    this.on(this.tech_, 'waiting', (e) => this.handleTechWaiting_(e));
23531    this.on(this.tech_, 'ended', (e) => this.handleTechEnded_(e));
23532    this.on(this.tech_, 'seeking', (e) => this.handleTechSeeking_(e));
23533    this.on(this.tech_, 'play', (e) => this.handleTechPlay_(e));
23534    this.on(this.tech_, 'pause', (e) => this.handleTechPause_(e));
23535    this.on(this.tech_, 'durationchange', (e) => this.handleTechDurationChange_(e));
23536    this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
23537    this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err));
23538    this.on(this.tech_, 'enterpictureinpicture', (e) => this.handleTechEnterPictureInPicture_(e));
23539    this.on(this.tech_, 'leavepictureinpicture', (e) => this.handleTechLeavePictureInPicture_(e));
23540    this.on(this.tech_, 'error', (e) => this.handleTechError_(e));
23541    this.on(this.tech_, 'posterchange', (e) => this.handleTechPosterChange_(e));
23542    this.on(this.tech_, 'textdata', (e) => this.handleTechTextData_(e));
23543    this.on(this.tech_, 'ratechange', (e) => this.handleTechRateChange_(e));
23544    this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
23545    this.usingNativeControls(this.techGet_('controls'));
23546    if (this.controls() && !this.usingNativeControls()) {
23547      this.addTechControlsListeners_();
23548    }
23549
23550    // Add the tech element in the DOM if it was not already there
23551    // Make sure to not insert the original video element if using Html5
23552    if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
23553      prependTo(this.tech_.el(), this.el());
23554    }
23555
23556    // Get rid of the original video tag reference after the first tech is loaded
23557    if (this.tag) {
23558      this.tag.player = null;
23559      this.tag = null;
23560    }
23561  }
23562
23563  /**
23564   * Unload and dispose of the current playback {@link Tech}.
23565   *
23566   * @private
23567   */
23568  unloadTech_() {
23569    // Save the current text tracks so that we can reuse the same text tracks with the next tech
23570    ALL.names.forEach((name) => {
23571      const props = ALL[name];
23572      this[props.privateName] = this[props.getterName]();
23573    });
23574    this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
23575    this.isReady_ = false;
vendor: 20,232 bytes, lines 23576-24253
23576    this.tech_.dispose();
23577    this.tech_ = false;
23578    if (this.isPosterFromTech_) {
23579      this.poster_ = '';
23580      this.trigger('posterchange');
23581    }
23582    this.isPosterFromTech_ = false;
23583  }
23584
23585  /**
23586   * Return a reference to the current {@link Tech}.
23587   * It will print a warning by default about the danger of using the tech directly
23588   * but any argument that is passed in will silence the warning.
23589   *
23590   * @param {*} [safety]
23591   *        Anything passed in to silence the warning
23592   *
23593   * @return {Tech}
23594   *         The Tech
23595   */
23596  tech(safety) {
23597    if (safety === undefined) {
23598      log$1.warn(
23599        "Using the tech directly can be dangerous. I hope you know what you're doing.\n" +
23600          'See https://github.com/videojs/video.js/issues/2617 for more info.\n'
23601      );
23602    }
23603    return this.tech_;
23604  }
23605
23606  /**
23607   * An object that contains Video.js version.
23608   *
23609   * @typedef {Object} PlayerVersion
23610   *
23611   * @property {string} 'video.js' - Video.js version
23612   */
23613
23614  /**
23615   * Returns an object with Video.js version.
23616   *
23617   * @return {PlayerVersion}
23618   *          An object with Video.js version.
23619   */
23620  version() {
23621    return {
23622      'video.js': version$6,
23623    };
23624  }
23625
23626  /**
23627   * Set up click and touch listeners for the playback element
23628   *
23629   * - On desktops: a click on the video itself will toggle playback
23630   * - On mobile devices: a click on the video toggles controls
23631   *   which is done by toggling the user state between active and
23632   *   inactive
23633   * - A tap can signal that a user has become active or has become inactive
23634   *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
23635   *   quick tap should hide them again (signaling the user is in an inactive
23636   *   viewing state)
23637   * - In addition to this, we still want the user to be considered inactive after
23638   *   a few seconds of inactivity.
23639   *
23640   * > Note: the only part of iOS interaction we can't mimic with this setup
23641   * is a touch and hold on the video element counting as activity in order to
23642   * keep the controls showing, but that shouldn't be an issue. A touch and hold
23643   * on any controls will still keep the user active
23644   *
23645   * @private
23646   */
23647  addTechControlsListeners_() {
23648    // Make sure to remove all the previous listeners in case we are called multiple times.
23649    this.removeTechControlsListeners_();
23650    this.on(this.tech_, 'click', this.boundHandleTechClick_);
23651    this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23652
23653    // If the controls were hidden we don't want that to change without a tap event
23654    // so we'll check if the controls were already showing before reporting user
23655    // activity
23656    this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23657    this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23658    this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23659
23660    // The tap listener needs to come after the touchend listener because the tap
23661    // listener cancels out any reportedUserActivity when setting userActive(false)
23662    this.on(this.tech_, 'tap', this.boundHandleTechTap_);
23663  }
23664
23665  /**
23666   * Remove the listeners used for click and tap controls. This is needed for
23667   * toggling to controls disabled, where a tap/touch should do nothing.
23668   *
23669   * @private
23670   */
23671  removeTechControlsListeners_() {
23672    // We don't want to just use `this.off()` because there might be other needed
23673    // listeners added by techs that extend this.
23674    this.off(this.tech_, 'tap', this.boundHandleTechTap_);
23675    this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23676    this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23677    this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23678    this.off(this.tech_, 'click', this.boundHandleTechClick_);
23679    this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23680  }
23681
23682  /**
23683   * Player waits for the tech to be ready
23684   *
23685   * @private
23686   */
23687  handleTechReady_() {
23688    this.triggerReady();
23689
23690    // Keep the same volume as before
23691    if (this.cache_.volume) {
23692      this.techCall_('setVolume', this.cache_.volume);
23693    }
23694
23695    // Look if the tech found a higher resolution poster while loading
23696    this.handleTechPosterChange_();
23697
23698    // Update the duration if available
23699    this.handleTechDurationChange_();
23700  }
23701
23702  /**
23703   * Retrigger the `loadstart` event that was triggered by the {@link Tech}.
23704   *
23705   * @fires Player#loadstart
23706   * @listens Tech#loadstart
23707   * @private
23708   */
23709  handleTechLoadStart_() {
23710    // TODO: Update to use `emptied` event instead. See #1277.
23711
23712    this.removeClass('vjs-ended', 'vjs-seeking');
23713
23714    // reset the error state
23715    this.error(null);
23716
23717    // Update the duration
23718    this.handleTechDurationChange_();
23719    if (!this.paused()) {
23720      /**
23721       * Fired when the user agent begins looking for media data
23722       *
23723       * @event Player#loadstart
23724       * @type {Event}
23725       */
23726      this.trigger('loadstart');
23727    } else {
23728      // reset the hasStarted state
23729      this.hasStarted(false);
23730      this.trigger('loadstart');
23731    }
23732
23733    // autoplay happens after loadstart for the browser,
23734    // so we mimic that behavior
23735    this.manualAutoplay_(
23736      this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay()
23737    );
23738  }
23739
23740  /**
23741   * Handle autoplay string values, rather than the typical boolean
23742   * values that should be handled by the tech. Note that this is not
23743   * part of any specification. Valid values and what they do can be
23744   * found on the autoplay getter at Player#autoplay()
23745   */
23746  manualAutoplay_(type) {
23747    if (!this.tech_ || typeof type !== 'string') {
23748      return;
23749    }
23750
23751    // Save original muted() value, set muted to true, and attempt to play().
23752    // On promise rejection, restore muted from saved value
23753    const resolveMuted = () => {
23754      const previouslyMuted = this.muted();
23755      this.muted(true);
23756      const restoreMuted = () => {
23757        this.muted(previouslyMuted);
23758      };
23759
23760      // restore muted on play terminatation
23761      this.playTerminatedQueue_.push(restoreMuted);
23762      const mutedPromise = this.play();
23763      if (!isPromise(mutedPromise)) {
23764        return;
23765      }
23766      return mutedPromise.catch((err) => {
23767        restoreMuted();
23768        throw new Error(`Rejection at manualAutoplay. Restoring muted value. ${err ? err : ''}`);
23769      });
23770    };
23771    let promise;
23772
23773    // if muted defaults to true
23774    // the only thing we can do is call play
23775    if (type === 'any' && !this.muted()) {
23776      promise = this.play();
23777      if (isPromise(promise)) {
23778        promise = promise.catch(resolveMuted);
23779      }
23780    } else if (type === 'muted' && !this.muted()) {
23781      promise = resolveMuted();
23782    } else {
23783      promise = this.play();
23784    }
23785    if (!isPromise(promise)) {
23786      return;
23787    }
23788    return promise
23789      .then(() => {
23790        this.trigger({
23791          type: 'autoplay-success',
23792          autoplay: type,
23793        });
23794      })
23795      .catch(() => {
23796        this.trigger({
23797          type: 'autoplay-failure',
23798          autoplay: type,
23799        });
23800      });
23801  }
23802
23803  /**
23804   * Update the internal source caches so that we return the correct source from
23805   * `src()`, `currentSource()`, and `currentSources()`.
23806   *
23807   * > Note: `currentSources` will not be updated if the source that is passed in exists
23808   *         in the current `currentSources` cache.
23809   *
23810   *
23811   * @param {Tech~SourceObject} srcObj
23812   *        A string or object source to update our caches to.
23813   */
23814  updateSourceCaches_(srcObj = '') {
23815    let src = srcObj;
23816    let type = '';
23817    if (typeof src !== 'string') {
23818      src = srcObj.src;
23819      type = srcObj.type;
23820    }
23821
23822    // make sure all the caches are set to default values
23823    // to prevent null checking
23824    this.cache_.source = this.cache_.source || {};
23825    this.cache_.sources = this.cache_.sources || [];
23826
23827    // try to get the type of the src that was passed in
23828    if (src && !type) {
23829      type = findMimetype(this, src);
23830    }
23831
23832    // update `currentSource` cache always
23833    this.cache_.source = merge$1({}, srcObj, {
23834      src,
23835      type,
23836    });
23837    const matchingSources = this.cache_.sources.filter((s) => s.src && s.src === src);
23838    const sourceElSources = [];
23839    const sourceEls = this.$$('source');
23840    const matchingSourceEls = [];
23841    for (let i = 0; i < sourceEls.length; i++) {
23842      const sourceObj = getAttributes(sourceEls[i]);
23843      sourceElSources.push(sourceObj);
23844      if (sourceObj.src && sourceObj.src === src) {
23845        matchingSourceEls.push(sourceObj.src);
23846      }
23847    }
23848
23849    // if we have matching source els but not matching sources
23850    // the current source cache is not up to date
23851    if (matchingSourceEls.length && !matchingSources.length) {
23852      this.cache_.sources = sourceElSources;
23853      // if we don't have matching source or source els set the
23854      // sources cache to the `currentSource` cache
23855    } else if (!matchingSources.length) {
23856      this.cache_.sources = [this.cache_.source];
23857    }
23858
23859    // update the tech `src` cache
23860    this.cache_.src = src;
23861  }
23862
23863  /**
23864   * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
23865   * causing the media element to reload.
23866   *
23867   * It will fire for the initial source and each subsequent source.
23868   * This event is a custom event from Video.js and is triggered by the {@link Tech}.
23869   *
23870   * The event object for this event contains a `src` property that will contain the source
23871   * that was available when the event was triggered. This is generally only necessary if Video.js
23872   * is switching techs while the source was being changed.
23873   *
23874   * It is also fired when `load` is called on the player (or media element)
23875   * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
23876   * says that the resource selection algorithm needs to be aborted and restarted.
23877   * In this case, it is very likely that the `src` property will be set to the
23878   * empty string `""` to indicate we do not know what the source will be but
23879   * that it is changing.
23880   *
23881   * *This event is currently still experimental and may change in minor releases.*
23882   * __To use this, pass `enableSourceset` option to the player.__
23883   *
23884   * @event Player#sourceset
23885   * @type {Event}
23886   * @prop {string} src
23887   *                The source url available when the `sourceset` was triggered.
23888   *                It will be an empty string if we cannot know what the source is
23889   *                but know that the source will change.
23890   */
23891  /**
23892   * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
23893   *
23894   * @fires Player#sourceset
23895   * @listens Tech#sourceset
23896   * @private
23897   */
23898  handleTechSourceset_(event) {
23899    // only update the source cache when the source
23900    // was not updated using the player api
23901    if (!this.changingSrc_) {
23902      let updateSourceCaches = (src) => this.updateSourceCaches_(src);
23903      const playerSrc = this.currentSource().src;
23904      const eventSrc = event.src;
23905
23906      // if we have a playerSrc that is not a blob, and a tech src that is a blob
23907      if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
23908        // if both the tech source and the player source were updated we assume
23909        // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
23910        if (
23911          !this.lastSource_ ||
23912          (this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc)
23913        ) {
23914          updateSourceCaches = () => {};
23915        }
23916      }
23917
23918      // update the source to the initial source right away
23919      // in some cases this will be empty string
23920      updateSourceCaches(eventSrc);
23921
23922      // if the `sourceset` `src` was an empty string
23923      // wait for a `loadstart` to update the cache to `currentSrc`.
23924      // If a sourceset happens before a `loadstart`, we reset the state
23925      if (!event.src) {
23926        this.tech_.any(['sourceset', 'loadstart'], (e) => {
23927          // if a sourceset happens before a `loadstart` there
23928          // is nothing to do as this `handleTechSourceset_`
23929          // will be called again and this will be handled there.
23930          if (e.type === 'sourceset') {
23931            return;
23932          }
23933          const techSrc = this.techGet_('currentSrc');
23934          this.lastSource_.tech = techSrc;
23935          this.updateSourceCaches_(techSrc);
23936        });
23937      }
23938    }
23939    this.lastSource_ = {
23940      player: this.currentSource().src,
23941      tech: event.src,
23942    };
23943    this.trigger({
23944      src: event.src,
23945      type: 'sourceset',
23946    });
23947  }
23948
23949  /**
23950   * Add/remove the vjs-has-started class
23951   *
23952   *
23953   * @param {boolean} request
23954   *        - true: adds the class
23955   *        - false: remove the class
23956   *
23957   * @return {boolean}
23958   *         the boolean value of hasStarted_
23959   */
23960  hasStarted(request) {
23961    if (request === undefined) {
23962      // act as getter, if we have no request to change
23963      return this.hasStarted_;
23964    }
23965    if (request === this.hasStarted_) {
23966      return;
23967    }
23968    this.hasStarted_ = request;
23969    if (this.hasStarted_) {
23970      this.addClass('vjs-has-started');
23971    } else {
23972      this.removeClass('vjs-has-started');
23973    }
23974  }
23975
23976  /**
23977   * Fired whenever the media begins or resumes playback
23978   *
23979   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
23980   * @fires Player#play
23981   * @listens Tech#play
23982   * @private
23983   */
23984  handleTechPlay_() {
23985    this.removeClass('vjs-ended', 'vjs-paused');
23986    this.addClass('vjs-playing');
23987
23988    // hide the poster when the user hits play
23989    this.hasStarted(true);
23990    /**
23991     * Triggered whenever an {@link Tech#play} event happens. Indicates that
23992     * playback has started or resumed.
23993     *
23994     * @event Player#play
23995     * @type {Event}
23996     */
23997    this.trigger('play');
23998  }
23999
24000  /**
24001   * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
24002   *
24003   * If there were any events queued while the playback rate was zero, fire
24004   * those events now.
24005   *
24006   * @private
24007   * @method Player#handleTechRateChange_
24008   * @fires Player#ratechange
24009   * @listens Tech#ratechange
24010   */
24011  handleTechRateChange_() {
24012    if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
24013      this.queuedCallbacks_.forEach((queued) => queued.callback(queued.event));
24014      this.queuedCallbacks_ = [];
24015    }
24016    this.cache_.lastPlaybackRate = this.tech_.playbackRate();
24017    /**
24018     * Fires when the playing speed of the audio/video is changed
24019     *
24020     * @event Player#ratechange
24021     * @type {event}
24022     */
24023    this.trigger('ratechange');
24024  }
24025
24026  /**
24027   * Retrigger the `waiting` event that was triggered by the {@link Tech}.
24028   *
24029   * @fires Player#waiting
24030   * @listens Tech#waiting
24031   * @private
24032   */
24033  handleTechWaiting_() {
24034    this.addClass('vjs-waiting');
24035    /**
24036     * A readyState change on the DOM element has caused playback to stop.
24037     *
24038     * @event Player#waiting
24039     * @type {Event}
24040     */
24041    this.trigger('waiting');
24042
24043    // Browsers may emit a timeupdate event after a waiting event. In order to prevent
24044    // premature removal of the waiting class, wait for the time to change.
24045    const timeWhenWaiting = this.currentTime();
24046    const timeUpdateListener = () => {
24047      if (timeWhenWaiting !== this.currentTime()) {
24048        this.removeClass('vjs-waiting');
24049        this.off('timeupdate', timeUpdateListener);
24050      }
24051    };
24052    this.on('timeupdate', timeUpdateListener);
24053  }
24054
24055  /**
24056   * Retrigger the `canplay` event that was triggered by the {@link Tech}.
24057   * > Note: This is not consistent between browsers. See #1351
24058   *
24059   * @fires Player#canplay
24060   * @listens Tech#canplay
24061   * @private
24062   */
24063  handleTechCanPlay_() {
24064    this.removeClass('vjs-waiting');
24065    /**
24066     * The media has a readyState of HAVE_FUTURE_DATA or greater.
24067     *
24068     * @event Player#canplay
24069     * @type {Event}
24070     */
24071    this.trigger('canplay');
24072  }
24073
24074  /**
24075   * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
24076   *
24077   * @fires Player#canplaythrough
24078   * @listens Tech#canplaythrough
24079   * @private
24080   */
24081  handleTechCanPlayThrough_() {
24082    this.removeClass('vjs-waiting');
24083    /**
24084     * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
24085     * entire media file can be played without buffering.
24086     *
24087     * @event Player#canplaythrough
24088     * @type {Event}
24089     */
24090    this.trigger('canplaythrough');
24091  }
24092
24093  /**
24094   * Retrigger the `playing` event that was triggered by the {@link Tech}.
24095   *
24096   * @fires Player#playing
24097   * @listens Tech#playing
24098   * @private
24099   */
24100  handleTechPlaying_() {
24101    this.removeClass('vjs-waiting');
24102    /**
24103     * The media is no longer blocked from playback, and has started playing.
24104     *
24105     * @event Player#playing
24106     * @type {Event}
24107     */
24108    this.trigger('playing');
24109  }
24110
24111  /**
24112   * Retrigger the `seeking` event that was triggered by the {@link Tech}.
24113   *
24114   * @fires Player#seeking
24115   * @listens Tech#seeking
24116   * @private
24117   */
24118  handleTechSeeking_() {
24119    this.addClass('vjs-seeking');
24120    /**
24121     * Fired whenever the player is jumping to a new time
24122     *
24123     * @event Player#seeking
24124     * @type {Event}
24125     */
24126    this.trigger('seeking');
24127  }
24128
24129  /**
24130   * Retrigger the `seeked` event that was triggered by the {@link Tech}.
24131   *
24132   * @fires Player#seeked
24133   * @listens Tech#seeked
24134   * @private
24135   */
24136  handleTechSeeked_() {
24137    this.removeClass('vjs-seeking', 'vjs-ended');
24138    /**
24139     * Fired when the player has finished jumping to a new time
24140     *
24141     * @event Player#seeked
24142     * @type {Event}
24143     */
24144    this.trigger('seeked');
24145  }
24146
24147  /**
24148   * Retrigger the `pause` event that was triggered by the {@link Tech}.
24149   *
24150   * @fires Player#pause
24151   * @listens Tech#pause
24152   * @private
24153   */
24154  handleTechPause_() {
24155    this.removeClass('vjs-playing');
24156    this.addClass('vjs-paused');
24157    /**
24158     * Fired whenever the media has been paused
24159     *
24160     * @event Player#pause
24161     * @type {Event}
24162     */
24163    this.trigger('pause');
24164  }
24165
24166  /**
24167   * Retrigger the `ended` event that was triggered by the {@link Tech}.
24168   *
24169   * @fires Player#ended
24170   * @listens Tech#ended
24171   * @private
24172   */
24173  handleTechEnded_() {
24174    this.addClass('vjs-ended');
24175    this.removeClass('vjs-waiting');
24176    if (this.options_.loop) {
24177      this.currentTime(0);
24178      this.play();
24179    } else if (!this.paused()) {
24180      this.pause();
24181    }
24182
24183    /**
24184     * Fired when the end of the media resource is reached (currentTime == duration)
24185     *
24186     * @event Player#ended
24187     * @type {Event}
24188     */
24189    this.trigger('ended');
24190  }
24191
24192  /**
24193   * Fired when the duration of the media resource is first known or changed
24194   *
24195   * @listens Tech#durationchange
24196   * @private
24197   */
24198  handleTechDurationChange_() {
24199    this.duration(this.techGet_('duration'));
24200  }
24201
24202  /**
24203   * Handle a click on the media element to play/pause
24204   *
24205   * @param {Event} event
24206   *        the event that caused this function to trigger
24207   *
24208   * @listens Tech#click
24209   * @private
24210   */
24211  handleTechClick_(event) {
24212    // When controls are disabled a click should not toggle playback because
24213    // the click is considered a control
24214    if (!this.controls_) {
24215      return;
24216    }
24217    if (
24218      this.options_ === undefined ||
24219      this.options_.userActions === undefined ||
24220      this.options_.userActions.click === undefined ||
24221      this.options_.userActions.click !== false
24222    ) {
24223      if (
24224        this.options_ !== undefined &&
24225        this.options_.userActions !== undefined &&
24226        typeof this.options_.userActions.click === 'function'
24227      ) {
24228        this.options_.userActions.click.call(this, event);
24229      } else if (this.paused()) {
24230        silencePromise(this.play());
24231      } else {
24232        this.pause();
24233      }
24234    }
24235  }
24236
24237  /**
24238   * Handle a double-click on the media element to enter/exit fullscreen,
24239   * or exit documentPictureInPicture mode
24240   *
24241   * @param {Event} event
24242   *        the event that caused this function to trigger
24243   *
24244   * @listens Tech#dblclick
24245   * @private
24246   */
24247  handleTechDoubleClick_(event) {
24248    if (!this.controls_) {
24249      return;
24250    }
24251
24252    // we do not want to toggle fullscreen state
24253    // when double-clicking inside a control bar or a modal
vendor: 5,733 bytes, lines 24254-24486
24254    const inAllowedEls = Array.prototype.some.call(
24255      this.$$('.vjs-control-bar, .vjs-modal-dialog'),
24256      (el) => el.contains(event.target)
24257    );
24258    if (!inAllowedEls) {
24259      /*
24260       * options.userActions.doubleClick
24261       *
24262       * If `undefined` or `true`, double-click toggles fullscreen if controls are present
24263       * Set to `false` to disable double-click handling
24264       * Set to a function to substitute an external double-click handler
24265       */
24266      if (
24267        this.options_ === undefined ||
24268        this.options_.userActions === undefined ||
24269        this.options_.userActions.doubleClick === undefined ||
24270        this.options_.userActions.doubleClick !== false
24271      ) {
24272        if (
24273          this.options_ !== undefined &&
24274          this.options_.userActions !== undefined &&
24275          typeof this.options_.userActions.doubleClick === 'function'
24276        ) {
24277          this.options_.userActions.doubleClick.call(this, event);
24278        } else if (
24279          this.isInPictureInPicture() &&
24280          !document__default['default'].pictureInPictureElement
24281        ) {
24282          // Checking the presence of `window.documentPictureInPicture.window` complicates
24283          // tests, checking `document.pictureInPictureElement` also works. It wouldn't
24284          // be null in regular picture in picture.
24285          // Exit picture in picture mode. This gesture can't trigger pip on the main window.
24286          this.exitPictureInPicture();
24287        } else if (this.isFullscreen()) {
24288          this.exitFullscreen();
24289        } else {
24290          this.requestFullscreen();
24291        }
24292      }
24293    }
24294  }
24295
24296  /**
24297   * Handle a tap on the media element. It will toggle the user
24298   * activity state, which hides and shows the controls.
24299   *
24300   * @listens Tech#tap
24301   * @private
24302   */
24303  handleTechTap_() {
24304    this.userActive(!this.userActive());
24305  }
24306
24307  /**
24308   * Handle touch to start
24309   *
24310   * @listens Tech#touchstart
24311   * @private
24312   */
24313  handleTechTouchStart_() {
24314    this.userWasActive = this.userActive();
24315  }
24316
24317  /**
24318   * Handle touch to move
24319   *
24320   * @listens Tech#touchmove
24321   * @private
24322   */
24323  handleTechTouchMove_() {
24324    if (this.userWasActive) {
24325      this.reportUserActivity();
24326    }
24327  }
24328
24329  /**
24330   * Handle touch to end
24331   *
24332   * @param {Event} event
24333   *        the touchend event that triggered
24334   *        this function
24335   *
24336   * @listens Tech#touchend
24337   * @private
24338   */
24339  handleTechTouchEnd_(event) {
24340    // Stop the mouse events from also happening
24341    if (event.cancelable) {
24342      event.preventDefault();
24343    }
24344  }
24345
24346  /**
24347   * @private
24348   */
24349  toggleFullscreenClass_() {
24350    if (this.isFullscreen()) {
24351      this.addClass('vjs-fullscreen');
24352    } else {
24353      this.removeClass('vjs-fullscreen');
24354    }
24355  }
24356
24357  /**
24358   * when the document fschange event triggers it calls this
24359   */
24360  documentFullscreenChange_(e) {
24361    const targetPlayer = e.target.player;
24362
24363    // if another player was fullscreen
24364    // do a null check for targetPlayer because older firefox's would put document as e.target
24365    if (targetPlayer && targetPlayer !== this) {
24366      return;
24367    }
24368    const el = this.el();
24369    let isFs = document__default['default'][this.fsApi_.fullscreenElement] === el;
24370    if (!isFs && el.matches) {
24371      isFs = el.matches(':' + this.fsApi_.fullscreen);
24372    }
24373    this.isFullscreen(isFs);
24374  }
24375
24376  /**
24377   * Handle Tech Fullscreen Change
24378   *
24379   * @param {Event} event
24380   *        the fullscreenchange event that triggered this function
24381   *
24382   * @param {Object} data
24383   *        the data that was sent with the event
24384   *
24385   * @private
24386   * @listens Tech#fullscreenchange
24387   * @fires Player#fullscreenchange
24388   */
24389  handleTechFullscreenChange_(event, data) {
24390    if (data) {
24391      if (data.nativeIOSFullscreen) {
24392        this.addClass('vjs-ios-native-fs');
24393        this.tech_.one('webkitendfullscreen', () => {
24394          this.removeClass('vjs-ios-native-fs');
24395        });
24396      }
24397      this.isFullscreen(data.isFullscreen);
24398    }
24399  }
24400  handleTechFullscreenError_(event, err) {
24401    this.trigger('fullscreenerror', err);
24402  }
24403
24404  /**
24405   * @private
24406   */
24407  togglePictureInPictureClass_() {
24408    if (this.isInPictureInPicture()) {
24409      this.addClass('vjs-picture-in-picture');
24410    } else {
24411      this.removeClass('vjs-picture-in-picture');
24412    }
24413  }
24414
24415  /**
24416   * Handle Tech Enter Picture-in-Picture.
24417   *
24418   * @param {Event} event
24419   *        the enterpictureinpicture event that triggered this function
24420   *
24421   * @private
24422   * @listens Tech#enterpictureinpicture
24423   */
24424  handleTechEnterPictureInPicture_(event) {
24425    this.isInPictureInPicture(true);
24426  }
24427
24428  /**
24429   * Handle Tech Leave Picture-in-Picture.
24430   *
24431   * @param {Event} event
24432   *        the leavepictureinpicture event that triggered this function
24433   *
24434   * @private
24435   * @listens Tech#leavepictureinpicture
24436   */
24437  handleTechLeavePictureInPicture_(event) {
24438    this.isInPictureInPicture(false);
24439  }
24440
24441  /**
24442   * Fires when an error occurred during the loading of an audio/video.
24443   *
24444   * @private
24445   * @listens Tech#error
24446   */
24447  handleTechError_() {
24448    const error = this.tech_.error();
24449    if (error) {
24450      this.error(error);
24451    }
24452  }
24453
24454  /**
24455   * Retrigger the `textdata` event that was triggered by the {@link Tech}.
24456   *
24457   * @fires Player#textdata
24458   * @listens Tech#textdata
24459   * @private
24460   */
24461  handleTechTextData_() {
24462    let data = null;
24463    if (arguments.length > 1) {
24464      data = arguments[1];
24465    }
24466
24467    /**
24468     * Fires when we get a textdata event from tech
24469     *
24470     * @event Player#textdata
24471     * @type {Event}
24472     */
24473    this.trigger('textdata', data);
24474  }
24475
24476  /**
24477   * Get object for cached values.
24478   *
24479   * @return {Object}
24480   *         get the current object cache
24481   */
24482  getCache() {
24483    return this.cache_;
24484  }
24485
24486  /**
vendor: 4,125 bytes, lines 24487-24628
24487   * Resets the internal cache object.
24488   *
24489   * Using this function outside the player constructor or reset method may
24490   * have unintended side-effects.
24491   *
24492   * @private
24493   */
24494  resetCache_() {
24495    this.cache_ = {
24496      // Right now, the currentTime is not _really_ cached because it is always
24497      // retrieved from the tech (see: currentTime). However, for completeness,
24498      // we set it to zero here to ensure that if we do start actually caching
24499      // it, we reset it along with everything else.
24500      currentTime: 0,
24501      initTime: 0,
24502      inactivityTimeout: this.options_.inactivityTimeout,
24503      duration: NaN,
24504      lastVolume: 1,
24505      lastPlaybackRate: this.defaultPlaybackRate(),
24506      media: null,
24507      src: '',
24508      source: {},
24509      sources: [],
24510      playbackRates: [],
24511      volume: 1,
24512    };
24513  }
24514
24515  /**
24516   * Pass values to the playback tech
24517   *
24518   * @param {string} [method]
24519   *        the method to call
24520   *
24521   * @param {Object} [arg]
24522   *        the argument to pass
24523   *
24524   * @private
24525   */
24526  techCall_(method, arg) {
24527    // If it's not ready yet, call method when it is
24528
24529    this.ready(function () {
24530      if (method in allowedSetters) {
24531        return set(this.middleware_, this.tech_, method, arg);
24532      } else if (method in allowedMediators) {
24533        return mediate(this.middleware_, this.tech_, method, arg);
24534      }
24535      try {
24536        if (this.tech_) {
24537          this.tech_[method](arg);
24538        }
24539      } catch (e) {
24540        log$1(e);
24541        throw e;
24542      }
24543    }, true);
24544  }
24545
24546  /**
24547   * Mediate attempt to call playback tech method
24548   * and return the value of the method called.
24549   *
24550   * @param {string} method
24551   *        Tech method
24552   *
24553   * @return {*}
24554   *         Value returned by the tech method called, undefined if tech
24555   *         is not ready or tech method is not present
24556   *
24557   * @private
24558   */
24559  techGet_(method) {
24560    if (!this.tech_ || !this.tech_.isReady_) {
24561      return;
24562    }
24563    if (method in allowedGetters) {
24564      return get(this.middleware_, this.tech_, method);
24565    } else if (method in allowedMediators) {
24566      return mediate(this.middleware_, this.tech_, method);
24567    }
24568
24569    // Log error when playback tech object is present but method
24570    // is undefined or unavailable
24571    try {
24572      return this.tech_[method]();
24573    } catch (e) {
24574      // When building additional tech libs, an expected method may not be defined yet
24575      if (this.tech_[method] === undefined) {
24576        log$1(
24577          `Video.js: ${method} method not defined for ${this.techName_} playback technology.`,
24578          e
24579        );
24580        throw e;
24581      }
24582
24583      // When a method isn't available on the object it throws a TypeError
24584      if (e.name === 'TypeError') {
24585        log$1(
24586          `Video.js: ${method} unavailable on ${this.techName_} playback technology element.`,
24587          e
24588        );
24589        this.tech_.isReady_ = false;
24590        throw e;
24591      }
24592
24593      // If error unknown, just log and throw
24594      log$1(e);
24595      throw e;
24596    }
24597  }
24598
24599  /**
24600   * Attempt to begin playback at the first opportunity.
24601   *
24602   * @return {Promise|undefined}
24603   *         Returns a promise if the browser supports Promises (or one
24604   *         was passed in as an option). This promise will be resolved on
24605   *         the return value of play. If this is undefined it will fulfill the
24606   *         promise chain otherwise the promise chain will be fulfilled when
24607   *         the promise from play is fulfilled.
24608   */
24609  play() {
24610    return new Promise((resolve) => {
24611      this.play_(resolve);
24612    });
24613  }
24614
24615  /**
24616   * The actual logic for play, takes a callback that will be resolved on the
24617   * return value of play. This allows us to resolve to the play promise if there
24618   * is one on modern browsers.
24619   *
24620   * @private
24621   * @param {Function} [callback]
24622   *        The callback that should be called when the techs play is actually called
24623   */
24624  play_(callback = silencePromise) {
24625    this.playCallbacks_.push(callback);
24626    const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
24627    const isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS);
24628
24629    // treat calls to play_ somewhat like the `one` event function
24630    if (this.waitToPlay_) {
24631      this.off(['ready', 'loadstart'], this.waitToPlay_);
24632      this.waitToPlay_ = null;
24633    }
24634
24635    // if the player/tech is not ready or the src itself is not ready
24636    // queue up a call to play on `ready` or `loadstart`
24637    if (!this.isReady_ || !isSrcReady) {
24638      this.waitToPlay_ = (e)
vendor: 8,726 bytes, lines 24638-24930
24638 => {
24639        this.play_();
24640      };
24641      this.one(['ready', 'loadstart'], this.waitToPlay_);
24642
24643      // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
24644      // in that case, we need to prime the video element by calling load so it'll be ready in time
24645      if (!isSrcReady && isSafariOrIOS) {
24646        this.load();
24647      }
24648      return;
24649    }
24650
24651    // If the player/tech is ready and we have a source, we can attempt playback.
24652    const val = this.techGet_('play');
24653
24654    // For native playback, reset the progress bar if we get a play call from a replay.
24655    const isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
24656    if (isNativeReplay) {
24657      this.resetProgressBar_();
24658    }
24659    // play was terminated if the returned value is null
24660    if (val === null) {
24661      this.runPlayTerminatedQueue_();
24662    } else {
24663      this.runPlayCallbacks_(val);
24664    }
24665  }
24666
24667  /**
24668   * These functions will be run when if play is terminated. If play
24669   * runPlayCallbacks_ is run these function will not be run. This allows us
24670   * to differentiate between a terminated play and an actual call to play.
24671   */
24672  runPlayTerminatedQueue_() {
24673    const queue = this.playTerminatedQueue_.slice(0);
24674    this.playTerminatedQueue_ = [];
24675    queue.forEach(function (q) {
24676      q();
24677    });
24678  }
24679
24680  /**
24681   * When a callback to play is delayed we have to run these
24682   * callbacks when play is actually called on the tech. This function
24683   * runs the callbacks that were delayed and accepts the return value
24684   * from the tech.
24685   *
24686   * @param {undefined|Promise} val
24687   *        The return value from the tech.
24688   */
24689  runPlayCallbacks_(val) {
24690    const callbacks = this.playCallbacks_.slice(0);
24691    this.playCallbacks_ = [];
24692    // clear play terminatedQueue since we finished a real play
24693    this.playTerminatedQueue_ = [];
24694    callbacks.forEach(function (cb) {
24695      cb(val);
24696    });
24697  }
24698
24699  /**
24700   * Pause the video playback
24701   */
24702  pause() {
24703    this.techCall_('pause');
24704  }
24705
24706  /**
24707   * Check if the player is paused or has yet to play
24708   *
24709   * @return {boolean}
24710   *         - false: if the media is currently playing
24711   *         - true: if media is not currently playing
24712   */
24713  paused() {
24714    // The initial state of paused should be true (in Safari it's actually false)
24715    return this.techGet_('paused') === false ? false : true;
24716  }
24717
24718  /**
24719   * Get a TimeRange object representing the current ranges of time that the user
24720   * has played.
24721   *
24722   * @return {TimeRange}
24723   *         A time range object that represents all the increments of time that have
24724   *         been played.
24725   */
24726  played() {
24727    return this.techGet_('played') || createTimeRanges$1(0, 0);
24728  }
24729
24730  /**
24731   * Sets or returns whether or not the user is "scrubbing". Scrubbing is
24732   * when the user has clicked the progress bar handle and is
24733   * dragging it along the progress bar.
24734   *
24735   * @param {boolean} [isScrubbing]
24736   *        whether the user is or is not scrubbing
24737   *
24738   * @return {boolean|undefined}
24739   *         - The value of scrubbing when getting
24740   *         - Nothing when setting
24741   */
24742  scrubbing(isScrubbing) {
24743    if (typeof isScrubbing === 'undefined') {
24744      return this.scrubbing_;
24745    }
24746    this.scrubbing_ = !!isScrubbing;
24747    this.techCall_('setScrubbing', this.scrubbing_);
24748    if (isScrubbing) {
24749      this.addClass('vjs-scrubbing');
24750    } else {
24751      this.removeClass('vjs-scrubbing');
24752    }
24753  }
24754
24755  /**
24756   * Get or set the current time (in seconds)
24757   *
24758   * @param {number|string} [seconds]
24759   *        The time to seek to in seconds
24760   *
24761   * @return {number|undefined}
24762   *         - the current time in seconds when getting
24763   *         - Nothing when setting
24764   */
24765  currentTime(seconds) {
24766    if (seconds === undefined) {
24767      // cache last currentTime and return. default to 0 seconds
24768      //
24769      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
24770      // currentTime when scrubbing, but may not provide much performance benefit after all.
24771      // Should be tested. Also something has to read the actual current time or the cache will
24772      // never get updated.
24773      this.cache_.currentTime = this.techGet_('currentTime') || 0;
24774      return this.cache_.currentTime;
24775    }
24776    if (seconds < 0) {
24777      seconds = 0;
24778    }
24779    if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
24780      this.cache_.initTime = seconds;
24781      this.off('canplay', this.boundApplyInitTime_);
24782      this.one('canplay', this.boundApplyInitTime_);
24783      return;
24784    }
24785    this.techCall_('setCurrentTime', seconds);
24786    this.cache_.initTime = 0;
24787    if (isFinite(seconds)) {
24788      this.cache_.currentTime = Number(seconds);
24789    }
24790  }
24791
24792  /**
24793   * Apply the value of initTime stored in cache as currentTime.
24794   *
24795   * @private
24796   */
24797  applyInitTime_() {
24798    this.currentTime(this.cache_.initTime);
24799  }
24800
24801  /**
24802   * Normally gets the length in time of the video in seconds;
24803   * in all but the rarest use cases an argument will NOT be passed to the method
24804   *
24805   * > **NOTE**: The video must have started loading before the duration can be
24806   * known, and depending on preload behaviour may not be known until the video starts
24807   * playing.
24808   *
24809   * @fires Player#durationchange
24810   *
24811   * @param {number} [seconds]
24812   *        The duration of the video to set in seconds
24813   *
24814   * @return {number|undefined}
24815   *         - The duration of the video in seconds when getting
24816   *         - Nothing when setting
24817   */
24818  duration(seconds) {
24819    if (seconds === undefined) {
24820      // return NaN if the duration is not known
24821      return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
24822    }
24823    seconds = parseFloat(seconds);
24824
24825    // Standardize on Infinity for signaling video is live
24826    if (seconds < 0) {
24827      seconds = Infinity;
24828    }
24829    if (seconds !== this.cache_.duration) {
24830      // Cache the last set value for optimized scrubbing
24831      this.cache_.duration = seconds;
24832      if (seconds === Infinity) {
24833        this.addClass('vjs-live');
24834      } else {
24835        this.removeClass('vjs-live');
24836      }
24837      if (!isNaN(seconds)) {
24838        // Do not fire durationchange unless the duration value is known.
24839        // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
24840
24841        /**
24842         * @event Player#durationchange
24843         * @type {Event}
24844         */
24845        this.trigger('durationchange');
24846      }
24847    }
24848  }
24849
24850  /**
24851   * Calculates how much time is left in the video. Not part
24852   * of the native video API.
24853   *
24854   * @return {number}
24855   *         The time remaining in seconds
24856   */
24857  remainingTime() {
24858    return this.duration() - this.currentTime();
24859  }
24860
24861  /**
24862   * A remaining time function that is intended to be used when
24863   * the time is to be displayed directly to the user.
24864   *
24865   * @return {number}
24866   *         The rounded time remaining in seconds
24867   */
24868  remainingTimeDisplay() {
24869    return Math.floor(this.duration()) - Math.floor(this.currentTime());
24870  }
24871
24872  //
24873  // Kind of like an array of portions of the video that have been downloaded.
24874
24875  /**
24876   * Get a TimeRange object with an array of the times of the video
24877   * that have been downloaded. If you just want the percent of the
24878   * video that's been downloaded, use bufferedPercent.
24879   *
24880   * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
24881   *
24882   * @return {TimeRange}
24883   *         A mock {@link TimeRanges} object (following HTML spec)
24884   */
24885  buffered() {
24886    let buffered = this.techGet_('buffered');
24887    if (!buffered || !buffered.length) {
24888      buffered = createTimeRanges$1(0, 0);
24889    }
24890    return buffered;
24891  }
24892
24893  /**
24894   * Get the TimeRanges of the media that are currently available
24895   * for seeking to.
24896   *
24897   * @see [Seekable Spec]{@link https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable}
24898   *
24899   * @return {TimeRange}
24900   *         A mock {@link TimeRanges} object (following HTML spec)
24901   */
24902  seekable() {
24903    let seekable = this.techGet_('seekable');
24904    if (!seekable || !seekable.length) {
24905      seekable = createTimeRanges$1(0, 0);
24906    }
24907    return seekable;
24908  }
24909
24910  /**
24911   * Returns whether the player is in the "seeking" state.
24912   *
24913   * @return {boolean} True if the player is in the seeking state, false if not.
24914   */
24915  seeking() {
24916    return this.techGet_('seeking');
24917  }
24918
24919  /**
24920   * Returns whether the player is in the "ended" state.
24921   *
24922   * @return {boolean} True if the player is in the ended state, false if not.
24923   */
24924  ended() {
24925    return this.techGet_('ended');
24926  }
24927
24928  /**
24929   * Returns the current state of network activity for the element, from
24930   * the codes in the list below.
vendor: 7,111 bytes, lines 24931-25149
24931   * - NETWORK_EMPTY (numeric value 0)
24932   *   The element has not yet been initialised. All attributes are in
24933   *   their initial states.
24934   * - NETWORK_IDLE (numeric value 1)
24935   *   The element's resource selection algorithm is active and has
24936   *   selected a resource, but it is not actually using the network at
24937   *   this time.
24938   * - NETWORK_LOADING (numeric value 2)
24939   *   The user agent is actively trying to download data.
24940   * - NETWORK_NO_SOURCE (numeric value 3)
24941   *   The element's resource selection algorithm is active, but it has
24942   *   not yet found a resource to use.
24943   *
24944   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
24945   * @return {number} the current network activity state
24946   */
24947  networkState() {
24948    return this.techGet_('networkState');
24949  }
24950
24951  /**
24952   * Returns a value that expresses the current state of the element
24953   * with respect to rendering the current playback position, from the
24954   * codes in the list below.
24955   * - HAVE_NOTHING (numeric value 0)
24956   *   No information regarding the media resource is available.
24957   * - HAVE_METADATA (numeric value 1)
24958   *   Enough of the resource has been obtained that the duration of the
24959   *   resource is available.
24960   * - HAVE_CURRENT_DATA (numeric value 2)
24961   *   Data for the immediate current playback position is available.
24962   * - HAVE_FUTURE_DATA (numeric value 3)
24963   *   Data for the immediate current playback position is available, as
24964   *   well as enough data for the user agent to advance the current
24965   *   playback position in the direction of playback.
24966   * - HAVE_ENOUGH_DATA (numeric value 4)
24967   *   The user agent estimates that enough data is available for
24968   *   playback to proceed uninterrupted.
24969   *
24970   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
24971   * @return {number} the current playback rendering state
24972   */
24973  readyState() {
24974    return this.techGet_('readyState');
24975  }
24976
24977  /**
24978   * Get the percent (as a decimal) of the video that's been downloaded.
24979   * This method is not a part of the native HTML video API.
24980   *
24981   * @return {number}
24982   *         A decimal between 0 and 1 representing the percent
24983   *         that is buffered 0 being 0% and 1 being 100%
24984   */
24985  bufferedPercent() {
24986    return bufferedPercent(this.buffered(), this.duration());
24987  }
24988
24989  /**
24990   * Get the ending time of the last buffered time range
24991   * This is used in the progress bar to encapsulate all time ranges.
24992   *
24993   * @return {number}
24994   *         The end of the last buffered time range
24995   */
24996  bufferedEnd() {
24997    const buffered = this.buffered();
24998    const duration = this.duration();
24999    let end = buffered.end(buffered.length - 1);
25000    if (end > duration) {
25001      end = duration;
25002    }
25003    return end;
25004  }
25005
25006  /**
25007   * Get or set the current volume of the media
25008   *
25009   * @param  {number} [percentAsDecimal]
25010   *         The new volume as a decimal percent:
25011   *         - 0 is muted/0%/off
25012   *         - 1.0 is 100%/full
25013   *         - 0.5 is half volume or 50%
25014   *
25015   * @return {number|undefined}
25016   *         The current volume as a percent when getting
25017   */
25018  volume(percentAsDecimal) {
25019    let vol;
25020    if (percentAsDecimal !== undefined) {
25021      // Force value to between 0 and 1
25022      vol = Math.max(0, Math.min(1, percentAsDecimal));
25023      this.cache_.volume = vol;
25024      this.techCall_('setVolume', vol);
25025      if (vol > 0) {
25026        this.lastVolume_(vol);
25027      }
25028      return;
25029    }
25030
25031    // Default to 1 when returning current volume.
25032    vol = parseFloat(this.techGet_('volume'));
25033    return isNaN(vol) ? 1 : vol;
25034  }
25035
25036  /**
25037   * Get the current muted state, or turn mute on or off
25038   *
25039   * @param {boolean} [muted]
25040   *        - true to mute
25041   *        - false to unmute
25042   *
25043   * @return {boolean|undefined}
25044   *         - true if mute is on and getting
25045   *         - false if mute is off and getting
25046   *         - nothing if setting
25047   */
25048  muted(muted) {
25049    if (muted !== undefined) {
25050      this.techCall_('setMuted', muted);
25051      return;
25052    }
25053    return this.techGet_('muted') || false;
25054  }
25055
25056  /**
25057   * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
25058   * indicates the state of muted on initial playback.
25059   *
25060   * ```js
25061   *   var myPlayer = videojs('some-player-id');
25062   *
25063   *   myPlayer.src("http://www.example.com/path/to/video.mp4");
25064   *
25065   *   // get, should be false
25066   *   console.log(myPlayer.defaultMuted());
25067   *   // set to true
25068   *   myPlayer.defaultMuted(true);
25069   *   // get should be true
25070   *   console.log(myPlayer.defaultMuted());
25071   * ```
25072   *
25073   * @param {boolean} [defaultMuted]
25074   *        - true to mute
25075   *        - false to unmute
25076   *
25077   * @return {boolean|undefined}
25078   *         - true if defaultMuted is on and getting
25079   *         - false if defaultMuted is off and getting
25080   *         - Nothing when setting
25081   */
25082  defaultMuted(defaultMuted) {
25083    if (defaultMuted !== undefined) {
25084      this.techCall_('setDefaultMuted', defaultMuted);
25085    }
25086    return this.techGet_('defaultMuted') || false;
25087  }
25088
25089  /**
25090   * Get the last volume, or set it
25091   *
25092   * @param  {number} [percentAsDecimal]
25093   *         The new last volume as a decimal percent:
25094   *         - 0 is muted/0%/off
25095   *         - 1.0 is 100%/full
25096   *         - 0.5 is half volume or 50%
25097   *
25098   * @return {number|undefined}
25099   *         - The current value of lastVolume as a percent when getting
25100   *         - Nothing when setting
25101   *
25102   * @private
25103   */
25104  lastVolume_(percentAsDecimal) {
25105    if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
25106      this.cache_.lastVolume = percentAsDecimal;
25107      return;
25108    }
25109    return this.cache_.lastVolume;
25110  }
25111
25112  /**
25113   * Check if current tech can support native fullscreen
25114   * (e.g. with built in controls like iOS)
25115   *
25116   * @return {boolean}
25117   *         if native fullscreen is supported
25118   */
25119  supportsFullScreen() {
25120    return this.techGet_('supportsFullScreen') || false;
25121  }
25122
25123  /**
25124   * Check if the player is in fullscreen mode or tell the player that it
25125   * is or is not in fullscreen mode.
25126   *
25127   * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
25128   * property and instead document.fullscreenElement is used. But isFullscreen is
25129   * still a valuable property for internal player workings.
25130   *
25131   * @param  {boolean} [isFS]
25132   *         Set the players current fullscreen state
25133   *
25134   * @return {boolean|undefined}
25135   *         - true if fullscreen is on and getting
25136   *         - false if fullscreen is off and getting
25137   *         - Nothing when setting
25138   */
25139  isFullscreen(isFS) {
25140    if (isFS !== undefined) {
25141      const oldValue = this.isFullscreen_;
25142      this.isFullscreen_ = Boolean(isFS);
25143
25144      // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
25145      // this is the only place where we trigger fullscreenchange events for older browsers
25146      // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
25147      if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
25148        /**
25149         
25149* @event Player#fullscreenchange
25150         * @type {Event}
25151         */
25152        this.trigger('fullscreenchange');
25153      }
25154      this.toggleFullscreenClass_();
25155      return;
25156    }
25157    return this.isFullscreen_;
25158  }
25159
25160  /**
25161   * Increase the size of the video to full screen
25162   * In some browsers, full screen is not supported natively, so it enters
25163   * "full window mode", where the video fills the browser window.
25164   * In browsers and devices that support native full screen, sometimes the
25165   * browser's default controls will be shown, and not the Video.js custom skin.
25166   * This includes most mobile devices (iOS, Android) and older versions of
25167   * Safari.
25168   *
25169   * @param  {Object} [fullscreenOptions]
25170   *         Override the player fullscreen options
25171   *
25172   * @fires Player#fullscreenchange
25173   */
25174  requestFullscreen(fullscreenOptions) {
25175    if (this.isInPictureInPicture()) {
25176      this.exitPictureInPicture();
25177    }
25178    const self = this;
25179    return new Promise((resolve, reject) => {
25180      function offHandler() {
25181        self.off('fullscreenerror', errorHandler);
25182        self.off('fullscreenchange', changeHandler);
25183      }
25184      function changeHandler() {
25185        offHandler();
25186        resolve();
25187      }
25188      function errorHandler(e, err) {
25189        offHandler();
25190        reject(err);
25191      }
25192      self.one('fullscreenchange', changeHandler);
25193      self.one('fullscreenerror', errorHandler);
25194      const promise = self.requestFullscreenHelper_(fullscreenOptions);
25195      if (promise) {
25196        promise.then(offHandler, offHandler);
25197        promise.then(resolve, reject);
25198      }
25199    });
25200  }
25201  requestFullscreenHelper_(fullscreenOptions) {
25202    let fsOptions;
25203
25204    // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
25205    // Use defaults or player configured option unless passed directly to this method.
25206    if (!this.fsApi_.prefixed) {
25207      fsOptions = (this.options_.fullscreen && this.options_.fullscreen.options) || {};
25208      if (fullscreenOptions !== undefined) {
25209        fsOptions = fullscreenOptions;
25210      }
25211    }
25212
25213    // This method works as follows:
25214    // 1. if a fullscreen api is available, use it
25215    //   1. call requestFullscreen with potential options
25216    //   2. if we got a promise from above, use it to update isFullscreen()
25217    // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
25218    //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
25219    // 3. otherwise, use "fullWindow" mode
25220    if (this.fsApi_.requestFullscreen) {
25221      const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
25222
25223      // Even on browsers with promise support this may not return a promise
25224      if (promise) {
25225        promise.then(
25226          () => this.isFullscreen(true),
vendor: 12,843 bytes, lines 25227-25619
25227          () => this.isFullscreen(false)
25228        );
25229      }
25230      return promise;
25231    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
25232      // we can't take the video.js controls fullscreen but we can go fullscreen
25233      // with native controls
25234      this.techCall_('enterFullScreen');
25235    } else {
25236      // fullscreen isn't supported so we'll just stretch the video element to
25237      // fill the viewport
25238      this.enterFullWindow();
25239    }
25240  }
25241
25242  /**
25243   * Return the video to its normal size after having been in full screen mode
25244   *
25245   * @fires Player#fullscreenchange
25246   */
25247  exitFullscreen() {
25248    const self = this;
25249    return new Promise((resolve, reject) => {
25250      function offHandler() {
25251        self.off('fullscreenerror', errorHandler);
25252        self.off('fullscreenchange', changeHandler);
25253      }
25254      function changeHandler() {
25255        offHandler();
25256        resolve();
25257      }
25258      function errorHandler(e, err) {
25259        offHandler();
25260        reject(err);
25261      }
25262      self.one('fullscreenchange', changeHandler);
25263      self.one('fullscreenerror', errorHandler);
25264      const promise = self.exitFullscreenHelper_();
25265      if (promise) {
25266        promise.then(offHandler, offHandler);
25267        // map the promise to our resolve/reject methods
25268        promise.then(resolve, reject);
25269      }
25270    });
25271  }
25272  exitFullscreenHelper_() {
25273    if (this.fsApi_.requestFullscreen) {
25274      const promise = document__default['default'][this.fsApi_.exitFullscreen]();
25275
25276      // Even on browsers with promise support this may not return a promise
25277      if (promise) {
25278        // we're splitting the promise here, so, we want to catch the
25279        // potential error so that this chain doesn't have unhandled errors
25280        silencePromise(promise.then(() => this.isFullscreen(false)));
25281      }
25282      return promise;
25283    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
25284      this.techCall_('exitFullScreen');
25285    } else {
25286      this.exitFullWindow();
25287    }
25288  }
25289
25290  /**
25291   * When fullscreen isn't supported we can stretch the
25292   * video container to as wide as the browser will let us.
25293   *
25294   * @fires Player#enterFullWindow
25295   */
25296  enterFullWindow() {
25297    this.isFullscreen(true);
25298    this.isFullWindow = true;
25299
25300    // Storing original doc overflow value to return to when fullscreen is off
25301    this.docOrigOverflow = document__default['default'].documentElement.style.overflow;
25302
25303    // Add listener for esc key to exit fullscreen
25304    on(document__default['default'], 'keydown', this.boundFullWindowOnEscKey_);
25305
25306    // Hide any scroll bars
25307    document__default['default'].documentElement.style.overflow = 'hidden';
25308
25309    // Apply fullscreen styles
25310    addClass(document__default['default'].body, 'vjs-full-window');
25311
25312    /**
25313     * @event Player#enterFullWindow
25314     * @type {Event}
25315     */
25316    this.trigger('enterFullWindow');
25317  }
25318
25319  /**
25320   * Check for call to either exit full window or
25321   * full screen on ESC key
25322   *
25323   * @param {string} event
25324   *        Event to check for key press
25325   */
25326  fullWindowOnEscKey(event) {
25327    if (event.key === 'Escape') {
25328      if (this.isFullscreen() === true) {
25329        if (!this.isFullWindow) {
25330          this.exitFullscreen();
25331        } else {
25332          this.exitFullWindow();
25333        }
25334      }
25335    }
25336  }
25337
25338  /**
25339   * Exit full window
25340   *
25341   * @fires Player#exitFullWindow
25342   */
25343  exitFullWindow() {
25344    this.isFullscreen(false);
25345    this.isFullWindow = false;
25346    off(document__default['default'], 'keydown', this.boundFullWindowOnEscKey_);
25347
25348    // Unhide scroll bars.
25349    document__default['default'].documentElement.style.overflow = this.docOrigOverflow;
25350
25351    // Remove fullscreen styles
25352    removeClass(document__default['default'].body, 'vjs-full-window');
25353
25354    // Resize the box, controller, and poster to original sizes
25355    // this.positionAll();
25356    /**
25357     * @event Player#exitFullWindow
25358     * @type {Event}
25359     */
25360    this.trigger('exitFullWindow');
25361  }
25362
25363  /**
25364   * Get or set disable Picture-in-Picture mode.
25365   *
25366   * @param {boolean} [value]
25367   *                  - true will disable Picture-in-Picture mode
25368   *                  - false will enable Picture-in-Picture mode
25369   */
25370  disablePictureInPicture(value) {
25371    if (value === undefined) {
25372      return this.techGet_('disablePictureInPicture');
25373    }
25374    this.techCall_('setDisablePictureInPicture', value);
25375    this.options_.disablePictureInPicture = value;
25376    this.trigger('disablepictureinpicturechanged');
25377  }
25378
25379  /**
25380   * Check if the player is in Picture-in-Picture mode or tell the player that it
25381   * is or is not in Picture-in-Picture mode.
25382   *
25383   * @param  {boolean} [isPiP]
25384   *         Set the players current Picture-in-Picture state
25385   *
25386   * @return {boolean|undefined}
25387   *         - true if Picture-in-Picture is on and getting
25388   *         - false if Picture-in-Picture is off and getting
25389   *         - nothing if setting
25390   */
25391  isInPictureInPicture(isPiP) {
25392    if (isPiP !== undefined) {
25393      this.isInPictureInPicture_ = !!isPiP;
25394      this.togglePictureInPictureClass_();
25395      return;
25396    }
25397    return !!this.isInPictureInPicture_;
25398  }
25399
25400  /**
25401   * Create a floating video window always on top of other windows so that users may
25402   * continue consuming media while they interact with other content sites, or
25403   * applications on their device.
25404   *
25405   * This can use document picture-in-picture or element picture in picture
25406   *
25407   * Set `enableDocumentPictureInPicture` to `true` to use docPiP on a supported browser
25408   * Else set `disablePictureInPicture` to `false` to disable elPiP on a supported browser
25409   *
25410   *
25411   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/}
25412   * @see [Spec]{@link https://wicg.github.io/document-picture-in-picture/}
25413   *
25414   * @fires Player#enterpictureinpicture
25415   *
25416   * @return {Promise}
25417   *         A promise with a Picture-in-Picture window.
25418   */
25419  requestPictureInPicture() {
25420    if (
25421      this.options_.enableDocumentPictureInPicture &&
25422      window__default['default'].documentPictureInPicture
25423    ) {
25424      const pipContainer = document__default['default'].createElement(this.el().tagName);
25425      pipContainer.classList = this.el().classList;
25426      pipContainer.classList.add('vjs-pip-container');
25427      if (this.posterImage) {
25428        pipContainer.appendChild(this.posterImage.el().cloneNode(true));
25429      }
25430      if (this.titleBar) {
25431        pipContainer.appendChild(this.titleBar.el().cloneNode(true));
25432      }
25433      pipContainer.appendChild(
25434        createEl(
25435          'p',
25436          {
25437            className: 'vjs-pip-text',
25438          },
25439          {},
25440          this.localize('Playing in picture-in-picture')
25441        )
25442      );
25443      return window__default['default'].documentPictureInPicture
25444        .requestWindow({
25445          // The aspect ratio won't be correct, Chrome bug https://crbug.com/1407629
25446          width: this.videoWidth(),
25447          height: this.videoHeight(),
25448        })
25449        .then((pipWindow) => {
25450          copyStyleSheetsToWindow(pipWindow);
25451          this.el_.parentNode.insertBefore(pipContainer, this.el_);
25452          pipWindow.document.body.appendChild(this.el_);
25453          pipWindow.document.body.classList.add('vjs-pip-window');
25454          this.player_.isInPictureInPicture(true);
25455          this.player_.trigger({
25456            type: 'enterpictureinpicture',
25457            pipWindow,
25458          });
25459
25460          // Listen for the PiP closing event to move the video back.
25461          pipWindow.addEventListener('pagehide', (event) => {
25462            const pipVideo = event.target.querySelector('.video-js');
25463            pipContainer.parentNode.replaceChild(pipVideo, pipContainer);
25464            this.player_.isInPictureInPicture(false);
25465            this.player_.trigger('leavepictureinpicture');
25466          });
25467          return pipWindow;
25468        });
25469    }
25470    if (
25471      'pictureInPictureEnabled' in document__default['default'] &&
25472      this.disablePictureInPicture() === false
25473    ) {
25474      /**
25475       * This event fires when the player enters picture in picture mode
25476       *
25477       * @event Player#enterpictureinpicture
25478       * @type {Event}
25479       */
25480      return this.techGet_('requestPictureInPicture');
25481    }
25482    return Promise.reject('No PiP mode is available');
25483  }
25484
25485  /**
25486   * Exit Picture-in-Picture mode.
25487   *
25488   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
25489   *
25490   * @fires Player#leavepictureinpicture
25491   *
25492   * @return {Promise}
25493   *         A promise.
25494   */
25495  exitPictureInPicture() {
25496    if (
25497      window__default['default'].documentPictureInPicture &&
25498      window__default['default'].documentPictureInPicture.window
25499    ) {
25500      // With documentPictureInPicture, Player#leavepictureinpicture is fired in the pagehide handler
25501      window__default['default'].documentPictureInPicture.window.close();
25502      return Promise.resolve();
25503    }
25504    if ('pictureInPictureEnabled' in document__default['default']) {
25505      /**
25506       * This event fires when the player leaves picture in picture mode
25507       *
25508       * @event Player#leavepictureinpicture
25509       * @type {Event}
25510       */
25511      return document__default['default'].exitPictureInPicture();
25512    }
25513  }
25514
25515  /**
25516   * Called when this Player has focus and a key gets pressed down, or when
25517   * any Component of this player receives a key press that it doesn't handle.
25518   * This allows player-wide hotkeys (either as defined below, or optionally
25519   * by an external function).
25520   *
25521   * @param {KeyboardEvent} event
25522   *        The `keydown` event that caused this function to be called.
25523   *
25524   * @listens keydown
25525   */
25526  handleKeyDown(event) {
25527    const { userActions } = this.options_;
25528
25529    // Bail out if hotkeys are not configured.
25530    if (!userActions || !userActions.hotkeys) {
25531      return;
25532    }
25533
25534    // Function that determines whether or not to exclude an element from
25535    // hotkeys handling.
25536    const excludeElement = (el) => {
25537      const tagName = el.tagName.toLowerCase();
25538
25539      // The first and easiest test is for `contenteditable` elements.
25540      if (el.isContentEditable) {
25541        return true;
25542      }
25543
25544      // Inputs matching these types will still trigger hotkey handling as
25545      // they are not text inputs.
25546      const allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
25547      if (tagName === 'input') {
25548        return allowedInputTypes.indexOf(el.type) === -1;
25549      }
25550
25551      // The final test is by tag name. These tags will be excluded entirely.
25552      const excludedTags = ['textarea'];
25553      return excludedTags.indexOf(tagName) !== -1;
25554    };
25555
25556    // Bail out if the user is focused on an interactive form element.
25557    if (excludeElement(this.el_.ownerDocument.activeElement)) {
25558      return;
25559    }
25560    if (typeof userActions.hotkeys === 'function') {
25561      userActions.hotkeys.call(this, event);
25562    } else {
25563      this.handleHotkeys(event);
25564    }
25565  }
25566
25567  /**
25568   * Called when this Player receives a hotkey keydown event.
25569   * Supported player-wide hotkeys are:
25570   *
25571   *   f          - toggle fullscreen
25572   *   m          - toggle mute
25573   *   k or Space - toggle play/pause
25574   *
25575   * @param {Event} event
25576   *        The `keydown` event that caused this function to be called.
25577   */
25578  handleHotkeys(event) {
25579    const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
25580
25581    // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
25582    const {
25583      fullscreenKey = (keydownEvent) => event.key.toLowerCase() === 'f',
25584      muteKey = (keydownEvent) => event.key.toLowerCase() === 'm',
25585      playPauseKey = (keydownEvent) =>
25586        event.key.toLowerCase() === 'k' || event.key.toLowerCase() === ' ',
25587    } = hotkeys;
25588    if (fullscreenKey.call(this, event)) {
25589      event.preventDefault();
25590      event.stopPropagation();
25591      const FSToggle = Component$1.getComponent('FullscreenToggle');
25592      if (document__default['default'][this.fsApi_.fullscreenEnabled] !== false) {
25593        FSToggle.prototype.handleClick.call(this, event);
25594      }
25595    } else if (muteKey.call(this, event)) {
25596      event.preventDefault();
25597      event.stopPropagation();
25598      const MuteToggle = Component$1.getComponent('MuteToggle');
25599      MuteToggle.prototype.handleClick.call(this, event);
25600    } else if (playPauseKey.call(this, event)) {
25601      event.preventDefault();
25602      event.stopPropagation();
25603      const PlayToggle = Component$1.getComponent('PlayToggle');
25604      PlayToggle.prototype.handleClick.call(this, event);
25605    }
25606  }
25607
25608  /**
25609   * Check whether the player can play a given mimetype
25610   *
25611   * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
25612   *
25613   * @param {string} type
25614   *        The mimetype to check
25615   *
25616   * @return {string}
25617   *         'probably', 'maybe', or '' (empty string)
25618   */
25619  canPlayType(type) {
25620    let can;
25621
25622    // Loop through each playback technology in the options order
25623    for (let i = 0, j = this.options_.techOrder; i < j.length; i++) {
25624      const techName = j[i];
25625      let tech = Tech.getTech(techName);
25626
25627      // Support old behavior of techs being registered as components.
25628      // Remove once that deprecated behavior is removed.
25629      if (!tech) {
25630        tech = Component$1.getComponent(techName);
25631      }
25632
25633      // Check if the current tech is defined before continuing
25634      if (!tech) {
25635        log$1.error(
25636          `The "${techName}" tech is undefined. Skipped browser support check for that tech.`
25637        );
25638        continue;
25639      }
25640
25641      // Check if the browser supports this technology
25642      if (tech.isSupported()) {
25643        can = tech.canPlayType(type);
25644        if (can) {
25645          return can;
25646        }
25647      }
25648    }
25649    return '';
25650  }
25651
25652  /**
25653   * Select source based on tech-order or source-order
25654   * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
25655   * defaults to tech-order selection
25656   *
25657   * @param {Array} sources
25658   *        The sources for a media asset
25659   *
25660   * @return {Object|boolean}
25661   *         Object of source and tech order or false
25662   */
25663  selectSource(sources) {
25664    // Get only the techs specified in `techOrder` that exist and are supported by the
25665    // current platform
25666    const techs = this.options_.techOrder
25667      .map((techName) => {
25668        return [techName, Tech.getTech(techName)];
25669      })
25670      .filter(([techName, tech]) => {
25671        // Check if the current tech is defined before continuing
25672        if (tech) {
25673          // Check if the browser supports this technology
25674          return tech.isSupported();
25675        }
25676        log$1.error(
25677          `The "${techName}" tech is undefined. Skipped browser support check for that tech.`
25678        );
25679        return false;
25680      });
25681
25682    // Iterate over each `innerArray` element once per `outerArray` element and execute
25683    // `tester` with both. If `tester` returns a non-falsy value, exit early and return
25684    // that value.
25685    const findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
25686      let found;
25687      outerArray.some((outerChoice) => {
25688        return innerArray.some((innerChoice) => {
25689          found = tester(outerChoice, innerChoice);
25690          if (found) {
25691            return true;
25692          }
25693        });
25694      });
25695      return found;
25696    };
25697    let foundSourceAndTech;
25698    const flip = (fn) => (a, b) => fn(b, a);
25699    const finder = ([techName, tech], source) => {
25700      if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
25701        return {
25702          source,
25703          tech: techName,
25704        };
25705      }
25706    };
25707
25708    // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
25709    // to select from them based on their priority.
25710    if (this.options_.sourceOrder) {
25711      // Source-first ordering
25712      foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
25713    } else {
25714      // Tech-first ordering
25715      foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
25716    }
25717    return foundSourceAndTech || false;
25718  }
25719
25720  /**
25721   * Executes source setting and getting logic
25722   *
25723   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25724   *        A SourceObject, an array of SourceObjects, or a string referencing
25725   *        a URL to a media source. It is _highly recommended_ that an object
25726   *        or array of objects is used here, so that source selection
25727   *        algorithms can take the `type` into account.
25728   *
25729   *        If not provided, this method acts as a getter.
25730   * @param {boolean} [isRetry]
25731   *        Indicates whether this is being called internally as a result of a retry
25732   *
25733   * @return {string|undefined}
25734   *         If the `source` argument is missing, returns the current source
25735   *         URL. Otherwise, returns nothing/undefined.
25736   */
25737  handleSrc_(source, isRetry) {
25738    // getter usage
25739    if (typeof source === 'undefined') {
25740      return this.cache_.src || '';
25741    }
25742
25743    // Reset retry behavior for new source
25744    if (this.resetRetryOnError_) {
25745      this.resetRetryOnError_();
25746    }
25747
25748    // filter out invalid sources and turn our source into
25749    // an array of source objects
25750    const sources = filterSource(source);
25751
vendor: 10,839 bytes, lines 25752-26123
25752    // if a source was passed in then it is invalid because
25753    // it was filtered to a zero length Array. So we have to
25754    // show an error
25755    if (!sources.length) {
25756      this.setTimeout(function () {
25757        this.error({
25758          code: 4,
25759          message: this.options_.notSupportedMessage,
25760        });
25761      }, 0);
25762      return;
25763    }
25764
25765    // initial sources
25766    this.changingSrc_ = true;
25767
25768    // Only update the cached source list if we are not retrying a new source after error,
25769    // since in that case we want to include the failed source(s) in the cache
25770    if (!isRetry) {
25771      this.cache_.sources = sources;
25772    }
25773    this.updateSourceCaches_(sources[0]);
25774
25775    // middlewareSource is the source after it has been changed by middleware
25776    setSource(this, sources[0], (middlewareSource, mws) => {
25777      this.middleware_ = mws;
25778
25779      // since sourceSet is async we have to update the cache again after we select a source since
25780      // the source that is selected could be out of order from the cache update above this callback.
25781      if (!isRetry) {
25782        this.cache_.sources = sources;
25783      }
25784      this.updateSourceCaches_(middlewareSource);
25785      const err = this.src_(middlewareSource);
25786      if (err) {
25787        if (sources.length > 1) {
25788          return this.handleSrc_(sources.slice(1));
25789        }
25790        this.changingSrc_ = false;
25791
25792        // We need to wrap this in a timeout to give folks a chance to add error event handlers
25793        this.setTimeout(function () {
25794          this.error({
25795            code: 4,
25796            message: this.options_.notSupportedMessage,
25797          });
25798        }, 0);
25799
25800        // we could not find an appropriate tech, but let's still notify the delegate that this is it
25801        // this needs a better comment about why this is needed
25802        this.triggerReady();
25803        return;
25804      }
25805      setTech(mws, this.tech_);
25806    });
25807
25808    // Try another available source if this one fails before playback.
25809    if (sources.length > 1) {
25810      const retry = () => {
25811        // Remove the error modal
25812        this.error(null);
25813        this.handleSrc_(sources.slice(1), true);
25814      };
25815      const stopListeningForErrors = () => {
25816        this.off('error', retry);
25817      };
25818      this.one('error', retry);
25819      this.one('playing', stopListeningForErrors);
25820      this.resetRetryOnError_ = () => {
25821        this.off('error', retry);
25822        this.off('playing', stopListeningForErrors);
25823      };
25824    }
25825  }
25826
25827  /**
25828   * Get or set the video source.
25829   *
25830   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25831   *        A SourceObject, an array of SourceObjects, or a string referencing
25832   *        a URL to a media source. It is _highly recommended_ that an object
25833   *        or array of objects is used here, so that source selection
25834   *        algorithms can take the `type` into account.
25835   *
25836   *        If not provided, this method acts as a getter.
25837   *
25838   * @return {string|undefined}
25839   *         If the `source` argument is missing, returns the current source
25840   *         URL. Otherwise, returns nothing/undefined.
25841   */
25842  src(source) {
25843    return this.handleSrc_(source, false);
25844  }
25845
25846  /**
25847   * Set the source object on the tech, returns a boolean that indicates whether
25848   * there is a tech that can play the source or not
25849   *
25850   * @param {Tech~SourceObject} source
25851   *        The source object to set on the Tech
25852   *
25853   * @return {boolean}
25854   *         - True if there is no Tech to playback this source
25855   *         - False otherwise
25856   *
25857   * @private
25858   */
25859  src_(source) {
25860    const sourceTech = this.selectSource([source]);
25861    if (!sourceTech) {
25862      return true;
25863    }
25864    if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
25865      this.changingSrc_ = true;
25866      // load this technology with the chosen source
25867      this.loadTech_(sourceTech.tech, sourceTech.source);
25868      this.tech_.ready(() => {
25869        this.changingSrc_ = false;
25870      });
25871      return false;
25872    }
25873
25874    // wait until the tech is ready to set the source
25875    // and set it synchronously if possible (#2326)
25876    this.ready(function () {
25877      // The setSource tech method was added with source handlers
25878      // so older techs won't support it
25879      // We need to check the direct prototype for the case where subclasses
25880      // of the tech do not support source handlers
25881      if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
25882        this.techCall_('setSource', source);
25883      } else {
25884        this.techCall_('src', source.src);
25885      }
25886      this.changingSrc_ = false;
25887    }, true);
25888    return false;
25889  }
25890
25891  /**
25892   * Add a <source> element to the <video> element.
25893   *
25894   * @param {string} srcUrl
25895   *        The URL of the video source.
25896   *
25897   * @param {string} [mimeType]
25898   *        The MIME type of the video source. Optional but recommended.
25899   *
25900   * @return {boolean}
25901   *         Returns true if the source element was successfully added, false otherwise.
25902   */
25903  addSourceElement(srcUrl, mimeType) {
25904    if (!this.tech_) {
25905      return false;
25906    }
25907    return this.tech_.addSourceElement(srcUrl, mimeType);
25908  }
25909
25910  /**
25911   * Remove a <source> element from the <video> element by its URL.
25912   *
25913   * @param {string} srcUrl
25914   *        The URL of the source to remove.
25915   *
25916   * @return {boolean}
25917   *         Returns true if the source element was successfully removed, false otherwise.
25918   */
25919  removeSourceElement(srcUrl) {
25920    if (!this.tech_) {
25921      return false;
25922    }
25923    return this.tech_.removeSourceElement(srcUrl);
25924  }
25925
25926  /**
25927   * Begin loading the src data.
25928   */
25929  load() {
25930    // Workaround to use the load method with the VHS.
25931    // Does not cover the case when the load method is called directly from the mediaElement.
25932    if (this.tech_ && this.tech_.vhs) {
25933      this.src(this.currentSource());
25934      return;
25935    }
25936    this.techCall_('load');
25937  }
25938
25939  /**
25940   * Reset the player. Loads the first tech in the techOrder,
25941   * removes all the text tracks in the existing `tech`,
25942   * and calls `reset` on the `tech`.
25943   */
25944  reset() {
25945    if (this.paused()) {
25946      this.doReset_();
25947    } else {
25948      const playPromise = this.play();
25949      silencePromise(playPromise.then(() => this.doReset_()));
25950    }
25951  }
25952  doReset_() {
25953    if (this.tech_) {
25954      this.tech_.clearTracks('text');
25955    }
25956    this.removeClass('vjs-playing');
25957    this.addClass('vjs-paused');
25958    this.resetCache_();
25959    this.poster('');
25960    this.loadTech_(this.options_.techOrder[0], null);
25961    this.techCall_('reset');
25962    this.resetControlBarUI_();
25963    this.error(null);
25964    if (this.titleBar) {
25965      this.titleBar.update({
25966        title: undefined,
25967        description: undefined,
25968      });
25969    }
25970    if (isEvented(this)) {
25971      this.trigger('playerreset');
25972    }
25973  }
25974
25975  /**
25976   * Reset Control Bar's UI by calling sub-methods that reset
25977   * all of Control Bar's components
25978   */
25979  resetControlBarUI_() {
25980    this.resetProgressBar_();
25981    this.resetPlaybackRate_();
25982    this.resetVolumeBar_();
25983  }
25984
25985  /**
25986   * Reset tech's progress so progress bar is reset in the UI
25987   */
25988  resetProgressBar_() {
25989    this.currentTime(0);
25990    const { currentTimeDisplay, durationDisplay, progressControl, remainingTimeDisplay } =
25991      this.controlBar || {};
25992    const { seekBar } = progressControl || {};
25993    if (currentTimeDisplay) {
25994      currentTimeDisplay.updateContent();
25995    }
25996    if (durationDisplay) {
25997      durationDisplay.updateContent();
25998    }
25999    if (remainingTimeDisplay) {
26000      remainingTimeDisplay.updateContent();
26001    }
26002    if (seekBar) {
26003      seekBar.update();
26004      if (seekBar.loadProgressBar) {
26005        seekBar.loadProgressBar.update();
26006      }
26007    }
26008  }
26009
26010  /**
26011   * Reset Playback ratio
26012   */
26013  resetPlaybackRate_() {
26014    this.playbackRate(this.defaultPlaybackRate());
26015    this.handleTechRateChange_();
26016  }
26017
26018  /**
26019   * Reset Volume bar
26020   */
26021  resetVolumeBar_() {
26022    this.volume(1.0);
26023    this.trigger('volumechange');
26024  }
26025
26026  /**
26027   * Returns all of the current source objects.
26028   *
26029   * @return {Tech~SourceObject[]}
26030   *         The current source objects
26031   */
26032  currentSources() {
26033    const source = this.currentSource();
26034    const sources = [];
26035
26036    // assume `{}` or `{ src }`
26037    if (Object.keys(source).length !== 0) {
26038      sources.push(source);
26039    }
26040    return this.cache_.sources || sources;
26041  }
26042
26043  /**
26044   * Returns the current source object.
26045   *
26046   * @return {Tech~SourceObject}
26047   *         The current source object
26048   */
26049  currentSource() {
26050    return this.cache_.source || {};
26051  }
26052
26053  /**
26054   * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
26055   * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
26056   *
26057   * @return {string}
26058   *         The current source
26059   */
26060  currentSrc() {
26061    return (this.currentSource() && this.currentSource().src) || '';
26062  }
26063
26064  /**
26065   * Get the current source type e.g. video/mp4
26066   * This can allow you rebuild the current source object so that you could load the same
26067   * source and tech later
26068   *
26069   * @return {string}
26070   *         The source MIME type
26071   */
26072  currentType() {
26073    return (this.currentSource() && this.currentSource().type) || '';
26074  }
26075
26076  /**
26077   * Get or set the preload attribute
26078   *
26079   * @param {'none'|'auto'|'metadata'} [value]
26080   *        Preload mode to pass to tech
26081   *
26082   * @return {string|undefined}
26083   *         - The preload attribute value when getting
26084   *         - Nothing when setting
26085   */
26086  preload(value) {
26087    if (value !== undefined) {
26088      this.techCall_('setPreload', value);
26089      this.options_.preload = value;
26090      return;
26091    }
26092    return this.techGet_('preload');
26093  }
26094
26095  /**
26096   * Get or set the autoplay option. When this is a boolean it will
26097   * modify the attribute on the tech. When this is a string the attribute on
26098   * the tech will be removed and `Player` will handle autoplay on loadstarts.
26099   *
26100   * @param {boolean|'play'|'muted'|'any'} [value]
26101   *        - true: autoplay using the browser behavior
26102   *        - false: do not autoplay
26103   *        - 'play': call play() on every loadstart
26104   *        - 'muted': call muted() then play() on every loadstart
26105   *        - 'any': call play() on every loadstart. if that fails call muted() then play().
26106   *        - *: values other than those listed here will be set `autoplay` to true
26107   *
26108   * @return {boolean|string|undefined}
26109   *         - The current value of autoplay when getting
26110   *         - Nothing when setting
26111   */
26112  autoplay(value) {
26113    // getter usage
26114    if (value === undefined) {
26115      return this.options_.autoplay || false;
26116    }
26117    let techAutoplay;
26118
26119    // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
26120    if (
26121      (typeof value === 'string' && /(any|play|muted)/.test(value)) ||
26122      (value === true && this.options_.normalizeAutoplay)
26123    
vendor: 6,874 bytes, lines 26123-26365
26123) {
26124      this.options_.autoplay = value;
26125      this.manualAutoplay_(typeof value === 'string' ? value : 'play');
26126      techAutoplay = false;
26127
26128      // any falsy value sets autoplay to false in the browser,
26129      // lets do the same
26130    } else if (!value) {
26131      this.options_.autoplay = false;
26132
26133      // any other value (ie truthy) sets autoplay to true
26134    } else {
26135      this.options_.autoplay = true;
26136    }
26137    techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
26138
26139    // if we don't have a tech then we do not queue up
26140    // a setAutoplay call on tech ready. We do this because the
26141    // autoplay option will be passed in the constructor and we
26142    // do not need to set it twice
26143    if (this.tech_) {
26144      this.techCall_('setAutoplay', techAutoplay);
26145    }
26146  }
26147
26148  /**
26149   * Set or unset the playsinline attribute.
26150   * Playsinline tells the browser that non-fullscreen playback is preferred.
26151   *
26152   * @param {boolean} [value]
26153   *        - true means that we should try to play inline by default
26154   *        - false means that we should use the browser's default playback mode,
26155   *          which in most cases is inline. iOS Safari is a notable exception
26156   *          and plays fullscreen by default.
26157   *
26158   * @return {string|undefined}
26159   *         - the current value of playsinline
26160   *         - Nothing when setting
26161   *
26162   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
26163   */
26164  playsinline(value) {
26165    if (value !== undefined) {
26166      this.techCall_('setPlaysinline', value);
26167      this.options_.playsinline = value;
26168    }
26169    return this.techGet_('playsinline');
26170  }
26171
26172  /**
26173   * Get or set the loop attribute on the video element.
26174   *
26175   * @param {boolean} [value]
26176   *        - true means that we should loop the video
26177   *        - false means that we should not loop the video
26178   *
26179   * @return {boolean|undefined}
26180   *         - The current value of loop when getting
26181   *         - Nothing when setting
26182   */
26183  loop(value) {
26184    if (value !== undefined) {
26185      this.techCall_('setLoop', value);
26186      this.options_.loop = value;
26187      return;
26188    }
26189    return this.techGet_('loop');
26190  }
26191
26192  /**
26193   * Get or set the poster image source url
26194   *
26195   * @fires Player#posterchange
26196   *
26197   * @param {string} [src]
26198   *        Poster image source URL
26199   *
26200   * @return {string|undefined}
26201   *         - The current value of poster when getting
26202   *         - Nothing when setting
26203   */
26204  poster(src) {
26205    if (src === undefined) {
26206      return this.poster_;
26207    }
26208
26209    // The correct way to remove a poster is to set as an empty string
26210    // other falsey values will throw errors
26211    if (!src) {
26212      src = '';
26213    }
26214    if (src === this.poster_) {
26215      return;
26216    }
26217
26218    // update the internal poster variable
26219    this.poster_ = src;
26220
26221    // update the tech's poster
26222    this.techCall_('setPoster', src);
26223    this.isPosterFromTech_ = false;
26224
26225    // alert components that the poster has been set
26226    /**
26227     * This event fires when the poster image is changed on the player.
26228     *
26229     * @event Player#posterchange
26230     * @type {Event}
26231     */
26232    this.trigger('posterchange');
26233  }
26234
26235  /**
26236   * Some techs (e.g. YouTube) can provide a poster source in an
26237   * asynchronous way. We want the poster component to use this
26238   * poster source so that it covers up the tech's controls.
26239   * (YouTube's play button). However we only want to use this
26240   * source if the player user hasn't set a poster through
26241   * the normal APIs.
26242   *
26243   * @fires Player#posterchange
26244   * @listens Tech#posterchange
26245   * @private
26246   */
26247  handleTechPosterChange_() {
26248    if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
26249      const newPoster = this.tech_.poster() || '';
26250      if (newPoster !== this.poster_) {
26251        this.poster_ = newPoster;
26252        this.isPosterFromTech_ = true;
26253
26254        // Let components know the poster has changed
26255        this.trigger('posterchange');
26256      }
26257    }
26258  }
26259
26260  /**
26261   * Get or set whether or not the controls are showing.
26262   *
26263   * @fires Player#controlsenabled
26264   *
26265   * @param {boolean} [bool]
26266   *        - true to turn controls on
26267   *        - false to turn controls off
26268   *
26269   * @return {boolean|undefined}
26270   *         - The current value of controls when getting
26271   *         - Nothing when setting
26272   */
26273  controls(bool) {
26274    if (bool === undefined) {
26275      return !!this.controls_;
26276    }
26277    bool = !!bool;
26278
26279    // Don't trigger a change event unless it actually changed
26280    if (this.controls_ === bool) {
26281      return;
26282    }
26283    this.controls_ = bool;
26284    if (this.usingNativeControls()) {
26285      this.techCall_('setControls', bool);
26286    }
26287    if (this.controls_) {
26288      this.removeClass('vjs-controls-disabled');
26289      this.addClass('vjs-controls-enabled');
26290      /**
26291       * @event Player#controlsenabled
26292       * @type {Event}
26293       */
26294      this.trigger('controlsenabled');
26295      if (!this.usingNativeControls()) {
26296        this.addTechControlsListeners_();
26297      }
26298    } else {
26299      this.removeClass('vjs-controls-enabled');
26300      this.addClass('vjs-controls-disabled');
26301      /**
26302       * @event Player#controlsdisabled
26303       * @type {Event}
26304       */
26305      this.trigger('controlsdisabled');
26306      if (!this.usingNativeControls()) {
26307        this.removeTechControlsListeners_();
26308      }
26309    }
26310  }
26311
26312  /**
26313   * Toggle native controls on/off. Native controls are the controls built into
26314   * devices (e.g. default iPhone controls) or other techs
26315   * (e.g. Vimeo Controls)
26316   * **This should only be set by the current tech, because only the tech knows
26317   * if it can support native controls**
26318   *
26319   * @fires Player#usingnativecontrols
26320   * @fires Player#usingcustomcontrols
26321   *
26322   * @param {boolean} [bool]
26323   *        - true to turn native controls on
26324   *        - false to turn native controls off
26325   *
26326   * @return {boolean|undefined}
26327   *         - The current value of native controls when getting
26328   *         - Nothing when setting
26329   */
26330  usingNativeControls(bool) {
26331    if (bool === undefined) {
26332      return !!this.usingNativeControls_;
26333    }
26334    bool = !!bool;
26335
26336    // Don't trigger a change event unless it actually changed
26337    if (this.usingNativeControls_ === bool) {
26338      return;
26339    }
26340    this.usingNativeControls_ = bool;
26341    if (this.usingNativeControls_) {
26342      this.addClass('vjs-using-native-controls');
26343
26344      /**
26345       * player is using the native device controls
26346       *
26347       * @event Player#usingnativecontrols
26348       * @type {Event}
26349       */
26350      this.trigger('usingnativecontrols');
26351    } else {
26352      this.removeClass('vjs-using-native-controls');
26353
26354      /**
26355       * player is using the custom HTML controls
26356       *
26357       * @event Player#usingcustomcontrols
26358       * @type {Event}
26359       */
26360      this.trigger('usingcustomcontrols');
26361    }
26362  }
26363
26364  /**
26365   * Set
26365 or get the current MediaError
26366   *
26367   * @fires Player#error
26368   *
26369   * @param  {MediaError|string|number} [err]
26370   *         A MediaError or a string/number to be turned
26371   *         into a MediaError
26372   *
26373   * @return {MediaError|null|undefined}
26374   *         - The current MediaError when getting (or null)
26375   *         - Nothing when setting
26376   */
26377  error(err) {
26378    if (err === undefined) {
26379      return this.error_ || null;
26380    }
26381
26382    // allow hooks to modify error object
vendor: 1,769 bytes, lines 26383-26442
26383    hooks('beforeerror').forEach((hookFunction) => {
26384      const newErr = hookFunction(this, err);
26385      if (
26386        !(
26387          (isObject(newErr) && !Array.isArray(newErr)) ||
26388          typeof newErr === 'string' ||
26389          typeof newErr === 'number' ||
26390          newErr === null
26391        )
26392      ) {
26393        this.log.error('please return a value that MediaError expects in beforeerror hooks');
26394        return;
26395      }
26396      err = newErr;
26397    });
26398
26399    // Suppress the first error message for no compatible source until
26400    // user interaction
26401    if (this.options_.suppressNotSupportedError && err && err.code === 4) {
26402      const triggerSuppressedError = function () {
26403        this.error(err);
26404      };
26405      this.options_.suppressNotSupportedError = false;
26406      this.any(['click', 'touchstart'], triggerSuppressedError);
26407      this.one('loadstart', function () {
26408        this.off(['click', 'touchstart'], triggerSuppressedError);
26409      });
26410      return;
26411    }
26412
26413    // restoring to default
26414    if (err === null) {
26415      this.error_ = null;
26416      this.removeClass('vjs-error');
26417      if (this.errorDisplay) {
26418        this.errorDisplay.close();
26419      }
26420      return;
26421    }
26422    this.error_ = new MediaError(err);
26423
26424    // add the vjs-error classname to the player
26425    this.addClass('vjs-error');
26426
26427    // log the name of the error type and any message
26428    // IE11 logs "[object object]" and required you to expand message to see error object
26429    log$1.error(
26430      `(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`,
26431      this.error_.message,
26432      this.error_
26433    );
26434
26435    /**
26436     * @event Player#error
26437     * @type {Event}
26438     */
26439    this.trigger('error');
26440
26441    // notify hooks of the per player error
26442    hooks('error').forEach((hookFunction)
vendor: 8,927 bytes, lines 26442-26705
26442 => hookFunction(this, this.error_));
26443    return;
26444  }
26445
26446  /**
26447   * Report user activity
26448   *
26449   * @param {Object} event
26450   *        Event object
26451   */
26452  reportUserActivity(event) {
26453    this.userActivity_ = true;
26454  }
26455
26456  /**
26457   * Get/set if user is active
26458   *
26459   * @fires Player#useractive
26460   * @fires Player#userinactive
26461   *
26462   * @param {boolean} [bool]
26463   *        - true if the user is active
26464   *        - false if the user is inactive
26465   *
26466   * @return {boolean|undefined}
26467   *         - The current value of userActive when getting
26468   *         - Nothing when setting
26469   */
26470  userActive(bool) {
26471    if (bool === undefined) {
26472      return this.userActive_;
26473    }
26474    bool = !!bool;
26475    if (bool === this.userActive_) {
26476      return;
26477    }
26478    this.userActive_ = bool;
26479    if (this.userActive_) {
26480      this.userActivity_ = true;
26481      this.removeClass('vjs-user-inactive');
26482      this.addClass('vjs-user-active');
26483      /**
26484       * @event Player#useractive
26485       * @type {Event}
26486       */
26487      this.trigger('useractive');
26488      return;
26489    }
26490
26491    // Chrome/Safari/IE have bugs where when you change the cursor it can
26492    // trigger a mousemove event. This causes an issue when you're hiding
26493    // the cursor when the user is inactive, and a mousemove signals user
26494    // activity. Making it impossible to go into inactive mode. Specifically
26495    // this happens in fullscreen when we really need to hide the cursor.
26496    //
26497    // When this gets resolved in ALL browsers it can be removed
26498    // https://code.google.com/p/chromium/issues/detail?id=103041
26499    if (this.tech_) {
26500      this.tech_.one('mousemove', function (e) {
26501        e.stopPropagation();
26502        e.preventDefault();
26503      });
26504    }
26505    this.userActivity_ = false;
26506    this.removeClass('vjs-user-active');
26507    this.addClass('vjs-user-inactive');
26508    /**
26509     * @event Player#userinactive
26510     * @type {Event}
26511     */
26512    this.trigger('userinactive');
26513  }
26514
26515  /**
26516   * Listen for user activity based on timeout value
26517   *
26518   * @private
26519   */
26520  listenForUserActivity_() {
26521    let mouseInProgress;
26522    let lastMoveX;
26523    let lastMoveY;
26524    const handleActivity = bind_(this, this.reportUserActivity);
26525    const handleMouseMove = function (e) {
26526      // #1068 - Prevent mousemove spamming
26527      // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
26528      if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
26529        lastMoveX = e.screenX;
26530        lastMoveY = e.screenY;
26531        handleActivity();
26532      }
26533    };
26534    const handleMouseDown = function () {
26535      handleActivity();
26536      // For as long as the they are touching the device or have their mouse down,
26537      // we consider them active even if they're not moving their finger or mouse.
26538      // So we want to continue to update that they are active
26539      this.clearInterval(mouseInProgress);
26540      // Setting userActivity=true now and setting the interval to the same time
26541      // as the activityCheck interval (250) should ensure we never miss the
26542      // next activityCheck
26543      mouseInProgress = this.setInterval(handleActivity, 250);
26544    };
26545    const handleMouseUpAndMouseLeave = function (event) {
26546      handleActivity();
26547      // Stop the interval that maintains activity if the mouse/touch is down
26548      this.clearInterval(mouseInProgress);
26549    };
26550
26551    // Any mouse movement will be considered user activity
26552    this.on('mousedown', handleMouseDown);
26553    this.on('mousemove', handleMouseMove);
26554    this.on('mouseup', handleMouseUpAndMouseLeave);
26555    this.on('mouseleave', handleMouseUpAndMouseLeave);
26556    const controlBar = this.getChild('controlBar');
26557
26558    // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
26559    // controlBar would no longer be hidden by default timeout.
26560    if (controlBar && !IS_IOS && !IS_ANDROID) {
26561      controlBar.on('mouseenter', function (event) {
26562        if (this.player().options_.inactivityTimeout !== 0) {
26563          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
26564        }
26565        this.player().options_.inactivityTimeout = 0;
26566      });
26567      controlBar.on('mouseleave', function (event) {
26568        this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
26569      });
26570    }
26571
26572    // Listen for keyboard navigation
26573    // Shouldn't need to use inProgress interval because of key repeat
26574    this.on('keydown', handleActivity);
26575    this.on('keyup', handleActivity);
26576
26577    // Run an interval every 250 milliseconds instead of stuffing everything into
26578    // the mousemove/touchmove function itself, to prevent performance degradation.
26579    // `this.reportUserActivity` simply sets this.userActivity_ to true, which
26580    // then gets picked up by this loop
26581    // http://ejohn.org/blog/learning-from-twitter/
26582    let inactivityTimeout;
26583
26584    /** @this Player */
26585    const activityCheck = function () {
26586      // Check to see if mouse/touch activity has happened
26587      if (!this.userActivity_) {
26588        return;
26589      }
26590
26591      // Reset the activity tracker
26592      this.userActivity_ = false;
26593
26594      // If the user state was inactive, set the state to active
26595      this.userActive(true);
26596
26597      // Clear any existing inactivity timeout to start the timer over
26598      this.clearTimeout(inactivityTimeout);
26599      const timeout = this.options_.inactivityTimeout;
26600      if (timeout <= 0) {
26601        return;
26602      }
26603
26604      // In <timeout> milliseconds, if no more activity has occurred the
26605      // user will be considered inactive
26606      inactivityTimeout = this.setTimeout(function () {
26607        // Protect against the case where the inactivityTimeout can trigger just
26608        // before the next user activity is picked up by the activity check loop
26609        // causing a flicker
26610        if (!this.userActivity_) {
26611          this.userActive(false);
26612        }
26613      }, timeout);
26614    };
26615    this.setInterval(activityCheck, 250);
26616  }
26617
26618  /**
26619   * Gets or sets the current playback rate. A playback rate of
26620   * 1.0 represents normal speed and 0.5 would indicate half-speed
26621   * playback, for instance.
26622   *
26623   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
26624   *
26625   * @param {number} [rate]
26626   *       New playback rate to set.
26627   *
26628   * @return {number|undefined}
26629   *         - The current playback rate when getting or 1.0
26630   *         - Nothing when setting
26631   */
26632  playbackRate(rate) {
26633    if (rate !== undefined) {
26634      // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
26635      // that is registered above
26636      this.techCall_('setPlaybackRate', rate);
26637      return;
26638    }
26639    if (this.tech_ && this.tech_.featuresPlaybackRate) {
26640      return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
26641    }
26642    return 1.0;
26643  }
26644
26645  /**
26646   * Gets or sets the current default playback rate. A default playback rate of
26647   * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
26648   * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
26649   * not the current playbackRate.
26650   *
26651   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
26652   *
26653   * @param {number} [rate]
26654   *       New default playback rate to set.
26655   *
26656   * @return {number|undefined}
26657   *         - The default playback rate when getting or 1.0
26658   *         - Nothing when setting
26659   */
26660  defaultPlaybackRate(rate) {
26661    if (rate !== undefined) {
26662      return this.techCall_('setDefaultPlaybackRate', rate);
26663    }
26664    if (this.tech_ && this.tech_.featuresPlaybackRate) {
26665      return this.techGet_('defaultPlaybackRate');
26666    }
26667    return 1.0;
26668  }
26669
26670  /**
26671   * Gets or sets the audio flag
26672   *
26673   * @param {boolean} [bool]
26674   *        - true signals that this is an audio player
26675   *        - false signals that this is not an audio player
26676   *
26677   * @return {boolean|undefined}
26678   *         - The current value of isAudio when getting
26679   *         - Nothing when setting
26680   */
26681  isAudio(bool) {
26682    if (bool !== undefined) {
26683      this.isAudio_ = !!bool;
26684      return;
26685    }
26686    return !!this.isAudio_;
26687  }
26688  updatePlayerHeightOnAudioOnlyMode_() {
26689    const controlBar = this.getChild('ControlBar');
26690    if (!controlBar || this.audioOnlyCache_.controlBarHeight === controlBar.currentHeight()) {
26691      return;
26692    }
26693    this.audioOnlyCache_.controlBarHeight = controlBar.currentHeight();
26694    this.height(this.audioOnlyCache_.controlBarHeight);
26695  }
26696  enableAudioOnlyUI_() {
26697    // Update styling immediately to show the control bar so we can get its height
26698    this.addClass('vjs-audio-only-mode');
26699    const playerChildren = this.children();
26700    const controlBar = this.getChild('ControlBar');
26701    const controlBarHeight = controlBar && controlBar.currentHeight();
26702
26703    // Hide all player components except the control bar. Control bar components
26704    // needed only for video are hidden with CSS
26705    playerChildren.forEach(
26705(child) => {
26706      if (child === controlBar) {
26707        return;
26708      }
26709      if (child.el_ && !child.hasClass('vjs-hidden')) {
26710        child.hide();
26711        this.audioOnlyCache_.hiddenChildren.push(child);
26712      }
26713    });
vendor: 6,815 bytes, lines 26714-26930
26714    this.audioOnlyCache_.playerHeight = this.currentHeight();
26715    this.audioOnlyCache_.controlBarHeight = controlBarHeight;
26716    this.on('playerresize', this.boundUpdatePlayerHeightOnAudioOnlyMode_);
26717
26718    // Set the player height the same as the control bar
26719    this.height(controlBarHeight);
26720    this.trigger('audioonlymodechange');
26721  }
26722  disableAudioOnlyUI_() {
26723    this.removeClass('vjs-audio-only-mode');
26724    this.off('playerresize', this.boundUpdatePlayerHeightOnAudioOnlyMode_);
26725
26726    // Show player components that were previously hidden
26727    this.audioOnlyCache_.hiddenChildren.forEach((child) => child.show());
26728
26729    // Reset player height
26730    this.height(this.audioOnlyCache_.playerHeight);
26731    this.trigger('audioonlymodechange');
26732  }
26733
26734  /**
26735   * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
26736   *
26737   * Setting this to `true` will hide all player components except the control bar,
26738   * as well as control bar components needed only for video.
26739   *
26740   * @param {boolean} [value]
26741   *         The value to set audioOnlyMode to.
26742   *
26743   * @return {Promise|boolean}
26744   *        A Promise is returned when setting the state, and a boolean when getting
26745   *        the present state
26746   */
26747  audioOnlyMode(value) {
26748    if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
26749      return this.audioOnlyMode_;
26750    }
26751    this.audioOnlyMode_ = value;
26752
26753    // Enable Audio Only Mode
26754    if (value) {
26755      const exitPromises = [];
26756
26757      // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
26758      if (this.isInPictureInPicture()) {
26759        exitPromises.push(this.exitPictureInPicture());
26760      }
26761      if (this.isFullscreen()) {
26762        exitPromises.push(this.exitFullscreen());
26763      }
26764      if (this.audioPosterMode()) {
26765        exitPromises.push(this.audioPosterMode(false));
26766      }
26767      return Promise.all(exitPromises).then(() => this.enableAudioOnlyUI_());
26768    }
26769
26770    // Disable Audio Only Mode
26771    return Promise.resolve().then(() => this.disableAudioOnlyUI_());
26772  }
26773  enablePosterModeUI_() {
26774    // Hide the video element and show the poster image to enable posterModeUI
26775    const tech = this.tech_ && this.tech_;
26776    tech.hide();
26777    this.addClass('vjs-audio-poster-mode');
26778    this.trigger('audiopostermodechange');
26779  }
26780  disablePosterModeUI_() {
26781    // Show the video element and hide the poster image to disable posterModeUI
26782    const tech = this.tech_ && this.tech_;
26783    tech.show();
26784    this.removeClass('vjs-audio-poster-mode');
26785    this.trigger('audiopostermodechange');
26786  }
26787
26788  /**
26789   * Get the current audioPosterMode state or set audioPosterMode to true or false
26790   *
26791   * @param {boolean} [value]
26792   *         The value to set audioPosterMode to.
26793   *
26794   * @return {Promise|boolean}
26795   *         A Promise is returned when setting the state, and a boolean when getting
26796   *        the present state
26797   */
26798  audioPosterMode(value) {
26799    if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
26800      return this.audioPosterMode_;
26801    }
26802    this.audioPosterMode_ = value;
26803    if (value) {
26804      if (this.audioOnlyMode()) {
26805        const audioOnlyModePromise = this.audioOnlyMode(false);
26806        return audioOnlyModePromise.then(() => {
26807          // enable audio poster mode after audio only mode is disabled
26808          this.enablePosterModeUI_();
26809        });
26810      }
26811      return Promise.resolve().then(() => {
26812        // enable audio poster mode
26813        this.enablePosterModeUI_();
26814      });
26815    }
26816    return Promise.resolve().then(() => {
26817      // disable audio poster mode
26818      this.disablePosterModeUI_();
26819    });
26820  }
26821
26822  /**
26823   * A helper method for adding a {@link TextTrack} to our
26824   * {@link TextTrackList}.
26825   *
26826   * In addition to the W3C settings we allow adding additional info through options.
26827   *
26828   * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
26829   *
26830   * @param {string} [kind]
26831   *        the kind of TextTrack you are adding
26832   *
26833   * @param {string} [label]
26834   *        the label to give the TextTrack label
26835   *
26836   * @param {string} [language]
26837   *        the language to set on the TextTrack
26838   *
26839   * @return {TextTrack|undefined}
26840   *         the TextTrack that was added or undefined
26841   *         if there is no tech
26842   */
26843  addTextTrack(kind, label, language) {
26844    if (this.tech_) {
26845      return this.tech_.addTextTrack(kind, label, language);
26846    }
26847  }
26848
26849  /**
26850   * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
26851   *
26852   * @param {Object} options
26853   *        Options to pass to {@link HTMLTrackElement} during creation. See
26854   *        {@link HTMLTrackElement} for object properties that you should use.
26855   *
26856   * @param {boolean} [manualCleanup=false] if set to true, the TextTrack will not be removed
26857   *                                        from the TextTrackList and HtmlTrackElementList
26858   *                                        after a source change
26859   *
26860   * @return {HtmlTrackElement}
26861   *         the HTMLTrackElement that was created and added
26862   *         to the HtmlTrackElementList and the remote
26863   *         TextTrackList
26864   *
26865   */
26866  addRemoteTextTrack(options, manualCleanup) {
26867    if (this.tech_) {
26868      return this.tech_.addRemoteTextTrack(options, manualCleanup);
26869    }
26870  }
26871
26872  /**
26873   * Remove a remote {@link TextTrack} from the respective
26874   * {@link TextTrackList} and {@link HtmlTrackElementList}.
26875   *
26876   * @param {Object} track
26877   *        Remote {@link TextTrack} to remove
26878   *
26879   * @return {undefined}
26880   *         does not return anything
26881   */
26882  removeRemoteTextTrack(obj = {}) {
26883    let { track } = obj;
26884    if (!track) {
26885      track = obj;
26886    }
26887
26888    // destructure the input into an object with a track argument, defaulting to arguments[0]
26889    // default the whole argument to an empty object if nothing was passed in
26890
26891    if (this.tech_) {
26892      return this.tech_.removeRemoteTextTrack(track);
26893    }
26894  }
26895
26896  /**
26897   * Gets available media playback quality metrics as specified by the W3C's Media
26898   * Playback Quality API.
26899   *
26900   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
26901   *
26902   * @return {Object|undefined}
26903   *         An object with supported media playback quality metrics or undefined if there
26904   *         is no tech or the tech does not support it.
26905   */
26906  getVideoPlaybackQuality() {
26907    return this.techGet_('getVideoPlaybackQuality');
26908  }
26909
26910  /**
26911   * Get video width
26912   *
26913   * @return {number}
26914   *         current video width
26915   */
26916  videoWidth() {
26917    return (this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth()) || 0;
26918  }
26919
26920  /**
26921   * Get video height
26922   *
26923   * @return {number}
26924   *         current video height
26925   */
26926  videoHeight() {
26927    return (this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight()) || 0;
26928  }
26929
26930  /**
26931   * Set or get the player's language code.
26932   *
26933   * Changing the language will trigger
26934   * [languagechange]{@link Player#event:languagechange}
26935   * which Components can use to update control text.
26936   * ClickableComponent will update its control text by default on
26937   * [languagechange]{@link Player#event:languagechange}.
26938   *
26939   * @fires Player#languagechange
26940   *
26941   * @param {string} [code]
26942   *        the language code to set the player to
26943   *
26944   * @return {string|undefined}
26945   *         - The current language code when getting
26946   *         - Nothing when setting
26947   */
26948  language(code) {
26949    if (code === undefined) {
26950      return this.language_;
26951    }
26952    if (this.language_ !== String(code).toLowerCase()) {
26953      this.language_ = String(code).toLowerCase();
26954
26955      // during first init, it's possible some things won't be evented
26956      if (isEvented(this)) {
26957        /**
26958         * fires when the player language change
26959         *
26960         * @event Player#languagechange
26961         * @type {Event}
26962         */
26963        this.trigger('languagechange');
26964      }
26965    }
26966  }
26967
26968  /**
26969   * Get the player's language dictionary
26970   * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
26971   * Languages specified directly in the player options have precedence
26972   *
26973   * @return {Array}
26974   *         An array of of supported languages
26975   */
26976  languages() {
26977    return merge$1(Player.prototype.options_.languages, this.languages_);
26978  }
26979
26980  /**
26981   * returns a JavaScript object representing the current track
26982   * information. **DOES not return it as JSON**
26983   *
26984   * @return {Object}
26985   *         Object representing the current of track info
26986   */
26987  toJSON() {
26988    const options = merge$1(this.options_);
26989    const tracks = options.tracks;
26990    options.tracks = [];
26991    for (let i = 0; i < tracks.length; i++) {
26992      let track = tracks[i];
26993
26994      // deep merge tracks and null out player so no circular references
26995      track = merge$1(track);
26996      track.player = undefined;
26997      options.tracks[i] = track;
26998    }
26999    return options;
27000  }
27001
27002  /**
27003   * Creates a simple modal dialog (an instance of the {@link ModalDialog}
27004   * component) that immediately overlays the player with arbitrary
27005   * content and removes itself when closed.
27006   *
27007   * @param {string|Function|Element|Array|null} content
27008   *        Same as {@link ModalDialog#content}'s param of the same name.
27009   *        The most straight-forward usage is to provide a string or DOM
27010   *        element.
27011   *
27012   * @param {Object} [options]
27013   *        Extra options which will be passed on to the {@link ModalDialog}.
27014   *
27015   * @return {ModalDialog}
27016   *         the {@link ModalDialog} that was created
27017   */
27018  createModal(content, options) {
27019    options = options || {};
27020    options.content = content || '';
27021    const modal = new ModalDialog(this, options);
27022    this.addChild(modal);
27023    modal.on('dispose', () => {
27024      this.removeChild(modal);
27025    });
27026    modal.open();
27027    return modal;
27028  }
27029
27030  /**
27031   * Change breakpoint classes when the player resizes.
27032   *
27033   * @private
27034   */
27035  updateCurrentBreakpoint_() {
27036    if (!this.responsive()) {
27037      return;
27038    }
27039    const currentBreakpoint = this.currentBreakpoint();
27040    const currentWidth = this.currentWidth();
27041    for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
27042      const candidateBreakpoint = BREAKPOINT_ORDER[i];
27043      const maxWidth = this.breakpoints_[candidateBreakpoint];
27044      if (currentWidth <= maxWidth) {
vendor: 5,631 bytes, lines 27045-27218
27045        // The current breakpoint did not change, nothing to do.
27046        if (currentBreakpoint === candidateBreakpoint) {
27047          return;
27048        }
27049
27050        // Only remove a class if there is a current breakpoint.
27051        if (currentBreakpoint) {
27052          this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
27053        }
27054        this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
27055        this.breakpoint_ = candidateBreakpoint;
27056        break;
27057      }
27058    }
27059  }
27060
27061  /**
27062   * Removes the current breakpoint.
27063   *
27064   * @private
27065   */
27066  removeCurrentBreakpoint_() {
27067    const className = this.currentBreakpointClass();
27068    this.breakpoint_ = '';
27069    if (className) {
27070      this.removeClass(className);
27071    }
27072  }
27073
27074  /**
27075   * Get or set breakpoints on the player.
27076   *
27077   * Calling this method with an object or `true` will remove any previous
27078   * custom breakpoints and start from the defaults again.
27079   *
27080   * @param  {Object|boolean} [breakpoints]
27081   *         If an object is given, it can be used to provide custom
27082   *         breakpoints. If `true` is given, will set default breakpoints.
27083   *         If this argument is not given, will simply return the current
27084   *         breakpoints.
27085   *
27086   * @param  {number} [breakpoints.tiny]
27087   *         The maximum width for the "vjs-layout-tiny" class.
27088   *
27089   * @param  {number} [breakpoints.xsmall]
27090   *         The maximum width for the "vjs-layout-x-small" class.
27091   *
27092   * @param  {number} [breakpoints.small]
27093   *         The maximum width for the "vjs-layout-small" class.
27094   *
27095   * @param  {number} [breakpoints.medium]
27096   *         The maximum width for the "vjs-layout-medium" class.
27097   *
27098   * @param  {number} [breakpoints.large]
27099   *         The maximum width for the "vjs-layout-large" class.
27100   *
27101   * @param  {number} [breakpoints.xlarge]
27102   *         The maximum width for the "vjs-layout-x-large" class.
27103   *
27104   * @param  {number} [breakpoints.huge]
27105   *         The maximum width for the "vjs-layout-huge" class.
27106   *
27107   * @return {Object}
27108   *         An object mapping breakpoint names to maximum width values.
27109   */
27110  breakpoints(breakpoints) {
27111    // Used as a getter.
27112    if (breakpoints === undefined) {
27113      return Object.assign(this.breakpoints_);
27114    }
27115    this.breakpoint_ = '';
27116    this.breakpoints_ = Object.assign({}, DEFAULT_BREAKPOINTS, breakpoints);
27117
27118    // When breakpoint definitions change, we need to update the currently
27119    // selected breakpoint.
27120    this.updateCurrentBreakpoint_();
27121
27122    // Clone the breakpoints before returning.
27123    return Object.assign(this.breakpoints_);
27124  }
27125
27126  /**
27127   * Get or set a flag indicating whether or not this player should adjust
27128   * its UI based on its dimensions.
27129   *
27130   * @param  {boolean} [value]
27131   *         Should be `true` if the player should adjust its UI based on its
27132   *         dimensions; otherwise, should be `false`.
27133   *
27134   * @return {boolean|undefined}
27135   *         Will be `true` if this player should adjust its UI based on its
27136   *         dimensions; otherwise, will be `false`.
27137   *         Nothing if setting
27138   */
27139  responsive(value) {
27140    // Used as a getter.
27141    if (value === undefined) {
27142      return this.responsive_;
27143    }
27144    value = Boolean(value);
27145    const current = this.responsive_;
27146
27147    // Nothing changed.
27148    if (value === current) {
27149      return;
27150    }
27151
27152    // The value actually changed, set it.
27153    this.responsive_ = value;
27154
27155    // Start listening for breakpoints and set the initial breakpoint if the
27156    // player is now responsive.
27157    if (value) {
27158      this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
27159      this.updateCurrentBreakpoint_();
27160
27161      // Stop listening for breakpoints if the player is no longer responsive.
27162    } else {
27163      this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
27164      this.removeCurrentBreakpoint_();
27165    }
27166    return value;
27167  }
27168
27169  /**
27170   * Get current breakpoint name, if any.
27171   *
27172   * @return {string}
27173   *         If there is currently a breakpoint set, returns a the key from the
27174   *         breakpoints object matching it. Otherwise, returns an empty string.
27175   */
27176  currentBreakpoint() {
27177    return this.breakpoint_;
27178  }
27179
27180  /**
27181   * Get the current breakpoint class name.
27182   *
27183   * @return {string}
27184   *         The matching class name (e.g. `"vjs-layout-tiny"` or
27185   *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
27186   *         there is no current breakpoint.
27187   */
27188  currentBreakpointClass() {
27189    return BREAKPOINT_CLASSES[this.breakpoint_] || '';
27190  }
27191
27192  /**
27193   * An object that describes a single piece of media.
27194   *
27195   * Properties that are not part of this type description will be retained; so,
27196   * this can be viewed as a generic metadata storage mechanism as well.
27197   *
27198   * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
27199   * @typedef  {Object} Player~MediaObject
27200   *
27201   * @property {string} [album]
27202   *           Unused, except if this object is passed to the `MediaSession`
27203   *           API.
27204   *
27205   * @property {string} [artist]
27206   *           Unused, except if this object is passed to the `MediaSession`
27207   *           API.
27208   *
27209   * @property {Object[]} [artwork]
27210   *           Unused, except if this object is passed to the `MediaSession`
27211   *           API. If not specified, will be populated via the `poster`, if
27212   *           available.
27213   *
27214   * @property {string} [poster]
27215   *           URL to an image that will display before playback.
27216   *
27217   * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
27218   *           A single source object, an array of source objects, or a string
27219   *           referencing a URL to a media source. It is _highly recommended_
27220   *           that an object or array of objects is used here, so that source
27221   *           selection algorithms can take the `type` into account.
27222   *
27223   * @property {string} [title]
27224   *           Unused, except if this object is passed to the `MediaSession`
27225   *           API.
27226   *
27227   * @property {Object[]} [textTracks]
27228   *           An array of objects to be used to create text tracks, following
27229   *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
27230   *           For ease of removal, these will be created as "remote" text
27231   *           tracks and set to automatically clean up on source changes.
27232   *
27233   *           These objects may have properties like `src`, `kind`, `label`,
27234   *           and `language`, see {@link Tech#createRemoteTextTrack}.
27235   */
27236
27237  /**
27238   * Populate the player using a {@link Player~MediaObject|MediaObject}.
27239   *
27240   * @param  {Player~MediaObject} media
27241   *         A media object.
27242   *
27243   * @param  {Function} ready
27244   *         A callback to be called when the player is ready.
27245   */
27246  loadMedia(media, ready) {
27247    if (!media || typeof media !== 'object') {
27248      return;
27249    }
27250    const crossOrigin = this.crossOrigin();
27251    this.reset();
27252
27253    // Clone the media object so it cannot be mutated from outside.
27254    this.cache_.media = merge$1(media);
27255    const { artist, artwork, description, poster, src, textTracks, title } = this.cache_.media;
27256
27257    // If `artwork` is not given, create it using `poster`.
27258    if (!artwork && poster) {
27259      this.cache_.media.artwork = [
27260        {
27261          src: poster,
27262          type: getMimetype(poster),
27263        },
27264      ];
27265    }
27266    if (crossOrigin) {
27267      this.crossOrigin(crossOrigin);
27268    }
27269    if (src) {
27270      this.src(src);
27271    }
27272    if (poster) {
27273      this.poster(poster);
27274    }
27275    if (Array.isArray(textTracks)) {
27276      textTracks.forEach((tt) => this.addRemoteTextTrack(tt, false));
27277    }
27278    if (this.titleBar) {
27279      this.titleBar.update({
27280        title,
27281        description: description || artist || '',
27282      });
27283    }
27284    this.ready(ready);
27285  }
27286
27287  /**
27288   * Get a clone of the current {@link Player~MediaObject} for this player.
27289   *
27290   * If the `loadMedia` method has not been used, will attempt to return a
27291   * {@link Player~MediaObject} based on the current state of the player.
27292   *
27293   * @return {Player~MediaObject}
27294   */
27295  getMedia() {
27296    if (!this.cache_.media) {
27297      const poster = this.poster();
27298      const src = this.currentSources();
27299      const textTracks = Array.prototype.map.call(this.remoteTextTracks(), (tt) => ({
27300        kind: tt.kind,
27301        label: tt.label,
27302        language: tt.language,
27303        src: tt.src,
27304      }));
27305      const media = {
27306        src,
27307        textTracks,
27308      };
27309      if (poster) {
27310        media.poster = poster;
27311        media.artwork = [
27312          {
27313            src: media.poster,
27314            type: getMimetype(media.poster),
27315          },
27316        ];
27317      }
27318      return media;
27319    }
27320    return merge$1(this.cache_.media);
27321  }
27322
27323  /**
27324   * Gets tag settings
27325   *
27326   * @param {Element} tag
27327   *        The player tag
27328   *
27329   * @return {Object}
27330   *         An object containing all of the settings
27331   *         for a player tag
27332   */
27333  static getTagSettings(tag) {
27334    const baseOptions = {
27335      sources: [],
27336      tracks: [],
27337    };
27338    const tagOptions = getAttributes(tag);
27339    const dataSetup = tagOptions['data-setup'];
27340    if (hasClass(tag, 'vjs-fill')) {
27341      tagOptions.fill = true;
27342    }
27343    if (hasClass(tag, 'vjs-fluid')) {
27344      tagOptions.fluid = true;
27345    }
27346
27347    // Check if data-setup attr exists.
27348    if (dataSetup !== null) {
27349      // Parse options JSON
27350      try {
27351        // If empty string, make it a parsable json object.
27352        Object.assign(tagOptions, JSON.parse(dataSetup || '{}'));
27353      } catch (e) {
27354        log$1.error('data-setup', e);
27355      }
27356    }
27357    Object.assign(baseOptions, tagOptions);
27358
27359    // Get tag children settings
27360    if (tag.hasChildNodes()) {
27361      const children = tag.childNodes;
27362      for (let i = 0, j = children.length; i < j; i++) {
27363        const child = children[i];
27364        // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
27365        const childName = child.nodeName.toLowerCase();
27366        if (childName === 'source') {
27367          baseOptions.sources.push(getAttributes(child));
27368        } else if (childName === 'track') {
27369          baseOptions.tracks.push(getAttributes(child));
27370        }
27371      }
27372    }
27373    return baseOptions;
27374  }
27375
27376  /**
27377   * Set debug mode to enable/disable logs at info level.
27378   *
27379   * @param {boolean} enabled
27380   * @fires Player#debugon
27381   * @fires Player#debugoff
27382   * @return {boolean|undefined}
27383   */
27384  debug(enabled) {
27385    if (enabled === undefined) {
27386      return this.debugEnabled_;
27387    }
27388    if (enabled) {
27389      this.trigger('debugon');
27390      this.previousLogLevel_ = this.log.level;
27391      this.log.level('debug');
27392      this.debugEnabled_ = true;
27393    } else {
27394      this.trigger('debugoff');
27395      this.log.level(this.previousLogLevel_);
27396      this.previousLogLevel_ = undefined;
27397      this.debugEnabled_ = false;
vendor: 4,984 bytes, lines 27398-27579
27398    }
27399  }
27400
27401  /**
27402   * Set or get current playback rates.
27403   * Takes an array and updates the playback rates menu with the new items.
27404   * Pass in an empty array to hide the menu.
27405   * Values other than arrays are ignored.
27406   *
27407   * @fires Player#playbackrateschange
27408   * @param {number[]} [newRates]
27409   *                   The new rates that the playback rates menu should update to.
27410   *                   An empty array will hide the menu
27411   * @return {number[]} When used as a getter will return the current playback rates
27412   */
27413  playbackRates(newRates) {
27414    if (newRates === undefined) {
27415      return this.cache_.playbackRates;
27416    }
27417
27418    // ignore any value that isn't an array
27419    if (!Array.isArray(newRates)) {
27420      return;
27421    }
27422
27423    // ignore any arrays that don't only contain numbers
27424    if (!newRates.every((rate) => typeof rate === 'number')) {
27425      return;
27426    }
27427    this.cache_.playbackRates = newRates;
27428
27429    /**
27430     * fires when the playback rates in a player are changed
27431     *
27432     * @event Player#playbackrateschange
27433     * @type {Event}
27434     */
27435    this.trigger('playbackrateschange');
27436  }
27437
27438  /**
27439   * Reports whether or not a player has a plugin available.
27440   *
27441   * This does not report whether or not the plugin has ever been initialized
27442   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
27443   *
27444   * @method hasPlugin
27445   * @param  {string}  name
27446   *         The name of a plugin.
27447   *
27448   * @return {boolean}
27449   *         Whether or not this player has the requested plugin available.
27450   */
27451
27452  /**
27453   * Reports whether or not a player is using a plugin by name.
27454   *
27455   * For basic plugins, this only reports whether the plugin has _ever_ been
27456   * initialized on this player.
27457   *
27458   * @method Player#usingPlugin
27459   * @param  {string} name
27460   *         The name of a plugin.
27461   *
27462   * @return {boolean}
27463   *         Whether or not this player is using the requested plugin.
27464   */
27465}
27466
27467/**
27468 * Get the {@link VideoTrackList}
27469 *
27470 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
27471 *
27472 * @return {VideoTrackList}
27473 *         the current video track list
27474 *
27475 * @method Player.prototype.videoTracks
27476 */
27477
27478/**
27479 * Get the {@link AudioTrackList}
27480 *
27481 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
27482 *
27483 * @return {AudioTrackList}
27484 *         the current audio track list
27485 *
27486 * @method Player.prototype.audioTracks
27487 */
27488
27489/**
27490 * Get the {@link TextTrackList}
27491 *
27492 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
27493 *
27494 * @return {TextTrackList}
27495 *         the current text track list
27496 *
27497 * @method Player.prototype.textTracks
27498 */
27499
27500/**
27501 * Get the remote {@link TextTrackList}
27502 *
27503 * @return {TextTrackList}
27504 *         The current remote text track list
27505 *
27506 * @method Player.prototype.remoteTextTracks
27507 */
27508
27509/**
27510 * Get the remote {@link HtmlTrackElementList} tracks.
27511 *
27512 * @return {HtmlTrackElementList}
27513 *         The current remote text track element list
27514 *
27515 * @method Player.prototype.remoteTextTrackEls
27516 */
27517
27518ALL.names.forEach(function (name) {
27519  const props = ALL[name];
27520  Player.prototype[props.getterName] = function () {
27521    if (this.tech_) {
27522      return this.tech_[props.getterName]();
27523    }
27524
27525    // if we have not yet loadTech_, we create {video,audio,text}Tracks_
27526    // these will be passed to the tech during loading
27527    this[props.privateName] = this[props.privateName] || new props.ListClass();
27528    return this[props.privateName];
27529  };
27530});
27531
27532/**
27533 * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
27534 * sets the `crossOrigin` property on the `<video>` tag to control the CORS
27535 * behavior.
27536 *
27537 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
27538 *
27539 * @param {string} [value]
27540 *        The value to set the `Player`'s crossorigin to. If an argument is
27541 *        given, must be one of `anonymous` or `use-credentials`.
27542 *
27543 * @return {string|undefined}
27544 *         - The current crossorigin value of the `Player` when getting.
27545 *         - undefined when setting
27546 */
27547Player.prototype.crossorigin = Player.prototype.crossOrigin;
27548
27549/**
27550 * Global enumeration of players.
27551 *
27552 * The keys are the player IDs and the values are either the {@link Player}
27553 * instance or `null` for disposed players.
27554 *
27555 * @type {Object}
27556 */
27557Player.players = {};
27558const navigator = window__default['default'].navigator;
27559
27560/*
27561 * Player instance options, surfaced using options
27562 * options = Player.prototype.options_
27563 * Make changes in options, not here.
27564 *
27565 * @type {Object}
27566 * @private
27567 */
27568Player.prototype.options_ = {
27569  // Default order of fallback technology
27570  techOrder: Tech.defaultTechOrder_,
27571  html5: {},
27572  // enable sourceset by default
27573  enableSourceset: true,
27574  // default inactivity timeout
27575  inactivityTimeout: 2000,
27576  // default playback rates
27577  playbackRates: [],
27578  // Add playback rate selection by adding rates
27579  // 'playbackRates': [0.5, 1, 1.5, 2],
27580  liveui: false,
27581  // Included control sets
27582  children: [
27583    'mediaLoader',
27584    'posterImage',
27585    'titleBar',
27586    'textTrackDisplay',
27587    'loadingSpinner',
27588    'bigPlayButton',
27589    'liveTracker',
27590    'controlBar',
27591    'errorDisplay',
27592    'textTrackSettings',
27593    'resizeManager',
27594  ],
27595  language:
27596    (navigator &&
27597      ((navigator.languages && navigator.languages[0]) ||
27598        navigator.userLanguage ||
27599        navigator.language)) ||
27600    'en',
27601  // locales and their language translations
27602  languages: {},
27603  // Default message to show when a video cannot be played.
27604  notSupportedMessage: 'No compatible source was found for this media.',
27605  normalizeAutoplay: false,
27606  fullscreen: {
27607    options: {
27608      navigationUI: 'hide',
27609    },
27610  },
27611  breakpoints: {},
27612  responsive: false,
27613  audioOnlyMode: false,
27614  audioPosterMode: false,
27615  spatialNavigation: {
27616    enabled: false,
27617    horizontalSeek: false,
27618  },
27619  // Default smooth seeking to false
27620  enableSmoothSeeking: false,
27621};
27622TECH_EVENTS_RETRIGGER.forEach(function (event) {
27623  Player.prototype[`handleTech${toTitleCase$1(event)}_`] = function () {
27624    return this.trigger(event);
27625  };
27626});
27627
27628/**
27629 * Fired when the player has initial duration and dimension information
27630 *
27631 * @event Player#loadedmetadata
27632 * @type {Event}
27633 */
27634
27635/**
27636 * Fired when the player has downloaded data at the current playback position
27637 *
27638 * @event Player#loadeddata
27639 * @type {Event}
27640 */
27641
27642/**
27643 * Fired when the current playback position has changed *
27644 * During playback this is fired every 15-250 milliseconds, depending on the
27645 * playback technology in use.
27646 *
27647 * @event Player#timeupdate
27648 * @type {Event}
27649 */
27650
27651/**
27652 * Fired when the volume changes
27653 *
27654 * @event Player#volumechange
27655 * @type {Event}
27656 */
27657
27658Component$1.registerComponent('Player', Player);
27659
27660/**
27661 * @file plugin.js
27662 */
27663
27664/**
27665 * The base plugin name.
27666 *
27667 * @private
27668 * @constant
27669 * @type {string}
27670 */
27671const BASE_PLUGIN_NAME = 'plugin';
27672
27673/**
27674 * The key on which a player's active plugins cache is stored.
27675 *
27676 * @private
27677 * @constant
27678 * @type     {string}
27679 */
27680const PLUGIN_CACHE_KEY = 'activePlugins_';
27681
27682/**
27683 * Stores registered plugins in a private space.
27684 *
27685 * @private
27686 * @type    {Object}
27687 */
27688const pluginStorage = {};
27689
27690/**
27691 * Reports whether or not a plugin has been registered.
27692 *
27693 * @private
27694 * @param   {string} name
27695 *          The name of a plugin.
27696 *
27697 * @return {boolean}
27698 *          Whether or not the plugin has been registered.
27699 */
27700const pluginExists = (name) => pluginStorage.hasOwnProperty(name);
27701
27702/**
27703 * Get a single registered plugin by name.
27704 *
27705 * @private
27706 * @param   {string} name
27707 *          The name of a plugin.
27708 *
27709 * @return {typeof Plugin|Function|undefined}
27710 *          The plugin (or undefined).
27711 */
27712const getPlugin = (name) => (pluginExists(name) ? pluginStorage[name] : undefined);
27713
27714/**
27715 * Marks a plugin as "active" on a player.
27716 *
27717 * Also, ensures that the player has an object for tracking active plugins.
27718 *
27719 * @private
27720 * @param   {Player} player
27721 *          A Video.js player instance.
27722 *
27723 * @param   {string} name
27724 *          The name of a plugin.
27725 */
27726const markPluginAsActive = (player, name) => {
27727  player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
27728  player[PLUGIN_CACHE_KEY][name] = true;
27729};
27730
27731/**
27732 * Triggers a pair of plugin setup events.
27733 *
27734 * @private
27735 * @param  {Player} player
27736 *         A Video.js player instance.
27737 *
27738 * @param  {PluginEventHash} hash
27739 *         A plugin event hash.
27740 *
27741 * @param  {boolean} [before]
27742 *         If true, prefixes the event name with "before". In other words,
27743 *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
27744 */
27745const triggerSetupEvent = (player, hash, before) => {
27746  const eventName = (before ? 'before' : '') + 'pluginsetup';
27747  player.trigger(eventName, hash);
27748  player.trigger(eventName + ':' + hash.name, hash);
27749};
27750
27751/**
27752 * Takes a basic plugin function and returns a wrapper function which marks
27753 * on the player that the plugin has been activated.
27754 *
27755 * @private
27756 * @param   {string} name
27757 *          The name of the plugin.
27758 *
27759 * @param   {Function} plugin
27760 *          The basic plugin.
27761 *
27762 * @return {Function}
27763 *          A wrapper function for the given plugin.
27764 */
27765const createBasicPlugin = function (name, plugin) {
27766  const basicPluginWrapper = function () {
27767    // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
27768    // regardless, but we want the hash to be consistent with the hash provided
27769    // for advanced plugins.
27770    //
27771    // The only potentially counter-intuitive thing here is the `instance` in
27772    // the "pluginsetup" event is the value returned by the `plugin` function.
27773    triggerSetupEvent(
27774      this,
27775      {
27776        name,
27777        plugin,
27778        instance: null,
27779      },
27780      true
27781    );
27782    const instance = plugin.apply(this, arguments);
27783    markPluginAsActive(this, name);
27784    triggerSetupEvent(this, {
27785      name,
27786      plugin,
27787      instance,
27788    });
27789    return instance;
27790  };
vendor: 7,923 bytes, lines 27791-28053
27791  Object.keys(plugin).forEach(function (prop) {
27792    basicPluginWrapper[prop] = plugin[prop];
27793  });
27794  return basicPluginWrapper;
27795};
27796
27797/**
27798 * Takes a plugin sub-class and returns a factory function for generating
27799 * instances of it.
27800 *
27801 * This factory function will replace itself with an instance of the requested
27802 * sub-class of Plugin.
27803 *
27804 * @private
27805 * @param   {string} name
27806 *          The name of the plugin.
27807 *
27808 * @param   {Plugin} PluginSubClass
27809 *          The advanced plugin.
27810 *
27811 * @return {Function}
27812 */
27813const createPluginFactory = (name, PluginSubClass) => {
27814  // Add a `name` property to the plugin prototype so that each plugin can
27815  // refer to itself by name.
27816  PluginSubClass.prototype.name = name;
27817  return function (...args) {
27818    triggerSetupEvent(
27819      this,
27820      {
27821        name,
27822        plugin: PluginSubClass,
27823        instance: null,
27824      },
27825      true
27826    );
27827    const instance = new PluginSubClass(...[this, ...args]);
27828
27829    // The plugin is replaced by a function that returns the current instance.
27830    this[name] = () => instance;
27831    triggerSetupEvent(this, instance.getEventHash());
27832    return instance;
27833  };
27834};
27835
27836/**
27837 * Parent class for all advanced plugins.
27838 *
27839 * @mixes   module:evented~EventedMixin
27840 * @mixes   module:stateful~StatefulMixin
27841 * @fires   Player#beforepluginsetup
27842 * @fires   Player#beforepluginsetup:$name
27843 * @fires   Player#pluginsetup
27844 * @fires   Player#pluginsetup:$name
27845 * @listens Player#dispose
27846 * @throws  {Error}
27847 *          If attempting to instantiate the base {@link Plugin} class
27848 *          directly instead of via a sub-class.
27849 */
27850class Plugin {
27851  /**
27852   * Creates an instance of this class.
27853   *
27854   * Sub-classes should call `super` to ensure plugins are properly initialized.
27855   *
27856   * @param {Player} player
27857   *        A Video.js player instance.
27858   */
27859  constructor(player) {
27860    if (this.constructor === Plugin) {
27861      throw new Error('Plugin must be sub-classed; not directly instantiated.');
27862    }
27863    this.player = player;
27864    if (!this.log) {
27865      this.log = this.player.log.createLogger(this.name);
27866    }
27867
27868    // Make this object evented, but remove the added `trigger` method so we
27869    // use the prototype version instead.
27870    evented(this);
27871    delete this.trigger;
27872    stateful(this, this.constructor.defaultState);
27873    markPluginAsActive(player, this.name);
27874
27875    // Auto-bind the dispose method so we can use it as a listener and unbind
27876    // it later easily.
27877    this.dispose = this.dispose.bind(this);
27878
27879    // If the player is disposed, dispose the plugin.
27880    player.on('dispose', this.dispose);
27881  }
27882
27883  /**
27884   * Get the version of the plugin that was set on <pluginName>.VERSION
27885   */
27886  version() {
27887    return this.constructor.VERSION;
27888  }
27889
27890  /**
27891   * Each event triggered by plugins includes a hash of additional data with
27892   * conventional properties.
27893   *
27894   * This returns that object or mutates an existing hash.
27895   *
27896   * @param   {Object} [hash={}]
27897   *          An object to be used as event an event hash.
27898   *
27899   * @return {PluginEventHash}
27900   *          An event hash object with provided properties mixed-in.
27901   */
27902  getEventHash(hash = {}) {
27903    hash.name = this.name;
27904    hash.plugin = this.constructor;
27905    hash.instance = this;
27906    return hash;
27907  }
27908
27909  /**
27910   * Triggers an event on the plugin object and overrides
27911   * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
27912   *
27913   * @param   {string|Object} event
27914   *          An event type or an object with a type property.
27915   *
27916   * @param   {Object} [hash={}]
27917   *          Additional data hash to merge with a
27918   *          {@link PluginEventHash|PluginEventHash}.
27919   *
27920   * @return {boolean}
27921   *          Whether or not default was prevented.
27922   */
27923  trigger(event, hash = {}) {
27924    return trigger(this.eventBusEl_, event, this.getEventHash(hash));
27925  }
27926
27927  /**
27928   * Handles "statechanged" events on the plugin. No-op by default, override by
27929   * subclassing.
27930   *
27931   * @abstract
27932   * @param    {Event} e
27933   *           An event object provided by a "statechanged" event.
27934   *
27935   * @param    {Object} e.changes
27936   *           An object describing changes that occurred with the "statechanged"
27937   *           event.
27938   */
27939  handleStateChanged(e) {}
27940
27941  /**
27942   * Disposes a plugin.
27943   *
27944   * Subclasses can override this if they want, but for the sake of safety,
27945   * it's probably best to subscribe the "dispose" event.
27946   *
27947   * @fires Plugin#dispose
27948   */
27949  dispose() {
27950    const { name, player } = this;
27951
27952    /**
27953     * Signals that a advanced plugin is about to be disposed.
27954     *
27955     * @event Plugin#dispose
27956     * @type  {Event}
27957     */
27958    this.trigger('dispose');
27959    this.off();
27960    player.off('dispose', this.dispose);
27961
27962    // Eliminate any possible sources of leaking memory by clearing up
27963    // references between the player and the plugin instance and nulling out
27964    // the plugin's state and replacing methods with a function that throws.
27965    player[PLUGIN_CACHE_KEY][name] = false;
27966    this.player = this.state = null;
27967
27968    // Finally, replace the plugin name on the player with a new factory
27969    // function, so that the plugin is ready to be set up again.
27970    player[name] = createPluginFactory(name, pluginStorage[name]);
27971  }
27972
27973  /**
27974   * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
27975   *
27976   * @param   {string|Function} plugin
27977   *          If a string, matches the name of a plugin. If a function, will be
27978   *          tested directly.
27979   *
27980   * @return {boolean}
27981   *          Whether or not a plugin is a basic plugin.
27982   */
27983  static isBasic(plugin) {
27984    const p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
27985    return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
27986  }
27987
27988  /**
27989   * Register a Video.js plugin.
27990   *
27991   * @param   {string} name
27992   *          The name of the plugin to be registered. Must be a string and
27993   *          must not match an existing plugin or a method on the `Player`
27994   *          prototype.
27995   *
27996   * @param   {typeof Plugin|Function} plugin
27997   *          A sub-class of `Plugin` or a function for basic plugins.
27998   *
27999   * @return {typeof Plugin|Function}
28000   *          For advanced plugins, a factory function for that plugin. For
28001   *          basic plugins, a wrapper function that initializes the plugin.
28002   */
28003  static registerPlugin(name, plugin) {
28004    if (typeof name !== 'string') {
28005      throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
28006    }
28007    if (pluginExists(name)) {
28008      log$1.warn(
28009        `A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`
28010      );
28011    } else if (Player.prototype.hasOwnProperty(name)) {
28012      throw new Error(
28013        `Illegal plugin name, "${name}", cannot share a name with an existing player method!`
28014      );
28015    }
28016    if (typeof plugin !== 'function') {
28017      throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
28018    }
28019    pluginStorage[name] = plugin;
28020
28021    // Add a player prototype method for all sub-classed plugins (but not for
28022    // the base Plugin class).
28023    if (name !== BASE_PLUGIN_NAME) {
28024      if (Plugin.isBasic(plugin)) {
28025        Player.prototype[name] = createBasicPlugin(name, plugin);
28026      } else {
28027        Player.prototype[name] = createPluginFactory(name, plugin);
28028      }
28029    }
28030    return plugin;
28031  }
28032
28033  /**
28034   * De-register a Video.js plugin.
28035   *
28036   * @param  {string} name
28037   *         The name of the plugin to be de-registered. Must be a string that
28038   *         matches an existing plugin.
28039   *
28040   * @throws {Error}
28041   *         If an attempt is made to de-register the base plugin.
28042   */
28043  static deregisterPlugin(name) {
28044    if (name === BASE_PLUGIN_NAME) {
28045      throw new Error('Cannot de-register base plugin.');
28046    }
28047    if (pluginExists(name)) {
28048      delete pluginStorage[name];
28049      delete Player.prototype[name];
28050    }
28051  }
28052
28053  /**
28054   * Gets an object containing multiple Video.js plugins.
28055   *
28056   * @param   {Array} [names]
28057   *          If provided, should be an array of plugin names. Defaults to _all_
28058   *          plugin names.
28059   *
28060   * @return {Object|undefined}
28061   *          An object containing plugin(s) associated with their name(s) or
28062   *          `undefined` if no matching plugins exist).
28063   */
28064  static getPlugins(names = Object.keys(pluginStorage)) {
28065    let result;
28066    names.forEach((name) => {
28067      const plugin = getPlugin(name);
28068      if (plugin) {
28069        result = result || {};
28070        result[name] = plugin;
28071      }
28072    });
28073    return result;
28074  }
28075
28076  /**
28077   * Gets a plugin's version, if available
28078   *
28079   * @param   {string} name
28080   *          The name of a plugin.
28081   *
28082   * @return {string}
28083   *          The plugin's version or an empty string.
28084   */
28085  static getPluginVersion(name) {
28086    const plugin = getPlugin(name);
28087    return (plugin && plugin.VERSION) || '';
28088  }
28089}
28090
28091/**
28092 * Gets a plugin by name if it exists.
28093 *
28094 * @static
28095 * @method   getPlugin
28096 * @memberOf Plugin
28097 * @param    {string} name
28098 *           The name of a plugin.
28099 *
28100 * @returns  {typeof Plugin|Function|undefined}
28101 *           The plugin (or `undefined`).
28102 */
28103Plugin.getPlugin = getPlugin;
28104
28105/**
28106 * The name of the base plugin class as it is registered.
28107 *
28108 * @type {string}
28109 */
28110Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
28111Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
28112
28113/**
28114 * Documented in player.js
28115 *
28116 * @ignore
28117 */
28118Player.prototype.usingPlugin = function (name) {
28119  return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
28120};
28121
28122/**
28123 * Documented in player.js
28124 *
28125 * @ignore
28126 */
28127Player.prototype.hasPlugin = function (name) {
28128  return !!pluginExists(name);
28129};
28130
28131/**
28132 * Signals that a plugin is about to be set up on a player.
28133 *
28134 * @event    Player#beforepluginsetup
28135 * @type     {PluginEventHash}
28136 */
28137
28138/**
28139 * Signals that a plugin is about to be set up on a player - by name. The name
28140 * is the name of the plugin.
28141 *
28142 * @event    Player#beforepluginsetup:$name
28143 * @type     {PluginEventHash}
28144 */
28145
28146/**
28147 * Signals that a plugin has just been set up on a player.
28148 *
28149 * @event    Player#pluginsetup
28150 * @type     {PluginEventHash}
28151 */
28152
28153/**
28154 * Signals that a plugin has just been set up on a player - by name. The name
28155 * is the name of the plugin.
28156 *
28157 * @event    Player#pluginsetup:$name
28158 * @type     {PluginEventHash}
28159 */
28160
28161/**
28162 * @typedef  {Object} PluginEventHash
28163 *
28164 * @property {string} instance
28165 *           For basic plugins, the return value of the plugin function. For
28166 *           advanced plugins, the plugin instance on which the event is fired.
28167 *
28168 * @property {string} name
28169 *           The name of the plugin.
28170 *
28171 * @property {string} plugin
28172 *           For basic plugins, the plugin function. For advanced plugins, the
28173 *           plugin class/constructor.
28174 */
28175
28176/**
28177 * @file deprecate.js
28178 * @module deprecate
28179 */
28180
28181/**
28182 * Decorate a function with a deprecation message the first time it is called.
28183 *
28184 * @param  {string}   message
28185 *         A deprecation message to log the first time the returned function
28186 *         is called.
28187 *
28188 * @param  {Function} fn
28189 *         The function to be deprecated.
28190 *
28191 * @return {Function}
28192 *         A wrapper function that will log a deprecation warning the first
28193 *         time it is called. The return value will be the return value of
28194 *         the wrapped function.
28195 */
28196function deprecate(message, fn) {
28197  let warned = false;
28198  return function (...args) {
28199    if (!warned) {
28200      log$1.warn(message);
28201    }
28202    warned = true;
28203    return fn.apply(this, args);
28204  };
28205}
28206
28207/**
28208 * Internal function used to mark a function as deprecated in the next major
28209 * version with consistent messaging.
28210 *
28211 * @param  {number}   major   The major version where it will be removed
28212 * @param  {string}   oldName The old function name
28213 * @param  {string}   newName The new function name
28214 * @param  {Function} fn      The function to deprecate
28215 * @return {Function}         The decorated function
28216 */
28217function deprecateForMajor(major, oldName, newName, fn) {
28218  return deprecate(
28219    `${oldName} is deprecated and will be removed in ${major}.0; please use ${newName} instead.`,
28220    fn
28221  );
28222}
28223
28224var VjsErrors = {
28225  NetworkBadStatus: 'networkbadstatus',
28226  NetworkRequestFailed: 'networkrequestfailed',
28227  NetworkRequestAborted: 'networkrequestaborted',
28228  NetworkRequestTimeout: 'networkrequesttimeout',
28229  NetworkBodyParserFailed: 'networkbodyparserfailed',
28230  StreamingHlsPlaylistParserError: 'streaminghlsplaylistparsererror',
28231  StreamingDashManifestParserError: 'streamingdashmanifestparsererror',
28232  StreamingContentSteeringParserError: 'streamingcontentsteeringparsererror',
28233  StreamingVttParserError: 'streamingvttparsererror',
28234  StreamingFailedToSelectNextSegment: 'streamingfailedtoselectnextsegment',
28235  StreamingFailedToDecryptSegment: 'streamingfailedtodecryptsegment',
28236  StreamingFailedToTransmuxSegment: 'streamingfailedtotransmuxsegment',
28237  StreamingFailedToAppendSegment: 'streamingfailedtoappendsegment',
28238  StreamingCodecsChangeError: 'streamingcodecschangeerror',
28239};
28240
28241/**
28242 * @file video.js
28243 * @module videojs
28244 */
28245
28246/** @import { PlayerReadyCallback } from './player' */
28247
28248/**
28249 * Normalize an `id` value by trimming off a leading `#`
28250 *
28251 * @private
28252 * @param   {string} id
28253 *          A string, maybe with a leading `#`.
28254 *
28255 * @return {string}
28256 *          The string, without any leading `#`.
28257 */
28258const normalizeId = (id) => (id.indexOf('#') === 0 ? id.slice(1) : id);
28259
28260/**
28261 * The `videojs()` function doubles as the main function for users to create a
28262 * {@link Player} instance as well as the main library namespace.
28263 *
28264 * It can also be used as a getter for a pre-existing {@link Player} instance.
28265 * However, we _strongly_ recommend using `videojs.getPlayer()` for this
28266 * purpose because it avoids any potential for unintended initialization.
28267 *
28268 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
28269 * of our JSDoc template, we cannot properly document this as both a function
28270 * and a namespace, so its function signature is documented here.
28271 *
28272 * #### Arguments
28273 * ##### id
28274 * string|Element, **required**
28275 *
28276 * Video element or video element ID.
28277 *
28278 * ##### options
28279 * Object, optional
28280 *
28281 * Options object for providing settings.
28282 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
28283 *
28284 * ##### ready
28285 * {@link Component~ReadyCallback}, optional
28286 *
28287 * A function to be called when the {@link Player} and {@link Tech} are ready.
28288 *
28289 * #### Return Value
28290 *
28291 * The `videojs()` function returns a {@link Player} instance.
28292 *
28293 * @namespace
28294 *
28295 * @borrows AudioTrack as AudioTrack
28296 * @borrows Component.getComponent as getComponent
28297 * @borrows module:events.on as on
28298 * @borrows module:events.one as one
28299 * @borrows module:events.off as off
28300 * @borrows module:events.trigger as trigger
28301 * @borrows EventTarget as EventTarget
28302 * @borrows module:middleware.use as use
28303 * @borrows Player.players as players
28304 * @borrows Plugin.registerPlugin as registerPlugin
28305 * @borrows Plugin.deregisterPlugin as deregisterPlugin
28306 * @borrows Plugin.getPlugins as getPlugins
28307 * @borrows Plugin.getPlugin as getPlugin
28308 * @borrows Plugin.getPluginVersion as getPluginVersion
28309 * @borrows Tech.getTech as getTech
28310 * @borrows Tech.registerTech as registerTech
28311 * @borrows TextTrack as TextTrack
28312 * @borrows VideoTrack as VideoTrack
28313 *
28314 * @param  {string|Element}
28314 id
28315 *         Video element or video element ID.
28316 *
28317 * @param  {Object} [options]
28318 *         Options object for providing settings.
28319 *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
28320 *
28321 * @param  {PlayerReadyCallback} [ready]
28322 *         A function to be called when the {@link Player} and {@link Tech} are
28323 *         ready.
28324 *
28325 * @return {Player}
28326 *         The `videojs()` function returns a {@link Player|Player} instance.
28327 */
28328function videojs(id, options, ready) {
28329  let player = videojs.getPlayer(id);
28330  if (player) {
28331    if (options) {
28332      log$1.warn(`Player "${id}" is already initialised. Options will not be applied.`);
28333    }
28334    if (ready) {
28335      player.ready(ready);
28336    }
28337    return player;
28338  }
28339  const el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
28340  if (!isEl(el)) {
28341    throw new TypeError('The element or ID supplied is not valid. (videojs)');
28342  }
28343
28344  // document.body.contains(el) will only check if el is contained within that one document.
28345  // This causes problems for elements in iframes.
28346  // Instead, use the element's ownerDocument instead of the global document.
28347  // This will make sure that the element is indeed in the dom of that document.
28348  // Additionally, check that the document in question has a default view.
28349  // If the document is no longer attached to the dom, the defaultView of the document will be null.
28350  // If element is inside Shadow DOM (e.g. is part of a Custom element), ownerDocument.body
28351  // always returns false. Instead, use the Shadow DOM root.
28352  const inShadowDom =
28353    'getRootNode' in el ? el.getRootNode() instanceof window__default['default'].ShadowRoot : false;
28354  const rootNode = inShadowDom ? el.getRootNode() : el.ownerDocument.body;
28355  if (!el.ownerDocument.defaultView || !rootNode.contains(el)) {
28356    log$1.warn('The element supplied is not included in the DOM');
28357  }
28358  options = options || {};
28359
28360  // Store a copy of the el before modification, if it is to be restored in destroy()
28361  // If div ingest, store the parent div
28362  if (options.restoreEl === true) {
28363    options.restoreEl = (
28364      el.parentNode && el.parentNode.hasAttribute && el.parentNode.hasAttribute('data-vjs-player')
28365        ? el.parentNode
28366        : el
28367    ).cloneNode(true);
28368  }
28369  hooks('beforesetup').forEach((hookFunction) => {
28370    const opts = hookFunction(el, merge$1(options));
28371    if (!isObject(opts) || Array.isArray(opts)) {
28372      log$1.error('please return an object in beforesetup hooks');
28373      return;
28374    }
28375    options = merge$1(options, opts);
28376  });
28377
28378  // We get the current "Player" component here in case an integration has
28379  // replaced it with a custom player.
28380  const PlayerComponent = Component$1.getComponent('Player');
28381  player = new PlayerComponent(el, options, ready);
28382  hooks('setup').forEach((hookFunction) => hookFunction(player));
28383  return player;
28384}
28385videojs.hooks_ = hooks_;
28386videojs.hooks = hooks;
28387videojs.hook = hook;
28388videojs.hookOnce = hookOnce;
28389videojs.removeHook = removeHook;
28390
28391// Add default styles
28392if (window__default['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
28393  let style = $('.vjs-styles-defaults');
28394  if (!style) {
28395    style = createStyleElement('vjs-styles-defaults');
28396    const head = $('head');
28397    if (head) {
28398      head.insertBefore(style, head.firstChild);
28399    }
28400    setTextContent(
28401      style,
28402      `
28403      .video-js {
28404        width: 300px;
28405        height: 150px;
28406      }
28407
28408      .vjs-fluid:not(.vjs-audio-only-mode) {
28409        padding-top: 56.25%
28410      }
28411    `
28412    );
28413  }
28414}
28415
28416// Run Auto-load players
28417// You have to wait at least once in case this script is loaded after your
28418// video in the DOM (weird behavior only with minified version)
28419autoSetupTimeout(1, videojs);
28420
28421/**
28422 * Current Video.js version. Follows [semantic versioning](https://semver.org/).
28423 *
28424 * @type {string}
28425 */
28426videojs.VERSION = version$6;
28427
28428/**
28429 * The global options object. These are the settings that take effect
28430 * if no overrides are specified when the player is created.
28431 *
28432 * @type {Object}
28433 */
28434videojs.options = Player.prototype.options_;
28435
28436/**
28437 * Get an object with the currently created players, keyed by player ID
28438 *
28439 * @return {Object}
28440 *         The created players
28441 */
28442videojs.getPlayers = () => Player.players;
28443
28444/**
28445 * Get a single player based on an ID or DOM element.
28446 *
28447 * This is useful if you want to check if an element or ID has an associated
28448 * Video.js player, but not create one if it doesn't.
28449 *
28450 * @param   {string|Element}
28450 id
28451 *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
28452 *          or a string matching the `id` of such an element.
28453 *
28454 * @return {Player|undefined}
28455 *          A player instance or `undefined` if there is no player instance
28456 *          matching the argument.
28457 */
28458videojs.getPlayer = (id) => {
28459  const players = Player.players;
28460  let tag;
28461  if (typeof id === 'string') {
28462    const nId = normalizeId(id);
28463    const player = players[nId];
28464    if (player) {
28465      return player;
28466    }
28467    tag = $('#' + nId);
28468  } else {
28469    tag = id;
28470  }
28471  if (isEl(tag)) {
28472    const { player, playerId } = tag;
28473
28474    // Element may have a `player` property referring to an already created
28475    // player instance. If so, return that.
28476    if (player || players[playerId]) {
28477      return player || players[playerId];
28478    }
28479  }
28480};
28481
28482/**
28483 * Returns an array of all current players.
28484 *
28485 * @return {Array}
28486 *         An array of all players. The array will be in the order that
28487 *         `Object.keys` provides, which could potentially vary between
28488 *         JavaScript engines.
28489 *
28490 */
28491videojs.getAllPlayers = () =>
28492  // Disposed players leave a key with a `null` value, so we need to make sure
28493  // we filter those out.
28494  Object.keys(Player.players)
28495    .map((k) => Player.players[k])
28496    .filter(Boolean);
28497videojs.players = Player.players;
28498videojs.getComponent = Component$1.getComponent;
28499
28500/**
28501 * Register a component so it can referred to by name. Used when adding to other
28502 * components, either through addChild `component.addChild('myComponent')` or through
28503 * default children options  `{ children: ['myComponent'] }`.
28504 *
28505 * > NOTE: You could also just initialize the component before adding.
28506 * `component.addChild(new MyComponent());`
28507 *
28508 * @param {string} name
28509 *        The class name of the component
28510 *
28511 * @param {typeof Component} comp
28512 *        The component class
28513 *
28514 * @return {typeof Component}
28515 *         The newly registered component
28516 */
28517videojs.registerComponent = (name, comp) => {
28518  if (Tech.isTech(comp)) {
28519    log$1.warn(
28520      `The ${name} tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)`
28521    );
28522  }
28523  return Component$1.registerComponent.call(Component$1, name, comp);
28524};
28525videojs.getTech = Tech.getTech;
28526videojs.registerTech = Tech.registerTech;
28527videojs.use = use;
28528
28529/**
28530 * An object that can be returned by a middleware to signify
28531 * that the middleware is being terminated.
28532 *
28533 * @type {object}
28534 * @property {object} middleware.TERMINATOR
28535 */
28536Object.defineProperty(videojs, 'middleware', {
28537  value: {},
28538  writeable: false,
28539  enumerable: true,
28540});
28541Object.defineProperty(videojs.middleware, 'TERMINATOR', {
28542  value: TERMINATOR,
28543  writeable: false,
28544  enumerable: true,
28545});
28546
28547/**
28548 * A reference to the {@link module:browser|browser utility module} as an object.
28549 *
28550 * @type {Object}
28551 * @see  {@link module:browser|browser}
28552 */
28553videojs.browser = browser;
28554
28555/**
28556 * A reference to the {@link module:obj|obj utility module} as an object.
28557 *
28558 * @type {Object}
28559 * @see  {@link module:obj|obj}
28560 */
28561videojs.obj = Obj;
28562
28563/**
28564 * Deprecated reference to the {@link module:obj.merge|merge function}
28565 *
28566 * @type {Function}
28567 * @see {@link module:obj.merge|merge}
28568 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.merge instead.
28569 */
28570videojs.mergeOptions = deprecateForMajor(9, 'videojs.mergeOptions', 'videojs.obj.merge', merge$1);
28571
28572/**
28573 * Deprecated reference to the {@link module:obj.defineLazyProperty|defineLazyProperty function}
28574 *
28575 * @type {Function}
28576 * @see {@link module:obj.defineLazyProperty|defineLazyProperty}
28577 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.defineLazyProperty instead.
28578 */
28579videojs.defineLazyProperty = deprecateForMajor(
28580  9,
28581  'videojs.defineLazyProperty',
28582  'videojs.obj.defineLazyProperty',
28583  defineLazyProperty
28584);
28585
28586/**
28587 * Deprecated reference to the {@link module:fn.bind_|fn.bind_ function}
28588 *
28589 * @type {Function}
28590 * @see {@link module:fn.bind_|fn.bind_}
28591 * @deprecated Deprecated and will be removed in 9.0. Please use native Function.prototype.bind instead.
28592 */
28593videojs.bind = deprecateForMajor(9, 'videojs.bind', 'native Function.prototype.bind', bind_);
28594videojs.registerPlugin = Plugin.registerPlugin;
28595videojs.deregisterPlugin = Plugin.deregisterPlugin;
28596
28597/**
28598 * Deprecated method to register a plugin with Video.js
28599 *
28600 * @deprecated Deprecated and will be removed in 9.0. Use videojs.registerPlugin() instead.
28601 *
28602 * @param {string} name
28603 *        The plugin name
28604 *
28605 * @param {typeof Plugin|Function} plugin
28606 *         The plugin sub-class or function
28607 *
28608 * @return {typeof Plugin|Function}
28609 */
28610videojs.plugin = (name, plugin) => {
28611  log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
28612  return Plugin.registerPlugin(name, plugin);
28613};
28614videojs.getPlugins = Plugin.getPlugins;
28615videojs.getPlugin = Plugin.getPlugin;
28616videojs.getPluginVersion = Plugin.getPluginVersion;
28617
28618/**
28619 * Adding languages so that they're available to all players.
28620 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
28621 *
28622 * @param {string} code
28623 *        The language code or dictionary property
28624 *
28625 * @param {Object} data
28626 *        The data values to be translated
28627 *
28628 * @return {Object}
28629 *         The resulting language dictionary object
28630 */
28631videojs.addLanguage = function (code, data) {
28632  code = ('' + code).toLowerCase();
28633  videojs.options.languages = merge$1(videojs.options.languages, {
28634    [code]: data,
28635  });
28636  return videojs.options.languages[code];
28637};
28638
28639/**
28640 * A reference to the {@link module:log|log utility module} as an object.
28641 *
28642 * @type {Function}
28643 * @see  {@link module:log|log}
28644 */
28645videojs.log = log$1;
28646videojs.createLogger = createLogger;
28647
28648/**
28649 * A reference to the {@link module:time|time utility module} as an object.
28650 *
28651 * @type {Object}
28652 * @see {@link module:time|time}
28653 */
28654videojs.time = Time;
28655
28656/**
28657 * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28658 *
28659 * @type {Function}
28660 * @see {@link module:time.createTimeRanges|createTimeRanges}
28661 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28662 */
28663videojs.createTimeRange = deprecateForMajor(
28664  9,
28665  'videojs.createTimeRange',
28666  'videojs.time.createTimeRanges',
28667  createTimeRanges$1
28668);
28669
28670/**
28671 * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28672 *
28673 * @type {Function}
28674 * @see {@link module:time.createTimeRanges|createTimeRanges}
28675 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28676 */
28677videojs.createTimeRanges = deprecateForMajor(
28678  9,
28679  'videojs.createTimeRanges',
28680  'videojs.time.createTimeRanges',
28681  createTimeRanges$1
28682);
28683
28684/**
28685 * Deprecated reference to the {@link module:time.formatTime|formatTime function}
28686 *
28687 * @type {Function}
28688 * @see {@link module:time.formatTime|formatTime}
28689 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.format instead.
28690 */
28691videojs.formatTime = deprecateForMajor(
28692  9,
28693  'videojs.formatTime',
28694  'videojs.time.formatTime',
28695  formatTime
28696);
28697
28698/**
28699 * Deprecated reference to the {@link module:time.setFormatTime|setFormatTime function}
28700 *
28701 * @type {Function}
28702 * @see {@link module:time.setFormatTime|setFormatTime}
28703 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.setFormat instead.
28704 */
28705videojs.setFormatTime = deprecateForMajor(
28706  9,
28707  'videojs.setFormatTime',
28708  'videojs.time.setFormatTime',
28709  setFormatTime
28710);
28711
28712/**
28713 * Deprecated reference to the {@link module:time.resetFormatTime|resetFormatTime function}
28714 *
28715 * @type {Function}
28716 * @see {@link module:time.resetFormatTime|resetFormatTime}
28717 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.resetFormat instead.
28718 */
28719videojs.resetFormatTime = deprecateForMajor(
28720  9,
28721  'videojs.resetFormatTime',
28722  'videojs.time.resetFormatTime',
28723  resetFormatTime
28724);
28725
28726/**
28727 * Deprecated reference to the {@link module:url.parseUrl|Url.parseUrl function}
28728 *
28729 * @type {Function}
28730 * @see {@link module:url.parseUrl|parseUrl}
28731 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.parseUrl instead.
28732 */
28733videojs.parseUrl = deprecateForMajor(9, 'videojs.parseUrl', 'videojs.url.parseUrl', parseUrl);
28734
28735/**
28736 * Deprecated reference to the {@link module:url.isCrossOrigin|Url.isCrossOrigin function}
28737 *
28738 * @type {Function}
28739 * @see {@link module:url.isCrossOrigin|isCrossOrigin}
28740 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.isCrossOrigin instead.
28741 */
28742videojs.isCrossOrigin = deprecateForMajor(
28743  9,
28744  'videojs.isCrossOrigin',
28745  'videojs.url.isCrossOrigin',
28746  isCrossOrigin
28747);
28748videojs.EventTarget = EventTarget$2;
28749videojs.any = any;
28750videojs.on = on;
28751videojs.one = one;
28752videojs.off = off;
28753videojs.trigger = trigger;
28754
28755/**
28756 * A cross-browser XMLHttpRequest wrapper.
28757 *
28758 * @function
28759 * @param    {Object} options
28760 *           Settings for the request.
28761 *
28762 * @return   {XMLHttpRequest|XDomainRequest}
28763 *           The request object.
28764 *
28765 * @see      https://github.com/Raynos/xhr
28766 */
28767videojs.xhr = XHR__default['default'];
28768videojs.TextTrack = TextTrack;
28769videojs.AudioTrack = AudioTrack;
28770videojs.VideoTrack = VideoTrack;
28771[
28772  'isEl',
28773  'isTextNode',
28774  'createEl',
28775  'hasClass',
28776  'addClass',
28777  'removeClass',
28778  'toggleClass',
28779  'setAttributes',
28780  'getAttributes',
28781  'emptyEl',
28782  'appendContent',
28783  'insertContent',
28784].forEach((k) => {
28785  videojs[k] = function () {
28786    log$1.warn(`videojs.${k}() is deprecated; use videojs.dom.${k}() instead`);
28787    return Dom[k].apply(null, arguments);
28788  };
28789});
28790videojs.computedStyle = deprecateForMajor(
28791  9,
28792  'videojs.computedStyle',
28793  'videojs.dom.computedStyle',
28794  computedStyle
28795);
28796
28797/**
28798 * A reference to the {@link module:dom|DOM utility module} as an object.
28799 *
28800 * @type {Object}
28801 * @see {@link module:dom|dom}
28802 */
28803videojs.dom = Dom;
28804
28805/**
28806 * A reference to the {@link module:fn|fn utility module} as an object.
28807 *
28808 * @type {Object}
28809 * @see {@link module:fn|fn}
28810 */
28811videojs.fn = Fn;
28812
28813/**
28814 * A reference to the {@link module:num|num utility module} as an object.
28815 *
28816 * @type {Object}
28817 * @see {@link module:num|num}
28818 */
28819videojs.num = Num;
28820
28821/**
28822 * A reference to the {@link module:str|str utility module} as an object.
28823 *
28824 * @type {Object}
28825 * @see {@link module:str|str}
28826 */
28827videojs.str = Str;
28828
28829/**
28830 * A reference to the {@link module:url|URL utility module} as an object.
28831 *
28832 * @type {Object}
28833 * @see {@link module:url|url}
28834 */
28835videojs.url = Url;
28836
28837// The list of possible error types to occur in video.js
28838videojs.Error = VjsErrors;
28839
28840/*! @name videojs-contrib-quality-levels @version 4.1.0 @license Apache-2.0 */
28841
28842/**
28843 * A single QualityLevel.
28844 *
28845 * interface QualityLevel {
28846 *   readonly attribute DOMString id;
28847 *            attribute DOMString label;
28848 *   readonly attribute long width;
28849 *   readonly attribute long height;
28850 *   readonly attribute long bitrate;
28851 *            attribute boolean enabled;
28852 * };
28853 *
28854 * @class QualityLevel
28855 */
28856class QualityLevel {
28857  /**
28858   * Creates a QualityLevel
28859   *
28860   * @param {Representation|Object} representation The representation of the quality level
28861   * @param {string}   representation.id        Unique id of the QualityLevel
28862   * @param {number=}  representation.width     Resolution width of the QualityLevel
28863   * @param {number=}  representation.height    Resolution height of the QualityLevel
28864   * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28865   * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28866   * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28867   */
28868  constructor(representation) {
28869    let level = this; // eslint-disable-line
28870
28871    level.id = representation.id;
28872    level.label = level.id;
28873    level.width = representation.width;
28874    level.height = representation.height;
28875    level.bitrate = representation.bandwidth;
28876    level.frameRate = representation.frameRate;
28877    level.enabled_ = representation.enabled;
28878    Object.defineProperty(level, 'enabled', {
28879      /**
28880       * Get whether the QualityLevel is enabled.
28881       *
28882       * @return {boolean} True if the QualityLevel is enabled.
28883       */
28884      get() {
28885        return level.enabled_();
28886      },
28887      /**
28888       * Enable or disable the QualityLevel.
28889       *
28890       * @param {boolean} enable true to enable QualityLevel, false to disable.
28891       */
28892      set(enable) {
28893        level.enabled_(enable);
28894      },
28895    });
28896    return level;
28897  }
28898}
28899
28900/**
28901 * A list of QualityLevels.
28902 *
28903 * interface QualityLevelList : EventTarget {
28904 *   getter QualityLevel (unsigned long index);
28905 *   readonly attribute unsigned long length;
28906 *   readonly attribute long selectedIndex;
28907 *
28908 *   void addQualityLevel(QualityLevel qualityLevel)
28909 *   void removeQualityLevel(QualityLevel remove)
28910 *   QualityLevel? getQualityLevelById(DOMString id);
28911 *
28912 *   attribute EventHandler onchange;
28913 *   attribute EventHandler onaddqualitylevel;
28914 *   attribute EventHandler onremovequalitylevel;
28915 * };
28916 *
28917 * @extends videojs.EventTarget
28918 * @class QualityLevelList
28919 */
28920class QualityLevelList extends videojs.EventTarget {
28921  /**
28922   * Creates a QualityLevelList.
28923   */
28924  constructor() {
28925    super();
28926    let list = this; // eslint-disable-line
28927
28928    list.levels_ = [];
28929    list.selectedIndex_ = -1;
28930
28931    /**
28932     * Get the index of the currently selected QualityLevel.
28933     *
28934     * @returns {number} The index of the selected QualityLevel. -1 if none selected.
28935     * @readonly
28936     */
28937    Object.defineProperty(list, 'selectedIndex', {
28938      get() {
28939        return list.selectedIndex_;
28940      },
28941    });
28942
28943    /**
28944     * Get the length of the list of QualityLevels.
28945     *
28946     * @returns {number} The length of the list.
28947     * @readonly
28948     */
28949    Object.defineProperty(list, 'length', {
28950      get() {
28951        return list.levels_.length;
28952      },
28953    });
28954    list[Symbol.iterator] = () => list.levels_.values();
28955    return list;
28956  }
28957
28958  /**
28959   * Adds a quality level to the list.
28960   *
28961   * @param {Representation|Object} representation The representation of the quality level
28962   * @param {string}   representation.id        Unique id of the QualityLevel
28963   * @param {number=}  representation.width     Resolution width of the QualityLevel
28964   * @param {number=}  representation.height    Resolution height of the QualityLevel
28965   * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28966   * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28967   * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28968   * @return {QualityLevel} the QualityLevel added to the list
28969   * @method addQualityLevel
28970   */
28971  addQualityLevel(representation) {
28972    let qualityLevel = this.getQualityLevelById(representation.id);
28973
vendor: 4,306 bytes, lines 28974-29125
28974    // Do not add duplicate quality levels
28975    if (qualityLevel) {
28976      return qualityLevel;
28977    }
28978    const index = this.levels_.length;
28979    qualityLevel = new QualityLevel(representation);
28980    if (!('' + index in this)) {
28981      Object.defineProperty(this, index, {
28982        get() {
28983          return this.levels_[index];
28984        },
28985      });
28986    }
28987    this.levels_.push(qualityLevel);
28988    this.trigger({
28989      qualityLevel,
28990      type: 'addqualitylevel',
28991    });
28992    return qualityLevel;
28993  }
28994
28995  /**
28996   * Removes a quality level from the list.
28997   *
28998   * @param {QualityLevel} qualityLevel The QualityLevel to remove from the list.
28999   * @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
29000   * @method removeQualityLevel
29001   */
29002  removeQualityLevel(qualityLevel) {
29003    let removed = null;
29004    for (let i = 0, l = this.length; i < l; i++) {
29005      if (this[i] === qualityLevel) {
29006        removed = this.levels_.splice(i, 1)[0];
29007        if (this.selectedIndex_ === i) {
29008          this.selectedIndex_ = -1;
29009        } else if (this.selectedIndex_ > i) {
29010          this.selectedIndex_--;
29011        }
29012        break;
29013      }
29014    }
29015    if (removed) {
29016      this.trigger({
29017        qualityLevel,
29018        type: 'removequalitylevel',
29019      });
29020    }
29021    return removed;
29022  }
29023
29024  /**
29025   * Searches for a QualityLevel with the given id.
29026   *
29027   * @param {string} id The id of the QualityLevel to find.
29028   * @return {QualityLevel|null} The QualityLevel with id, or null if not found.
29029   * @method getQualityLevelById
29030   */
29031  getQualityLevelById(id) {
29032    for (let i = 0, l = this.length; i < l; i++) {
29033      const level = this[i];
29034      if (level.id === id) {
29035        return level;
29036      }
29037    }
29038    return null;
29039  }
29040
29041  /**
29042   * Resets the list of QualityLevels to empty
29043   *
29044   * @method dispose
29045   */
29046  dispose() {
29047    this.selectedIndex_ = -1;
29048    this.levels_.length = 0;
29049  }
29050}
29051
29052/**
29053 * change - The selected QualityLevel has changed.
29054 * addqualitylevel - A QualityLevel has been added to the QualityLevelList.
29055 * removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
29056 */
29057QualityLevelList.prototype.allowedEvents_ = {
29058  change: 'change',
29059  addqualitylevel: 'addqualitylevel',
29060  removequalitylevel: 'removequalitylevel',
29061};
29062
29063// emulate attribute EventHandler support to allow for feature detection
29064for (const event in QualityLevelList.prototype.allowedEvents_) {
29065  QualityLevelList.prototype['on' + event] = null;
29066}
29067var version$5 = '4.1.0';
29068
29069/**
29070 * Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
29071 * event handlers.
29072 *
29073 * @param {Player} player Player object.
29074 * @param {Object} options Plugin options object.
29075 * @return {QualityLevelList} a list of QualityLevels
29076 */
29077const initPlugin$1 = function (player, options) {
29078  const originalPluginFn = player.qualityLevels;
29079  const qualityLevelList = new QualityLevelList();
29080  const disposeHandler = function () {
29081    qualityLevelList.dispose();
29082    player.qualityLevels = originalPluginFn;
29083    player.off('dispose', disposeHandler);
29084  };
29085  player.on('dispose', disposeHandler);
29086  player.qualityLevels = () => qualityLevelList;
29087  player.qualityLevels.VERSION = version$5;
29088  return qualityLevelList;
29089};
29090
29091/**
29092 * A video.js plugin.
29093 *
29094 * In the plugin function, the value of `this` is a video.js `Player`
29095 * instance. You cannot rely on the player being in a "ready" state here,
29096 * depending on how the plugin is invoked. This may or may not be important
29097 * to you; if not, remove the wait for "ready"!
29098 *
29099 * @param {Object} options Plugin options object
29100 * @return {QualityLevelList} a list of QualityLevels
29101 */
29102const qualityLevels = function (options) {
29103  return initPlugin$1(this, videojs.obj.merge({}, options));
29104};
29105
29106// Register the plugin with video.js.
29107videojs.registerPlugin('qualityLevels', qualityLevels);
29108
29109// Include the version number.
29110qualityLevels.VERSION = version$5;
29111
29112/*! @name @videojs/http-streaming @version 3.16.0 @license Apache-2.0 */
29113
29114/**
29115 * @file resolve-url.js - Handling how URLs are resolved and manipulated
29116 */
29117const resolveUrl = _resolveUrl__default['default'];
29118/**
29119 * If the xhr request was redirected, return the responseURL, otherwise,
29120 * return the original url.
29121 *
29122 * @api private
29123 *
29124 * @param  {string} url - an url being requested
29125 * @param  {XMLHttpRequest}
vendor: 10,079 bytes, lines 29125-29495
29125 req - xhr request result
29126 *
29127 * @return {string}
29128 */
29129
29130const resolveManifestRedirect = (url, req) => {
29131  // To understand how the responseURL below is set and generated:
29132  // - https://fetch.spec.whatwg.org/#concept-response-url
29133  // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
29134  if (req && req.responseURL && url !== req.responseURL) {
29135    return req.responseURL;
29136  }
29137  return url;
29138};
29139const logger = (source) => {
29140  if (videojs.log.debug) {
29141    return videojs.log.debug.bind(videojs, 'VHS:', `${source} >`);
29142  }
29143  return function () {};
29144};
29145
29146/**
29147 * Provides a compatibility layer between Video.js 7 and 8 API changes for VHS.
29148 */
29149/**
29150 * Delegates to videojs.obj.merge (Video.js 8) or
29151 * videojs.mergeOptions (Video.js 7).
29152 */
29153
29154function merge(...args) {
29155  const context = videojs.obj || videojs;
29156  const fn = context.merge || context.mergeOptions;
29157  return fn.apply(context, args);
29158}
29159/**
29160 * Delegates to videojs.time.createTimeRanges (Video.js 8) or
29161 * videojs.createTimeRanges (Video.js 7).
29162 */
29163
29164function createTimeRanges(...args) {
29165  const context = videojs.time || videojs;
29166  const fn = context.createTimeRanges || context.createTimeRanges;
29167  return fn.apply(context, args);
29168}
29169/**
29170 * Converts provided buffered ranges to a descriptive string
29171 *
29172 * @param {TimeRanges} buffered - received buffered time ranges
29173 *
29174 * @return {string} - descriptive string
29175 */
29176
29177function bufferedRangesToString(buffered) {
29178  if (buffered.length === 0) {
29179    return 'Buffered Ranges are empty';
29180  }
29181  let bufferedRangesStr = 'Buffered Ranges: \n';
29182  for (let i = 0; i < buffered.length; i++) {
29183    const start = buffered.start(i);
29184    const end = buffered.end(i);
29185    bufferedRangesStr += `${start} --> ${end}. Duration (${end - start})\n`;
29186  }
29187  return bufferedRangesStr;
29188}
29189
29190/**
29191 * ranges
29192 *
29193 * Utilities for working with TimeRanges.
29194 *
29195 */
29196
29197const TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
29198// can be misleading because of precision differences or when the current media has poorly
29199// aligned audio and video, which can cause values to be slightly off from what you would
29200// expect. This value is what we consider to be safe to use in such comparisons to account
29201// for these scenarios.
29202
29203const SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
29204const filterRanges = function (timeRanges, predicate) {
29205  const results = [];
29206  let i;
29207  if (timeRanges && timeRanges.length) {
29208    // Search for ranges that match the predicate
29209    for (i = 0; i < timeRanges.length; i++) {
29210      if (predicate(timeRanges.start(i), timeRanges.end(i))) {
29211        results.push([timeRanges.start(i), timeRanges.end(i)]);
29212      }
29213    }
29214  }
29215  return createTimeRanges(results);
29216};
29217/**
29218 * Attempts to find the buffered TimeRange that contains the specified
29219 * time.
29220 *
29221 * @param {TimeRanges} buffered - the TimeRanges object to query
29222 * @param {number} time  - the time to filter on.
29223 * @return {TimeRanges} a new TimeRanges object
29224 */
29225
29226const findRange = function (buffered, time) {
29227  return filterRanges(buffered, function (start, end) {
29228    return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
29229  });
29230};
29231/**
29232 * Returns the TimeRanges that begin later than the specified time.
29233 *
29234 * @param {TimeRanges} timeRanges - the TimeRanges object to query
29235 * @param {number} time - the time to filter on.
29236 * @return {TimeRanges} a new TimeRanges object.
29237 */
29238
29239const findNextRange = function (timeRanges, time) {
29240  return filterRanges(timeRanges, function (start) {
29241    return start - TIME_FUDGE_FACTOR >= time;
29242  });
29243};
29244/**
29245 * Returns gaps within a list of TimeRanges
29246 *
29247 * @param {TimeRanges} buffered - the TimeRanges object
29248 * @return {TimeRanges} a TimeRanges object of gaps
29249 */
29250
29251const findGaps = function (buffered) {
29252  if (buffered.length < 2) {
29253    return createTimeRanges();
29254  }
29255  const ranges = [];
29256  for (let i = 1; i < buffered.length; i++) {
29257    const start = buffered.end(i - 1);
29258    const end = buffered.start(i);
29259    ranges.push([start, end]);
29260  }
29261  return createTimeRanges(ranges);
29262};
29263/**
29264 * Calculate the intersection of two TimeRanges
29265 *
29266 * @param {TimeRanges} bufferA
29267 * @param {TimeRanges} bufferB
29268 * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
29269 */
29270
29271const bufferIntersection = function (bufferA, bufferB) {
29272  let start = null;
29273  let end = null;
29274  let arity = 0;
29275  const extents = [];
29276  const ranges = [];
29277  if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
29278    return createTimeRanges();
29279  } // Handle the case where we have both buffers and create an
29280  // intersection of the two
29281
29282  let count = bufferA.length; // A) Gather up all start and end times
29283
29284  while (count--) {
29285    extents.push({
29286      time: bufferA.start(count),
29287      type: 'start',
29288    });
29289    extents.push({
29290      time: bufferA.end(count),
29291      type: 'end',
29292    });
29293  }
29294  count = bufferB.length;
29295  while (count--) {
29296    extents.push({
29297      time: bufferB.start(count),
29298      type: 'start',
29299    });
29300    extents.push({
29301      time: bufferB.end(count),
29302      type: 'end',
29303    });
29304  } // B) Sort them by time
29305
29306  extents.sort(function (a, b) {
29307    return a.time - b.time;
29308  }); // C) Go along one by one incrementing arity for start and decrementing
29309  //    arity for ends
29310
29311  for (count = 0; count < extents.length; count++) {
29312    if (extents[count].type === 'start') {
29313      arity++; // D) If arity is ever incremented to 2 we are entering an
29314      //    overlapping range
29315
29316      if (arity === 2) {
29317        start = extents[count].time;
29318      }
29319    } else if (extents[count].type === 'end') {
29320      arity--; // E) If arity is ever decremented to 1 we leaving an
29321      //    overlapping range
29322
29323      if (arity === 1) {
29324        end = extents[count].time;
29325      }
29326    } // F) Record overlapping ranges
29327
29328    if (start !== null && end !== null) {
29329      ranges.push([start, end]);
29330      start = null;
29331      end = null;
29332    }
29333  }
29334  return createTimeRanges(ranges);
29335};
29336/**
29337 * Gets a human readable string for a TimeRange
29338 *
29339 * @param {TimeRange} range
29340 * @return {string} a human readable string
29341 */
29342
29343const printableRange = (range) => {
29344  const strArr = [];
29345  if (!range || !range.length) {
29346    return '';
29347  }
29348  for (let i = 0; i < range.length; i++) {
29349    strArr.push(range.start(i) + ' => ' + range.end(i));
29350  }
29351  return strArr.join(', ');
29352};
29353/**
29354 * Calculates the amount of time left in seconds until the player hits the end of the
29355 * buffer and causes a rebuffer
29356 *
29357 * @param {TimeRange} buffered
29358 *        The state of the buffer
29359 * @param {Numnber} currentTime
29360 *        The current time of the player
29361 * @param {number} playbackRate
29362 *        The current playback rate of the player. Defaults to 1.
29363 * @return {number}
29364 *         Time until the player has to start rebuffering in seconds.
29365 * @function timeUntilRebuffer
29366 */
29367
29368const timeUntilRebuffer = function (buffered, currentTime, playbackRate = 1) {
29369  const bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
29370  return (bufferedEnd - currentTime) / playbackRate;
29371};
29372/**
29373 * Converts a TimeRanges object into an array representation
29374 *
29375 * @param {TimeRanges} timeRanges
29376 * @return {Array}
29377 */
29378
29379const timeRangesToArray = (timeRanges) => {
29380  const timeRangesList = [];
29381  for (let i = 0; i < timeRanges.length; i++) {
29382    timeRangesList.push({
29383      start: timeRanges.start(i),
29384      end: timeRanges.end(i),
29385    });
29386  }
29387  return timeRangesList;
29388};
29389/**
29390 * Determines if two time range objects are different.
29391 *
29392 * @param {TimeRange} a
29393 *        the first time range object to check
29394 *
29395 * @param {TimeRange} b
29396 *        the second time range object to check
29397 *
29398 * @return {Boolean}
29399 *         Whether the time range objects differ
29400 */
29401
29402const isRangeDifferent = function (a, b) {
29403  // same object
29404  if (a === b) {
29405    return false;
29406  } // one or the other is undefined
29407
29408  if ((!a && b) || (!b && a)) {
29409    return true;
29410  } // length is different
29411
29412  if (a.length !== b.length) {
29413    return true;
29414  } // see if any start/end pair is different
29415
29416  for (let i = 0; i < a.length; i++) {
29417    if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
29418      return true;
29419    }
29420  } // if the length and every pair is the same
29421  // this is the same time range
29422
29423  return false;
29424};
29425const lastBufferedEnd = function (a) {
29426  if (!a || !a.length || !a.end) {
29427    return;
29428  }
29429  return a.end(a.length - 1);
29430};
29431/**
29432 * A utility function to add up the amount of time in a timeRange
29433 * after a specified startTime.
29434 * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
29435 *     would return 40 as there are 40s seconds after 0 in the timeRange
29436 *
29437 * @param {TimeRange} range
29438 *        The range to check against
29439 * @param {number} startTime
29440 *        The time in the time range that you should start counting from
29441 *
29442 * @return {number}
29443 *          The number of seconds in the buffer passed the specified time.
29444 */
29445
29446const timeAheadOf = function (range, startTime) {
29447  let time = 0;
29448  if (!range || !range.length) {
29449    return time;
29450  }
29451  for (let i = 0; i < range.length; i++) {
29452    const start = range.start(i);
29453    const end = range.end(i); // startTime is after this range entirely
29454
29455    if (startTime > end) {
29456      continue;
29457    } // startTime is within this range
29458
29459    if (startTime > start && startTime <= end) {
29460      time += end - startTime;
29461      continue;
29462    } // startTime is before this range.
29463
29464    time += end - start;
29465  }
29466  return time;
29467};
29468
29469/**
29470 * @file playlist.js
29471 *
29472 * Playlist related utilities.
29473 */
29474/**
29475 * Get the duration of a segment, with special cases for
29476 * llhls segments that do not have a duration yet.
29477 *
29478 * @param {Object} playlist
29479 *        the playlist that the segment belongs to.
29480 * @param {Object} segment
29481 *        the segment to get a duration for.
29482 *
29483 * @return {number}
29484 *          the segment duration
29485 */
29486
29487const segmentDurationWithParts = (playlist, segment) => {
29488  // if this isn't a preload segment
29489  // then we will have a segment duration that is accurate.
29490  if (!segment.preload) {
29491    return segment.duration;
29492  } // otherwise we have to add up parts and preload hints
29493  // to get an up to date duration.
29494
29495  let result = 0;
29496  (segment.parts || []).forEach(function (p) {
29497    result += p.duration;
29498  }); // for preload hints we have to use partTargetDuration
29499  // as they won't even have a duration yet.
29500
29501  (segment.preloadHints || []).forEach(function (p) {
29502    if (p.type === 'PART') {
29503      result += playlist.partTargetDuration;
29504    }
29505  });
29506  return result;
29507};
29508/**
29509 * A function to get a combined list of parts and segments with durations
29510 * and indexes.
29511 *
29512 * @param {Playlist} playlist the playlist to get the list for.
29513 *
29514 * @return {Array} The part/segment list.
29515 */
29516
29517const getPartsAndSegments = (playlist) =>
29518  (playlist.segments || []).reduce((acc, segment, si) => {
29519    if (segment.parts) {
29520      segment.parts.forEach(function (part, pi) {
29521        acc.push({
29522          duration: part.duration,
29523          segmentIndex: si,
29524          partIndex: pi,
29525          part,
29526          segment,
29527        });
29528      });
29529    } else {
29530      acc.push({
29531        duration: segment.duration,
29532        segmentIndex: si,
29533        partIndex: null,
29534        segment,
29535        part: null,
29536      });
29537    }
29538    return acc;
29539  }, []);
29540const getLastParts = (media) => {
29541  const lastSegment =
29542    media.segments && media.segments.length && media.segments[media.segments.length - 1];
29543  return (lastSegment && lastSegment.parts) || [];
29544};
29545const getKnownPartCount = ({ preloadSegment }) => {
29546  if (!preloadSegment) {
29547    return;
29548  }
29549  const { parts, preloadHints } = preloadSegment;
29550  let partCount = (preloadHints || []).reduce(
29551    (count, hint) => count + (hint.type === 'PART' ? 1 : 0),
29552    0
29553  );
29554  partCount += parts && parts.length ? parts.length : 0;
29555  return partCount;
29556};
29557/**
29558 * Get the number of seconds to delay from the end of a
29559 * live playlist.
29560 *
29561 * @param {Playlist} main the main playlist
29562 * @param {Playlist} media the media playlist
29563 * @return {number} the hold back in seconds.
29564 */
29565
29566const liveEdgeDelay = (main, media) => {
29567  if (media.endList) {
29568    return 0;
29569  } // dash suggestedPresentationDelay trumps everything
29570
29571  if (main && main.suggestedPresentationDelay) {
29572    return main.suggestedPresentationDelay;
29573  }
29574  const hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
29575
29576  if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
29577    return media.serverControl.partHoldBack;
29578  } else if (hasParts && media.partTargetDuration) {
29579    return media.partTargetDuration * 3; // finally look for full segment delays
29580  } else if (media.serverControl && media.serverControl.holdBack) {
29581    return media.serverControl.holdBack;
29582  } else if (media.targetDuration) {
29583    return media.targetDuration * 3;
29584  }
29585  return 0;
29586};
29587/**
29588 * walk backward until we find a duration we can use
29589 * or return a failure
29590 *
29591 * @param {Playlist} playlist the playlist to walk through
29592 * @param {Number} endSequence the mediaSequence to stop walking on
29593 */
29594
29595const backwardDuration = function (playlist, endSequence) {
29596  let result = 0;
29597  let i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
29598  // the interval, use it
29599
29600  let segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
29601  // information that is earlier than endSequence
29602
29603  if (segment) {
29604    if (typeof segment.start !== 'undefined') {
29605      return {
29606        result: segment.start,
29607        precise: true,
29608      };
29609    }
29610    if (typeof segment.end !== 'undefined') {
29611      return {
29612        result: segment.end - segment.duration,
29613        precise: true,
29614      };
29615    }
29616  }
29617  while (i--) {
29618    segment = playlist.segments[i];
29619    if (typeof segment.end !== 'undefined') {
29620      return {
29621        result: result + segment.end,
29622        precise: true,
29623      };
29624    }
29625    result += segmentDurationWithParts(playlist, segment);
29626    if (typeof segment.start !== 'undefined') {
29627      return {
29628        result: result + segment.start,
29629        precise: true,
29630      };
29631    }
29632  }
29633  return {
29634    result,
29635    precise: false,
29636  };
29637};
29638/**
29639 * walk forward until we find a duration we can use
29640 * or return a failure
29641 *
29642 * @param {Playlist} playlist the playlist to walk through
29643 * @param {number} endSequence the mediaSequence to stop walking on
29644 */
29645
29646const forwardDuration = function (playlist, endSequence) {
29647  let result = 0;
29648  let segment;
29649  let i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
29650  // information
29651
29652  for (; i < playlist.segments.length; i++) {
29653    segment = playlist.segments[i];
29654    if (typeof segment.start !== 'undefined') {
29655      return {
29656        result: segment.start - result,
29657        precise: true,
29658      };
29659    }
29660    result += segmentDurationWithParts(playlist, segment);
29661    if (typeof segment.end !== 'undefined') {
29662      return {
29663        result: segment.end - result,
29664        precise: true,
29665      };
29666    }
29667  } // indicate we didn't find a useful duration estimate
29668
29669  return {
vendor: 4,793 bytes, lines 29670-29792
29670    result: -1,
29671    precise: false,
29672  };
29673};
29674/**
29675 * Calculate the media duration from the segments associated with a
29676 * playlist. The duration of a subinterval of the available segments
29677 * may be calculated by specifying an end index.
29678 *
29679 * @param {Object} playlist a media playlist object
29680 * @param {number=} endSequence an exclusive upper boundary
29681 * for the playlist.  Defaults to playlist length.
29682 * @param {number} expired the amount of time that has dropped
29683 * off the front of the playlist in a live scenario
29684 * @return {number} the duration between the first available segment
29685 * and end index.
29686 */
29687
29688const intervalDuration = function (playlist, endSequence, expired) {
29689  if (typeof endSequence === 'undefined') {
29690    endSequence = playlist.mediaSequence + playlist.segments.length;
29691  }
29692  if (endSequence < playlist.mediaSequence) {
29693    return 0;
29694  } // do a backward walk to estimate the duration
29695
29696  const backward = backwardDuration(playlist, endSequence);
29697  if (backward.precise) {
29698    // if we were able to base our duration estimate on timing
29699    // information provided directly from the Media Source, return
29700    // it
29701    return backward.result;
29702  } // walk forward to see if a precise duration estimate can be made
29703  // that way
29704
29705  const forward = forwardDuration(playlist, endSequence);
29706  if (forward.precise) {
29707    // we found a segment that has been buffered and so it's
29708    // position is known precisely
29709    return forward.result;
29710  } // return the less-precise, playlist-based duration estimate
29711
29712  return backward.result + expired;
29713};
29714/**
29715 * Calculates the duration of a playlist. If a start and end index
29716 * are specified, the duration will be for the subset of the media
29717 * timeline between those two indices. The total duration for live
29718 * playlists is always Infinity.
29719 *
29720 * @param {Object} playlist a media playlist object
29721 * @param {number=} endSequence an exclusive upper
29722 * boundary for the playlist. Defaults to the playlist media
29723 * sequence number plus its length.
29724 * @param {number=} expired the amount of time that has
29725 * dropped off the front of the playlist in a live scenario
29726 * @return {number} the duration between the start index and end
29727 * index.
29728 */
29729
29730const duration = function (playlist, endSequence, expired) {
29731  if (!playlist) {
29732    return 0;
29733  }
29734  if (typeof expired !== 'number') {
29735    expired = 0;
29736  } // if a slice of the total duration is not requested, use
29737  // playlist-level duration indicators when they're present
29738
29739  if (typeof endSequence === 'undefined') {
29740    // if present, use the duration specified in the playlist
29741    if (playlist.totalDuration) {
29742      return playlist.totalDuration;
29743    } // duration should be Infinity for live playlists
29744
29745    if (!playlist.endList) {
29746      return window__default['default'].Infinity;
29747    }
29748  } // calculate the total duration based on the segment durations
29749
29750  return intervalDuration(playlist, endSequence, expired);
29751};
29752/**
29753 * Calculate the time between two indexes in the current playlist
29754 * neight the start- nor the end-index need to be within the current
29755 * playlist in which case, the targetDuration of the playlist is used
29756 * to approximate the durations of the segments
29757 *
29758 * @param {Array} options.durationList list to iterate over for durations.
29759 * @param {number} options.defaultDuration duration to use for elements before or after the durationList
29760 * @param {number} options.startIndex partsAndSegments index to start
29761 * @param {number} options.endIndex partsAndSegments index to end.
29762 * @return {number} the number of seconds between startIndex and endIndex
29763 */
29764
29765const sumDurations = function ({ defaultDuration, durationList, startIndex, endIndex }) {
29766  let durations = 0;
29767  if (startIndex > endIndex) {
29768    [startIndex, endIndex] = [endIndex, startIndex];
29769  }
29770  if (startIndex < 0) {
29771    for (let i = startIndex; i < Math.min(0, endIndex); i++) {
29772      durations += defaultDuration;
29773    }
29774    startIndex = 0;
29775  }
29776  for (let i = startIndex; i < endIndex; i++) {
29777    durations += durationList[i].duration;
29778  }
29779  return durations;
29780};
29781/**
29782 * Calculates the playlist end time
29783 *
29784 * @param {Object} playlist a media playlist object
29785 * @param {number=} expired the amount of time that has
29786 *                  dropped off the front of the playlist in a live scenario
29787 * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
29788 *                        playlist end calculation should consider the safe live end
29789 *                        (truncate the playlist end by three segments). This is normally
29790 *                        used for calculating the end of the playlist's seekable range.
29791 *                        This takes into account the value of liveEdgePadding.
29792 *                        Setting liveEdgePadding to 0 is equivalent to setting this to fal
29792se.
29793 * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
29794 *                 If this is provided, it is used in the safe live end calculation.
29795 *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
29796 *                 Corresponds to suggestedPresentationDelay in DASH manifests.
29797 * @return {number} the end time of playlist
29798 * @function playlistEnd
29799 */
29800
29801const playlistEnd = function (playlist, expired, useSafeLiveEnd, liveEdgePadding) {
29802  if (!playlist || !playlist.segments) {
29803    return null;
29804  }
29805  if (playlist.endList) {
29806    return duration(playlist);
29807  }
29808  if (expired === null) {
29809    return null;
29810  }
29811  expired = expired || 0;
29812  let lastSegmentEndTime = intervalDuration(
29813    playlist,
29814    playlist.mediaSequence + playlist.segments.length,
29815    expired
29816  );
29817  if (useSafeLiveEnd) {
29818    liveEdgePadding =
29819      typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
29820    lastSegmentEndTime -= liveEdgePadding;
29821  } // don't return a time less than zero
29822
29823  return Math.max(0, lastSegmentEndTime);
29824};
29825/**
29826 * Calculates the interval of time that is currently seekable in a
29827 * playlist. The returned time ranges are relative to the earliest
29828 * moment in the specified playlist that is still available. A full
29829 * seekable implementation for live streams would need to offset
29830 * these values by the duration of content that has expired from the
29831 * stream.
29832 *
29833 * @param {Object} playlist a media playlist object
29834 * dropped off the front of the playlist in a live scenario
29835 * @param {number=} expired the amount of time that has
29836 * dropped off the front of the playlist in a live scenario
29837 * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
29838 *        Corresponds to suggestedPresentationDelay in DASH manifests.
29839 * @return {TimeRanges} the periods of time that are valid targets
29840 * for seeking
29841 */
29842
29843const seekable = function (playlist, expired, liveEdgePadding) {
29844  const useSafeLiveEnd = true;
29845  const seekableStart = expired || 0;
29846  let seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
29847  if (seekableEnd === null) {
29848    return createTimeRanges();
29849  } // Clamp seekable end since it can not be less than the seekable start
29850
29851  if (seekableEnd < seekableStart) {
29852    seekableEnd = seekableStart;
29853  }
29854  return createTimeRanges(seekableStart, seekableEnd);
29855};
29856/**
29857 * Determine the index and estimated starting time of the segment that
29858 * contains a specified playback position in a media playlist.
29859 *
29860 * @param {Object} options.playlist the media playlist to query
29861 * @param {number} options.currentTime The number of seconds since the earliest
29862 * possible position to determine the containing segment for
29863 * @param {number} options.startTime the time when the segment/part starts
29864 * @param {number} options.startingSegmentIndex the segment index to start looking at.
29865 * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
29866 *
29867 * @return {Object} an object with partIndex, segmentIndex, and startTime.
29868 */
29869
29870const getMediaInfoForTime = function ({
29871  playlist,
29872  currentTime,
29873  startingSegmentIndex,
29874  startingPartIndex,
29875  startTime,
29876  exactManifestTimings,
29877}) {
29878  let time = currentTime - startTime;
29879  const partsAndSegments = getPartsAndSegments(playlist);
29880  let startIndex = 0;
29881  for (let i = 0; i < partsAndSegments.length; i++) {
29882    const partAndSegment = partsAndSegments[i];
29883    if (startingSegmentIndex !== partAndSegment.segmentIndex) {
29884      continue;
29885    } // skip this if part index does not match.
29886
29887    if (
29888      typeof startingPartIndex === 'number' &&
29889      typeof partAndSegment.partIndex === 'number' &&
29890      startingPartIndex !== partAndSegment.partIndex
29891    ) {
29892      continue;
29893    }
29894    startIndex = i;
29895    break;
29896  }
29897  if (time < 0) {
29898    // Walk backward from startIndex in the playlist, adding durations
29899    // until we find a segment that contains `time` and return it
29900    if (startIndex > 0) {
29901      for (let i = startIndex - 1; i >= 0; i--) {
29902        const partAndSegment = partsAndSegments[i];
29903        time += partAndSegment.duration;
29904        if (exactManifestTimings) {
29905          if (time < 0) {
29906            continue;
29907          }
29908        } else if (time + TIME_FUDGE_FACTOR <= 0) {
29909          continue;
29910        }
29911        return {
29912          partIndex: partAndSegment.partIndex,
29913          segmentIndex: partAndSegment.segmentIndex,
29914          startTime:
29915            startTime -
29916            sumDurations({
29917              defaultDuration: playlist.targetDuration,
29918              durationList: partsAndSegments,
29919              startIndex,
29920              endIndex: i,
29921            }),
29922        };
29923      }
29924    } // We were unable to find a good segment within the playlist
29925    // so select the first segment
29926
29927    return {
29928      partIndex: (partsAndSegments[0] && partsAndSegments[0].partIndex) || null,
29929      segmentIndex: (partsAndSegments[0] && partsAndSegments[0].segmentIndex) || 0,
29930      startTime: currentTime,
29931    };
29932  } // When startIndex is negative, we first walk forward to first segment
29933  // adding target durations. If we "run out of time" before getting to
29934  // the first segment, return the first segment
29935
29936  if (startIndex < 0) {
29937    for (let i = startIndex; i < 0; i++) {
29938      time -= playlist.targetDuration;
29939      if (time < 0) {
29940        return {
29941          partIndex: (partsAndSegments[0] && partsAndSegments[0].partIndex) || null,
29942          segmentIndex: (partsAndSegments[0] && partsAndSegments[0].segmentIndex) || 0,
29943          startTime: currentTime,
29944        };
29945      }
29946    }
29947    startIndex = 0;
29948  } // Walk forward from startIndex in the playlist, subtracting durations
29949  // until we find a segment that contains `time` and return it
29950
29951  for (let i = startIndex; i < partsAndSegments.length; i++) {
29952    const partAndSegment = partsAndSegments[i];
29953    time -= partAndSegment.duration;
29954    const canUseFudgeFactor = partAndSegment.duration > TIME_FUDGE_FACTOR;
29955    const isExactlyAtTheEnd = time === 0;
29956    const isExtremelyCloseToTheEnd = canUseFudgeFactor && time + TIME_FUDGE_FACTOR >= 0;
29957    if (isExactlyAtTheEnd || isExtremelyCloseToTheEnd) {
29958      // 1) We are exactly at the end of the current segment.
29959      // 2) We are extremely close to the end of the current segment (The difference is less than  1 / 30).
29960      //    We may encounter this situation when
29961      //    we don't have exact match between segment duration info in the manifest and the actual duration of the segment
29962      //    For example:
29963      //    We appended 3 segments 10 seconds each, meaning we should have 30 sec buffered,
29964      //    but we the actual buffered is 29.99999
29965      //
29966      // In both cases:
29967      // if we passed current time -> it means that we already played current segment
29968      // if we passed buffered.end -> it means that this segment is already loaded and buffered
29969      // we should select the next segment if we have one:
29970      if (i !== partsAndSegments.length - 1) {
29971        continue;
29972      }
29973    }
29974    if (exactManifestTimings) {
29975      if (time > 0) {
29976        continue;
29977      }
29978    } else if (time - TIME_FUDGE_FACTOR >= 0) {
29979      continue;
29980    }
29981    return {
29982      partIndex: partAndSegment.partIndex,
29983      segmentIndex: partAndSegment.segmentIndex,
29984      startTime:
29985        startTime +
29986        sumDurations({
29987          defaultDuration: playlist.targetDuration,
29988          durationList: partsAndSegments,
29989          startIndex,
29990          endIndex: i,
29991        }),
29992    };
29993  } // We are out of possible candidates so load the last one...
29994
29995  return {
29996    segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
29997    partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
29998    startTime: currentTime,
29999  };
30000};
30001/**
30002 * Check whether the playlist is excluded or not.
30003 *
30004 * @param {Object} playlist the media playlist object
30005 * @return {boolean} whether the playlist is excluded or not
30006 * @function isExcluded
30007 */
30008
30009const isExcluded = function (playlist) {
30010  return playlist.excludeUntil && playlist.excludeUntil > Date.now();
30011};
30012/**
30013 * Check whether the playlist is compatible with current playback configuration or has
30014 * been excluded permanently for being incompatible.
30015 *
30016 * @param {Object} playlist the media playlist object
30017 * @return {boolean} whether the playlist is incompatible or not
30018 * @function isIncompatible
30019 */
30020
30021const isIncompatible = function (playlist) {
30022  return playlist.excludeUntil && playlist.excludeUntil === Infinity;
30023};
30024/**
30025 * Check whether the playlist is enabled or not.
30026 *
30027 * @param {Object} playlist the media playlist object
30028 * @return {boolean} whether the playlist is enabled or not
30029 * @function isEnabled
30030 */
30031
30032const isEnabled = function (playlist) {
30033  const excluded = isExcluded(playlist);
30034  return !playlist.disabled && !excluded;
30035};
30036/**
30037 * Check whether the playlist has been manually disabled through the representations api.
30038 *
30039 * @param {Object} playlist the media playlist object
30040 * @return {boolean} whether the playlist is disabled manually or not
30041 * @function isDisabled
30042 */
30043
30044const isDisabled = function (playlist) {
30045  return playlist.disabled;
30046};
30047/**
30048 * Returns whether the current playlist is an AES encrypted HLS stream
30049 *
30050 * @return {boolean} true if it's an AES encrypted HLS stream
30051 */
30052
30053const isAes = function (media) {
30054  for (let i = 0; i < media.segments.length; i++) {
30055    if (media.segments[i].key) {
30056      return true;
30057    }
30058  }
30059  return false;
30060};
30061/**
30062 * Checks if the playlist has a value for the specified attribute
30063 *
30064 * @param {string} attr
30065 *        Attribute to check for
30066 * @param {Object} playlist
30067 *        The media playlist object
30068 * @return {boolean}
30069 *         Whether the playlist contains a value for the attribute or not
30070 * @function hasAttribute
30071 */
30072
30073const hasAttribute = function (attr, playlist) {
30074  return playlist.attributes && playlist.attributes[attr];
30075};
30076/**
30077 * Estimates the time required to complete a segment download from the specified playlist
30078 *
30079 * @param {number} segmentDuration
30080 *        Duration of requested segment
30081 * @param {number} bandwidth
30082 *        Current measured bandwidth of the player
30083 * @param {Object} playlist
30084 *        The media playlist object
30085 * @param {number=} bytesReceived
30086 *        Number of bytes already received for the request. Defaults to 0
30087 * @return {number|NaN}
30088 *         The estimated time to request the segment. NaN if bandwidth information for
30089 *         the given playlist is unavailable
30090 * @function estimateSegmentRequestTime
30091 */
30092
30093const estimateSegmentRequestTime = function (
30094  segmentDuration,
30095  bandwidth,
30096  playlist,
30097  bytesReceived = 0
30098) {
30099  if (!hasAttribute('BANDWIDTH', playlist)) {
30100    return NaN;
30101  }
30102  const size = segmentDuration * playlist.attributes.BANDWIDTH;
30103  return (size - bytesReceived * 8) / bandwidth;
30104};
30105/*
30106 * Returns whether the current playlist is the lowest rendition
30107 *
30108 * @return {Boolean} true if on lowest rendition
30109 */
30110
30111const isLowestEnabledRendition = (main, media) => {
30112  if (main.playlists.length === 1) {
30113    return true;
30114  }
30115  const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
30116  return (
30117    main.playlists.filter((playlist) => {
30118      if (!isEnabled(playlist)) {
30119        return false;
30120      }
30121      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
30122    }).length === 0
30123  );
30124};
30125const playlistMatch = (a, b) => {
30126  // both playlits are null
30127  // or only one playlist is non-null
30128  // no match
30129  if ((!a && !b) || (!a && b) || (a && !b)) {
30130    return false;
30131  } // playlist objects are the same, match
30132
30133  if (a === b) {
30134    return true;
30135  } // first try to use id as it should be the most
30136  // accurate
30137
30138  if (a.id && b.id && a.id === b.id) {
30139    return true;
30140  } // next try to use reslovedUri as it should be the
30141  // second most accurate.
30142
30143  if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
30144    return true;
30145  } // finally try to use uri as it should be accurate
30146  // but might miss a few cases for relative uris
30147
30148  if (a.uri && b.uri && a.uri === b.uri) {
30149    return true;
30150  }
30151  return false;
30152};
30153const someAudioVariant = function (main, callback) {
30154  const AUDIO = (main && main.mediaGroups && main.mediaGroups.AUDIO) || {};
30155  let found = false;
30156  for (const groupName in AUDIO) {
30157    for (const label in AUDIO[groupName]) {
30158      found = callback(AUDIO[groupName][label]);
30159      if (found) {
30160        break;
30161      }
30162    }
30163    if (found) {
30164      break;
30165    }
30166  }
30167  return !!found;
30168};
30169const isAudioOnly = (main) => {
30170  // we are audio only if we have no main playlists but do
30171  // have media group playlists.
30172  if (!main || !main.playlists || !main.playlists.length) {
30173    // without audio variants or playlists this
30174    // is not an audio only main.
30175    const found = someAudioVariant(
30176      main,
30177      (variant) => (variant.playlists && variant.playlists.length) || variant.uri
30178    );
30179    return found;
30180  } // if every playlist has only an audio codec it is audio only
30181
30182  for (let i = 0; i < main.playlists.length; i++) {
30183    const playlist = main.playlists[i];
30184    const CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
30185
30186    if (CODECS && CODECS.split(',').every((c) => codecs_js.isAudioCodec(c))) {
30187      continue;
30188    } // playlist is in an audio group it is audio only
30189
30190    const found = someAudioVariant(main, (variant) => playlistMatch(playlist, variant));
30191    if (found) {
30192      continue;
30193    } // if we make it here this playlist isn't audio and we
30194    // are not audio only
30195
30196    return false;
30197  } // if we make it past every playlist without returning, then
30198  // this is an audio only playlist.
30199
30200  return true;
30201}; // exports
30202
30203var Playlist = {
30204  liveEdgeDelay,
30205  duration,
30206  seekable,
30207  getMediaInfoForTime,
30208  isEnabled,
30209  isDisabled,
30210  isExcluded,
30211  isIncompatible,
30212  playlistEnd,
30213  isAes,
30214  hasAttribute,
30215  estimateSegmentRequestTime,
30216  isLowestEnabledRendition,
30217  isAudioOnly,
30218  playlistMatch,
30219  segmentDurationWithParts,
30220};
30221const { log } = videojs;
30222const createPlaylistID = (index, uri) => {
30223  return `${index}-${uri}`;
30224}; // default function for creating a group id
30225
30226const groupID = (type, group, label) => {
30227  return `placeholder-uri-${type}-${group}-${label}`;
30228};
30229/**
30230 * Parses a given m3u8 playlist
30231 *
30232 * @param {Function} [onwarn]
30233 *        a function to call when the parser triggers a warning event.
30234 * @param {Function} [oninfo]
30235 *        a function to call when the parser triggers an info event.
30236 * @param {string} manifestString
30237 *        The downloaded manifest string
30238 * @param {Object[]} [customTagParsers]
30239 *        An array of custom tag parsers for the m3u8-parser instance
30240 * @param {Object[]} [customTagMappers]
30241 *        An array of custom tag mappers for the m3u8-parser instance
30242 * @param {boolean} [llhls]
30243 *        Whether to keep ll-hls features in the manifest after parsing.
30244 * @return {Object}
30245 *         The manifest object
30246 */
30247
30248const parseManifest = ({
30249  onwarn,
30250  oninfo,
30251  manifestString,
30252  customTagParsers = [],
30253  customTagMappers = [],
30254  llhls,
30255}) => {
30256  const parser = new m3u8Parser.Parser();
30257  if (onwarn) {
30258    parser.on('warn', onwarn);
30259  }
30260  if (oninfo) {
30261    parser.on('info', oninfo);
30262  }
30263  customTagParsers.forEach((customParser) => parser.addParser(customParser));
30264  customTagMappers.forEach((mapper) => parser.addTagMapper(mapper));
30265  parser.push(manifestString);
30266  parser.end();
30267  const manifest = parser.manifest; // remove llhls features from the parsed manifest
30268  // if we don't want llhls support.
30269
30270  if (!llhls) {
30271    [
30272      'preloadSegment',
30273      'skip',
30274      'serverControl',
30275      'renditionReports',
30276      'partInf',
30277      'partTargetDuration',
30278    ].forEach(function (k) {
30279      if (manifest.hasOwnProperty(k)) {
30280        delete manifest[k];
30281      }
30282    });
30283    if (manifest.segments) {
30284      manifest.segments.forEach(function (segment) {
30285        ['parts', 'preloadHints'].forEach(function (k) {
30286          if (segment.hasOwnProperty(k)) {
30287            delete segment[k];
30288          }
30289        });
30290      });
30291    }
30292  }
30293  if (!manifest.targetDuration) {
30294    let targetDuration = 10;
30295    if (manifest.segments && manifest.segments.length) {
30296      targetDuration = manifest.segments.reduce((acc, s) => Math.max(acc, s.duration), 0);
30297    }
30298    if (onwarn) {
30299      onwarn({
30300        message: `manifest has no targetDuration defaulting to ${targetDuration}`,
30301      });
30302    }
30303    manifest.targetDuration = targetDuration;
30304  }
30305  const parts = getLastParts(manifest);
30306  if (parts.length && !manifest.partTargetDuration) {
30307    const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
30308    if (onwarn) {
30309      onwarn({
30310        message: `manifest has no partTargetDuration defaulting to ${partTargetDuration}`,
30311      });
30312      log.error(
30313        '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.'
30314      );
30315    }
30316    manifest.partTargetDuration = partTargetDuration;
30317  }
30318  return manifest;
30319};
30320/**
30321 * Loops through all supported media groups in main and calls the provided
30322 * callback for each group
30323 *
30324 * @param {Object} main
30325 *        The parsed main manifest object
30326 * @param {Function} callback
30327 *        Callback to call for each media group
30328 */
30329
vendor: 1,222 bytes, lines 30330-30366
30330const forEachMediaGroup = (main, callback) => {
30331  if (!main.mediaGroups) {
30332    return;
30333  }
30334  ['AUDIO', 'SUBTITLES'].forEach((mediaType) => {
30335    if (!main.mediaGroups[mediaType]) {
30336      return;
30337    }
30338    for (const groupKey in main.mediaGroups[mediaType]) {
30339      for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
30340        const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
30341        callback(mediaProperties, mediaType, groupKey, labelKey);
30342      }
30343    }
30344  });
30345};
30346/**
30347 * Adds properties and attributes to the playlist to keep consistent functionality for
30348 * playlists throughout VHS.
30349 *
30350 * @param {Object} config
30351 *        Arguments object
30352 * @param {Object} config.playlist
30353 *        The media playlist
30354 * @param {string} [config.uri]
30355 *        The uri to the media playlist (if media playlist is not from within a main
30356 *        playlist)
30357 * @param {string} id
30358 *        ID to use for the playlist
30359 */
30360
30361const setupMediaPlaylist = ({ playlist, uri, id }) => {
30362  playlist.id = id;
30363  playlist.playlistErrors_ = 0;
30364  if (uri) {
30365    // For media playlists, m3u8-parser does not have access to a URI, as HLS media
30366    // playlists do not contain their own source URI, but one is needed for c
30366onsistency in
30367    // VHS.
30368    playlist.uri = uri;
30369  } // For HLS main playlists, even though certain attributes MUST be defined, the
30370  // stream may still be played without them.
30371  // For HLS media playlists, m3u8-parser does not attach an attributes object to the
30372  // manifest.
30373  //
30374  // To avoid undefined reference errors through the project, and make the code easier
30375  // to write/read, add an empty attributes object for these cases.
30376
30377  playlist.attributes = playlist.attributes || {};
30378};
30379/**
30380 * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
30381 * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
30382 * playlist references to the playlists array.
30383 *
30384 * @param {Object} main
30385 *        The main playlist
30386 */
30387
30388const setupMediaPlaylists = (main) => {
30389  let i = main.playlists.length;
30390  while (i--) {
30391    const playlist = main.playlists[i];
30392    setupMediaPlaylist({
30393      playlist,
30394      id: createPlaylistID(i, playlist.uri),
30395    });
30396    playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
30397    main.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
30398
30399    main.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
30400    // the stream can be played without it. Although an attributes property may have been
30401    // added to the playlist to prevent undefined references, issue a warning to fix the
30402    // manifest.
30403
30404    if (!playlist.attributes.BANDWIDTH) {
30405      log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
30406    }
30407  }
30408};
30409/**
30410 * Adds resolvedUri properties to each media group.
30411 *
30412 * @param {Object} main
30413 *        The main playlist
30414 */
30415
30416const resolveMediaGroupUris = (main) => {
30417  forEachMediaGroup(main, (properties) => {
30418    if (properties.uri) {
30419      properties.resolvedUri = resolveUrl(main.uri, properties.uri);
30420    }
30421  });
30422};
30423/**
30424 * Creates a main playlist wrapper to insert a sole media playlist into.
30425 *
30426 * @param {Object} media
30427 *        Media playlist
30428 * @param {string} uri
30429 *        The media URI
30430 *
30431 * @return {Object}
30432 *         main playlist
30433 */
30434
30435const mainForMedia = (media, uri) => {
30436  const id = createPlaylistID(0, uri);
30437  const main = {
30438    mediaGroups: {
30439      AUDIO: {},
30440      VIDEO: {},
30441      'CLOSED-CAPTIONS': {},
30442      SUBTITLES: {},
30443    },
30444    uri: window__default['default'].location.href,
30445    resolvedUri: window__default['default'].location.href,
30446    playlists: [
30447      {
30448        uri,
30449        id,
30450        resolvedUri: uri,
30451        // m3u8-parser does not attach an attributes property to media playlists so make
30452        // sure that the property is attached to avoid undefined reference errors
30453        attributes: {},
30454      },
30455    ],
30456  }; // set up ID reference
30457
30458  main.playlists[id] = main.playlists[0]; // URI reference added for backwards compatibility
30459
30460  main.playlists[uri] = main.playlists[0];
30461  return main;
30462};
30463/**
30464 * Does an in-place update of the main manifest to add updated playlist URI references
30465 * as well as other properties needed by VHS that aren't included by the parser.
30466 *
30467 * @param {Object} main
30468 *        main manifest object
30469 * @param {string} uri
30470 *        The source URI
30471 * @param {function} createGroupID
30472 *        A function to determine how to create the groupID for mediaGroups
30473 */
30474
30475const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
30476  main.uri = uri;
30477  for (let i = 0; i < main.playlists.length; i++) {
30478    if (!main.playlists[i].uri) {
30479      // Set up phony URIs for the playlists since playlists are referenced by their URIs
30480      // throughout VHS, but some formats (e.g., DASH) don't have external URIs
30481      // TODO: consider adding dummy URIs in mpd-parser
30482      const phonyUri = `placeholder-uri-${i}`;
30483      main.playlists[i].uri = phonyUri;
30484    }
30485  }
30486  const audioOnlyMain = isAudioOnly(main);
30487  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
30488    // add a playlist array under properties
30489    if (!properties.playlists || !properties.playlists.length) {
30490      // If the manifest is audio only and this media group does not have a uri, check
30491      // if the media group is located in the main list of playlists. If it is, don't add
30492      // placeholder properties as it shouldn't be considered an alternate audio track.
30493      if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
30494        for (let i = 0; i < main.playlists.length; i++) {
30495          const p = main.playlists[i];
30496          if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
30497            return;
30498          }
30499        }
30500      }
30501      properties.playlists = [_extends__default['default']({}, properties)];
30502    }
vendor: 10,983 bytes, lines 30503-30802
30503    properties.playlists.forEach(function (p, i) {
30504      const groupId = createGroupID(mediaType, groupKey, labelKey, p);
30505      const id = createPlaylistID(i, groupId);
30506      if (p.uri) {
30507        p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
30508      } else {
30509        // DEPRECATED, this has been added to prevent a breaking change.
30510        // previously we only ever had a single media group playlist, so
30511        // we mark the first playlist uri without prepending the index as we used to
30512        // ideally we would do all of the playlists the same way.
30513        p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
30514        // the placeholder again
30515
30516        p.resolvedUri = p.uri;
30517      }
30518      p.id = p.id || id; // add an empty attributes object, all playlists are
30519      // expected to have this.
30520
30521      p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
30522
30523      main.playlists[p.id] = p;
30524      main.playlists[p.uri] = p;
30525    });
30526  });
30527  setupMediaPlaylists(main);
30528  resolveMediaGroupUris(main);
30529};
30530class DateRangesStorage {
30531  constructor() {
30532    this.offset_ = null;
30533    this.pendingDateRanges_ = new Map();
30534    this.processedDateRanges_ = new Map();
30535  }
30536  setOffset(segments = []) {
30537    // already set
30538    if (this.offset_ !== null) {
30539      return;
30540    } // no segment to process
30541
30542    if (!segments.length) {
30543      return;
30544    }
30545    const [firstSegment] = segments; // no program date time
30546
30547    if (firstSegment.programDateTime === undefined) {
30548      return;
30549    } // Set offset as ProgramDateTime for the very first segment of the very first playlist load:
30550
30551    this.offset_ = firstSegment.programDateTime / 1000;
30552  }
30553  setPendingDateRanges(dateRanges = []) {
30554    if (!dateRanges.length) {
30555      return;
30556    }
30557    const [dateRange] = dateRanges;
30558    const startTime = dateRange.startDate.getTime();
30559    this.trimProcessedDateRanges_(startTime);
30560    this.pendingDateRanges_ = dateRanges.reduce((map, pendingDateRange) => {
30561      map.set(pendingDateRange.id, pendingDateRange);
30562      return map;
30563    }, new Map());
30564  }
30565  processDateRange(dateRange) {
30566    this.pendingDateRanges_.delete(dateRange.id);
30567    this.processedDateRanges_.set(dateRange.id, dateRange);
30568  }
30569  getDateRangesToProcess() {
30570    if (this.offset_ === null) {
30571      return [];
30572    }
30573    const dateRangeClasses = {};
30574    const dateRangesToProcess = [];
30575    this.pendingDateRanges_.forEach((dateRange, id) => {
30576      if (this.processedDateRanges_.has(id)) {
30577        return;
30578      }
30579      dateRange.startTime = dateRange.startDate.getTime() / 1000 - this.offset_;
30580      dateRange.processDateRange = () => this.processDateRange(dateRange);
30581      dateRangesToProcess.push(dateRange);
30582      if (!dateRange.class) {
30583        return;
30584      }
30585      if (dateRangeClasses[dateRange.class]) {
30586        const length = dateRangeClasses[dateRange.class].push(dateRange);
30587        dateRange.classListIndex = length - 1;
30588      } else {
30589        dateRangeClasses[dateRange.class] = [dateRange];
30590        dateRange.classListIndex = 0;
30591      }
30592    });
30593    for (const dateRange of dateRangesToProcess) {
30594      const classList = dateRangeClasses[dateRange.class] || [];
30595      if (dateRange.endDate) {
30596        dateRange.endTime = dateRange.endDate.getTime() / 1000 - this.offset_;
30597      } else if (dateRange.endOnNext && classList[dateRange.classListIndex + 1]) {
30598        dateRange.endTime = classList[dateRange.classListIndex + 1].startTime;
30599      } else if (dateRange.duration) {
30600        dateRange.endTime = dateRange.startTime + dateRange.duration;
30601      } else if (dateRange.plannedDuration) {
30602        dateRange.endTime = dateRange.startTime + dateRange.plannedDuration;
30603      } else {
30604        dateRange.endTime = dateRange.startTime;
30605      }
30606    }
30607    return dateRangesToProcess;
30608  }
30609  trimProcessedDateRanges_(startTime) {
30610    const copy = new Map(this.processedDateRanges_);
30611    copy.forEach((dateRange, id) => {
30612      if (dateRange.startDate.getTime() < startTime) {
30613        this.processedDateRanges_.delete(id);
30614      }
30615    });
30616  }
30617}
30618const QUOTA_EXCEEDED_ERR = 22;
30619const getStreamingNetworkErrorMetadata = ({ requestType, request, error, parseFailure }) => {
30620  const isBadStatus = request.status < 200 || request.status > 299;
30621  const isFailure = request.status >= 400 && request.status <= 499;
30622  const errorMetadata = {
30623    uri: request.uri,
30624    requestType,
30625  };
30626  const isBadStatusOrParseFailure = (isBadStatus && !isFailure) || parseFailure;
30627  if (error && isFailure) {
30628    // copy original error and add to the metadata.
30629    errorMetadata.error = _extends__default['default']({}, error);
30630    errorMetadata.errorType = videojs.Error.NetworkRequestFailed;
30631  } else if (request.aborted) {
30632    errorMetadata.errorType = videojs.Error.NetworkRequestAborted;
30633  } else if (request.timedout) {
30634    errorMetadata.erroType = videojs.Error.NetworkRequestTimeout;
30635  } else if (isBadStatusOrParseFailure) {
30636    const errorType = parseFailure
30637      ? videojs.Error.NetworkBodyParserFailed
30638      : videojs.Error.NetworkBadStatus;
30639    errorMetadata.errorType = errorType;
30640    errorMetadata.status = request.status;
30641    errorMetadata.headers = request.headers;
30642  }
30643  return errorMetadata;
30644};
30645const { EventTarget: EventTarget$1 } = videojs;
30646const addLLHLSQueryDirectives = (uri, media) => {
30647  if (media.endList || !media.serverControl) {
30648    return uri;
30649  }
30650  const parameters = {};
30651  if (media.serverControl.canBlockReload) {
30652    const { preloadSegment } = media; // next msn is a zero based value, length is not.
30653
30654    let nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
30655    // that we are going to request a part of that preload segment.
30656    // the logic below is used to determine that.
30657
30658    if (preloadSegment) {
30659      const parts = preloadSegment.parts || []; // _HLS_part is a zero based index
30660
30661      const nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
30662      // length of parts, then we know we had part preload hints
30663      // and we need to add the _HLS_part= query
30664
30665      if (nextPart > -1 && nextPart !== parts.length - 1) {
30666        // add existing parts to our preload hints
30667        // eslint-disable-next-line
30668        parameters._HLS_part = nextPart;
30669      } // this if statement makes sure that we request the msn
30670      // of the preload segment if:
30671      // 1. the preload segment had parts (and was not yet a full segment)
30672      //    but was added to our segments array
30673      // 2. the preload segment had preload hints for parts that are not in
30674      //    the manifest yet.
30675      // in all other cases we want the segment after the preload segment
30676      // which will be given by using media.segments.length because it is 1 based
30677      // rather than 0 based.
30678
30679      if (nextPart > -1 || parts.length) {
30680        nextMSN--;
30681      }
30682    } // add _HLS_msn= in front of any _HLS_part query
30683    // eslint-disable-next-line
30684
30685    parameters._HLS_msn = nextMSN;
30686  }
30687  if (media.serverControl && media.serverControl.canSkipUntil) {
30688    // add _HLS_skip= infront of all other queries.
30689    // eslint-disable-next-line
30690    parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
30691  }
30692  if (Object.keys(parameters).length) {
30693    const parsedUri = new window__default['default'].URL(uri);
30694    ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
30695      if (!parameters.hasOwnProperty(name)) {
30696        return;
30697      }
30698      parsedUri.searchParams.set(name, parameters[name]);
30699    });
30700    uri = parsedUri.toString();
30701  }
30702  return uri;
30703};
30704/**
30705 * Returns a new segment object with properties and
30706 * the parts array merged.
30707 *
30708 * @param {Object} a the old segment
30709 * @param {Object} b the new segment
30710 *
30711 * @return {Object} the merged segment
30712 */
30713
30714const updateSegment = (a, b) => {
30715  if (!a) {
30716    return b;
30717  }
30718  const result = merge(a, b); // if only the old segment has preload hints
30719  // and the new one does not, remove preload hints.
30720
30721  if (a.preloadHints && !b.preloadHints) {
30722    delete result.preloadHints;
30723  } // if only the old segment has parts
30724  // then the parts are no longer valid
30725
30726  if (a.parts && !b.parts) {
30727    delete result.parts; // if both segments have parts
30728    // copy part propeties from the old segment
30729    // to the new one.
30730  } else if (a.parts && b.parts) {
30731    for (let i = 0; i < b.parts.length; i++) {
30732      if (a.parts && a.parts[i]) {
30733        result.parts[i] = merge(a.parts[i], b.parts[i]);
30734      }
30735    }
30736  } // set skipped to false for segments that have
30737  // have had information merged from the old segment.
30738
30739  if (!a.skipped && b.skipped) {
30740    result.skipped = false;
30741  } // set preload to false for segments that have
30742  // had information added in the new segment.
30743
30744  if (a.preload && !b.preload) {
30745    result.preload = false;
30746  }
30747  return result;
30748};
30749/**
30750 * Returns a new array of segments that is the result of merging
30751 * properties from an older list of segments onto an updated
30752 * list. No properties on the updated playlist will be ovewritten.
30753 *
30754 * @param {Array} original the outdated list of segments
30755 * @param {Array} update the updated list of segments
30756 * @param {number=} offset the index of the first update
30757 * segment in the original segment list. For non-live playlists,
30758 * this should always be zero and does not need to be
30759 * specified. For live playlists, it should be the difference
30760 * between the media sequence numbers in the original and updated
30761 * playlists.
30762 * @return {Array} a list of merged segment objects
30763 */
30764
30765const updateSegments = (original, update, offset) => {
30766  const oldSegments = original.slice();
30767  const newSegments = update.slice();
30768  offset = offset || 0;
30769  const result = [];
30770  let currentMap;
30771  for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
30772    const oldSegment = oldSegments[newIndex + offset];
30773    const newSegment = newSegments[newIndex];
30774    if (oldSegment) {
30775      currentMap = oldSegment.map || currentMap;
30776      result.push(updateSegment(oldSegment, newSegment));
30777    } else {
30778      // carry over map to new segment if it is missing
30779      if (currentMap && !newSegment.map) {
30780        newSegment.map = currentMap;
30781      }
30782      result.push(newSegment);
30783    }
30784  }
30785  return result;
30786};
30787const resolveSegmentUris = (segment, baseUri) => {
30788  // preloadSegment will not have a uri at all
30789  // as the segment isn't actually in the manifest yet, only parts
30790  if (!segment.resolvedUri && segment.uri) {
30791    segment.resolvedUri = resolveUrl(baseUri, segment.uri);
30792  }
30793  if (segment.key && !segment.key.resolvedUri) {
30794    segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
30795  }
30796  if (segment.map && !segment.map.resolvedUri) {
30797    segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
30798  }
30799  if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
30800    segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
30801  }
30802  if (segment.parts && segment.parts.length) {
vendor: 5,582 bytes, lines 30803-30956
30803    segment.parts.forEach((p) => {
30804      if (p.resolvedUri) {
30805        return;
30806      }
30807      p.resolvedUri = resolveUrl(baseUri, p.uri);
30808    });
30809  }
30810  if (segment.preloadHints && segment.preloadHints.length) {
30811    segment.preloadHints.forEach((p) => {
30812      if (p.resolvedUri) {
30813        return;
30814      }
30815      p.resolvedUri = resolveUrl(baseUri, p.uri);
30816    });
30817  }
30818};
30819const getAllSegments = function (media) {
30820  const segments = media.segments || [];
30821  const preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
30822  // a usable segment, only include a preloadSegment that has
30823  // parts.
30824
30825  if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
30826    // if preloadHints has a MAP that means that the
30827    // init segment is going to change. We cannot use any of the parts
30828    // from this preload segment.
30829    if (preloadSegment.preloadHints) {
30830      for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
30831        if (preloadSegment.preloadHints[i].type === 'MAP') {
30832          return segments;
30833        }
30834      }
30835    } // set the duration for our preload segment to target duration.
30836
30837    preloadSegment.duration = media.targetDuration;
30838    preloadSegment.preload = true;
30839    segments.push(preloadSegment);
30840  }
30841  return segments;
30842}; // consider the playlist unchanged if the playlist object is the same or
30843// the number of segments is equal, the media sequence number is unchanged,
30844// and this playlist hasn't become the end of the playlist
30845
30846const isPlaylistUnchanged = (a, b) =>
30847  a === b ||
30848  (a.segments &&
30849    b.segments &&
30850    a.segments.length === b.segments.length &&
30851    a.endList === b.endList &&
30852    a.mediaSequence === b.mediaSequence &&
30853    a.preloadSegment === b.preloadSegment);
30854/**
30855 * Returns a new main playlist that is the result of merging an
30856 * updated media playlist into the original version. If the
30857 * updated media playlist does not match any of the playlist
30858 * entries in the original main playlist, null is returned.
30859 *
30860 * @param {Object} main a parsed main M3U8 object
30861 * @param {Object} media a parsed media M3U8 object
30862 * @return {Object} a new object that represents the original
30863 * main playlist with the updated media playlist merged in, or
30864 * null if the merge produced no change.
30865 */
30866
30867const updateMain$1 = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
30868  const result = merge(main, {});
30869  const oldMedia = result.playlists[newMedia.id];
30870  if (!oldMedia) {
30871    return null;
30872  }
30873  if (unchangedCheck(oldMedia, newMedia)) {
30874    return null;
30875  }
30876  newMedia.segments = getAllSegments(newMedia);
30877  const mergedPlaylist = merge(oldMedia, newMedia); // always use the new media's preload segment
30878
30879  if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
30880    delete mergedPlaylist.preloadSegment;
30881  } // if the update could overlap existing segment information, merge the two segment lists
30882
30883  if (oldMedia.segments) {
30884    if (newMedia.skip) {
30885      newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
30886      // old properties into the new segments
30887
30888      for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
30889        newMedia.segments.unshift({
30890          skipped: true,
30891        });
30892      }
30893    }
30894    mergedPlaylist.segments = updateSegments(
30895      oldMedia.segments,
30896      newMedia.segments,
30897      newMedia.mediaSequence - oldMedia.mediaSequence
30898    );
30899  } // resolve any segment URIs to prevent us from having to do it later
30900
30901  mergedPlaylist.segments.forEach((segment) => {
30902    resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
30903  }); // TODO Right now in the playlists array there are two references to each playlist, one
30904  // that is referenced by index, and one by URI. The index reference may no longer be
30905  // necessary.
30906
30907  for (let i = 0; i < result.playlists.length; i++) {
30908    if (result.playlists[i].id === newMedia.id) {
30909      result.playlists[i] = mergedPlaylist;
30910    }
30911  }
30912  result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
30913
30914  result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
30915
30916  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
30917    if (!properties.playlists) {
30918      return;
30919    }
30920    for (let i = 0; i < properties.playlists.length; i++) {
30921      if (newMedia.id === properties.playlists[i].id) {
30922        properties.playlists[i] = mergedPlaylist;
30923      }
30924    }
30925  });
30926  return result;
30927};
30928/**
30929 * Calculates the time to wait before refreshing a live playlist
30930 *
30931 * @param {Object} media
30932 *        The current media
30933 * @param {boolean} update
30934 *        True if there were any updates from the last refresh, false otherwise
30935 * @return {number}
30936 *         The time in ms to wait before refreshing the live playlist
30937 */
30938
30939const refreshDelay = (media, update) => {
30940  const segments = media.segments || [];
30941  const lastSegment = segments[segments.length - 1];
30942  const lastPart =
30943    lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
30944  const lastDuration = (lastPart && lastPart.duration) || (lastSegment && lastSegment.duration);
30945  if (update && lastDuration) {
30946    return lastDuration * 1000;
30947  } // if the playlist is unchanged since the last reload or last segment duration
30948  // cannot be determined, try again after half the target duration
30949
30950  return (media.partTargetDuration || media.targetDuration || 10) * 500;
30951};
30952const playlistMetadataPayload = (playlists, type, isLive) => {
30953  if (!playlists) {
30954    return;
30955  }
30956  const renditions = [];
30957  playlists.forEach((playlist) => {
30958    // we need attributes to populate rendition data.
30959    if (!playlist.attributes) {
30960      return;
30961    }
30962    const { BANDWIDTH, RESOLUTION, CODECS } = playlist.attributes;
30963    renditions.push({
30964      id: playlist.id,
30965      bandwidth: BANDWIDTH,
30966      resolution: RESOLUTION,
30967      codecs: CODECS,
30968    });
30969  });
30970  return {
30971    type,
30972    isLive,
30973    renditions,
30974  };
30975};
30976/**
30977 * Load a playlist from a remote location
30978 *
30979 * @class PlaylistLoader
30980 * @extends Stream
30981 * @param {string|Object} src url or object of manifest
30982 * @param {boolean} withCredentials the withCredentials xhr option
30983 * @class
30984 */
30985
30986class PlaylistLoader extends EventTarget$1 {
30987  constructor(src, vhs, options = {}) {
30988    super();
30989    if (!src) {
30990      throw new Error('A non-empty playlist URL or object is required');
30991    }
30992    this.logger_ = logger('PlaylistLoader');
30993    const { withCredentials = false } = options;
30994    this.src = src;
30995    this.vhs_ = vhs;
30996    this.withCredentials = withCredentials;
30997    this.addDateRangesToTextTrack_ = options.addDateRangesToTextTrack;
30998    const vhsOptions = vhs.options_;
30999    this.customTagParsers = (vhsOptions && vhsOptions.customTagParsers) || [];
31000    this.customTagMappers = (vhsOptions && vhsOptions.customTagMappers) || [];
31001    this.llhls = vhsOptions && vhsOptions.llhls;
31002    this.dateRangesStorage_ = new DateRangesStorage(); // initialize the loader state
31003
31004    this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
31005
31006    this.handleMediaupdatetimeout_ = this.handleMediaupdatetimeout_.bind(this);
31007    this.on('mediaupdatetimeout', this.handleMediaupdatetimeout_);
31008    this.on('loadedplaylist', this.handleLoadedPlaylist_.bind(this));
31009  }
31010  handleLoadedPlaylist_() {
31011    const mediaPlaylist = this.media();
31012    if (!mediaPlaylist) {
31013      return;
31014    }
31015    this.dateRangesStorage_.setOffset(mediaPlaylist.segments);
31016    this.dateRangesStorage_.setPendingDateRanges(mediaPlaylist.dateRanges);
31017    const availableDateRanges = this.dateRangesStorage_.getDateRangesToProcess();
31018    if (!availableDateRanges.length || !this.addDateRangesToTextTrack_) {
31019      return;
31020    }
31021    this.addDateRangesToTextTrack_(availableDateRanges);
31022  }
31023  handleMediaupdatetimeout_() {
31024    if (this.state !== 'HAVE_METADATA') {
31025      // only refresh the media playlist if no other activity is going on
31026      return;
31027    }
31028    const media = this.media();
31029    let uri = resolveUrl(this.main.uri, media.uri);
31030    if (this.llhls) {
31031      uri = addLLHLSQueryDirectives(uri, media);
31032    }
31033    this.state = 'HAVE_CURRENT_METADATA';
31034    this.request = this.vhs_.xhr(
31035      {
31036        uri,
31037        withCredentials: this.withCredentials,
31038        requestType: 'hls-playlist',
31039      },
31040      (error, req) => {
31041        // disposed
31042        if (!this.request) {
31043          return;
31044        }
31045        if (error) {
31046          return this.playlistRequestError(this.request, this.media(), 'HAVE_METADATA');
31047        }
31048        this.haveMetadata({
31049          playlistString: this.request.responseText,
31050          url: this.media().uri,
31051          id: this.media().id,
31052        });
31053      }
31054    );
31055  }
31056  playlistRequestError(xhr, playlist, startingState) {
31057    const { uri, id } = playlist; // any in-flight request is now finished
31058
31059    this.request = null;
31060    if (startingState) {
31061      this.state = startingState;
31062    }
31063    this.error = {
31064      playlist: this.main.playlists[id],
31065      status: xhr.status,
31066      message: `HLS playlist request error at URL: ${uri}.`,
31067      responseText: xhr.responseText,
31068      code: xhr.status >= 500 ? 4 : 2,
31069      metadata: getStreamingNetworkErrorMetadata({
31070        requestType: xhr.requestType,
31071        request: xhr,
31072        error: xhr.error,
31073      }),
31074    };
31075    this.trigger('error');
31076  }
31077  parseManifest_({ url, manifestString }) {
31078    try {
31079      return parseManifest({
31080        onwarn: ({ message }) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
31081        oninfo: ({ message }) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
31082        manifestString,
31083        customTagParsers: this.customTagParsers,
31084        customTagMappers: this.customTagMappers,
31085        llhls: this.llhls,
31086      });
31087    } catch (error) {
31088      this.error = error;
31089      this.error.metadata = {
31090        errorType: videojs.Error.StreamingHlsPlaylistParserError,
31091        error,
31092      };
31093    }
31094  }
31095  /**
31096   * Update the playlist loader's state in response to a new or updated playlist.
31097   *
31098   * @param {string} [playlistString]
vendor: 7,753 bytes, lines 31099-31336
31099   *        Playlist string (if playlistObject is not provided)
31100   * @param {Object} [playlistObject]
31101   *        Playlist object (if playlistString is not provided)
31102   * @param {string} url
31103   *        URL of playlist
31104   * @param {string} id
31105   *        ID to use for playlist
31106   */
31107
31108  haveMetadata({ playlistString, playlistObject, url, id }) {
31109    // any in-flight request is now finished
31110    this.request = null;
31111    this.state = 'HAVE_METADATA';
31112    const metadata = {
31113      playlistInfo: {
31114        type: 'media',
31115        uri: url,
31116      },
31117    };
31118    this.trigger({
31119      type: 'playlistparsestart',
31120      metadata,
31121    });
31122    const playlist =
31123      playlistObject ||
31124      this.parseManifest_({
31125        url,
31126        manifestString: playlistString,
31127      });
31128    playlist.lastRequest = Date.now();
31129    setupMediaPlaylist({
31130      playlist,
31131      uri: url,
31132      id,
31133    }); // merge this playlist into the main manifest
31134
31135    const update = updateMain$1(this.main, playlist);
31136    this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
31137    this.pendingMedia_ = null;
31138    if (update) {
31139      this.main = update;
31140      this.media_ = this.main.playlists[id];
31141    } else {
31142      this.trigger('playlistunchanged');
31143    }
31144    this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
31145    metadata.parsedPlaylist = playlistMetadataPayload(
31146      this.main.playlists,
31147      metadata.playlistInfo.type,
31148      !this.media_.endList
31149    );
31150    this.trigger({
31151      type: 'playlistparsecomplete',
31152      metadata,
31153    });
31154    this.trigger('loadedplaylist');
31155  }
31156  /**
31157   * Abort any outstanding work and clean up.
31158   */
31159
31160  dispose() {
31161    this.trigger('dispose');
31162    this.stopRequest();
31163    window__default['default'].clearTimeout(this.mediaUpdateTimeout);
31164    window__default['default'].clearTimeout(this.finalRenditionTimeout);
31165    this.dateRangesStorage_ = new DateRangesStorage();
31166    this.off();
31167  }
31168  stopRequest() {
31169    if (this.request) {
31170      const oldRequest = this.request;
31171      this.request = null;
31172      oldRequest.onreadystatechange = null;
31173      oldRequest.abort();
31174    }
31175  }
31176  /**
31177   * When called without any arguments, returns the currently
31178   * active media playlist. When called with a single argument,
31179   * triggers the playlist loader to asynchronously switch to the
31180   * specified media playlist. Calling this method while the
31181   * loader is in the HAVE_NOTHING causes an error to be emitted
31182   * but otherwise has no effect.
31183   *
31184   * @param {Object=} playlist the parsed media playlist
31185   * object to switch to
31186   * @param {boolean=} shouldDelay whether we should delay the request by half target duration
31187   *
31188   * @return {Playlist} the current loaded media
31189   */
31190
31191  media(playlist, shouldDelay) {
31192    // getter
31193    if (!playlist) {
31194      return this.media_;
31195    } // setter
31196
31197    if (this.state === 'HAVE_NOTHING') {
31198      throw new Error('Cannot switch media playlist from ' + this.state);
31199    } // find the playlist object if the target playlist has been
31200    // specified by URI
31201
31202    if (typeof playlist === 'string') {
31203      if (!this.main.playlists[playlist]) {
31204        throw new Error('Unknown playlist URI: ' + playlist);
31205      }
31206      playlist = this.main.playlists[playlist];
31207    }
31208    window__default['default'].clearTimeout(this.finalRenditionTimeout);
31209    if (shouldDelay) {
31210      const delay =
31211        ((playlist.partTargetDuration || playlist.targetDuration) / 2) * 1000 || 5 * 1000;
31212      this.finalRenditionTimeout = window__default['default'].setTimeout(
31213        this.media.bind(this, playlist, false),
31214        delay
31215      );
31216      return;
31217    }
31218    const startingState = this.state;
31219    const mediaChange = !this.media_ || playlist.id !== this.media_.id;
31220    const mainPlaylistRef = this.main.playlists[playlist.id]; // switch to fully loaded playlists immediately
31221
31222    if (
31223      (mainPlaylistRef && mainPlaylistRef.endList) ||
31224      // handle the case of a playlist object (e.g., if using vhs-json with a resolved
31225      // media playlist or, for the case of demuxed audio, a resolved audio media group)
31226      (playlist.endList && playlist.segments.length)
31227    ) {
31228      // abort outstanding playlist requests
31229      if (this.request) {
31230        this.request.onreadystatechange = null;
31231        this.request.abort();
31232        this.request = null;
31233      }
31234      this.state = 'HAVE_METADATA';
31235      this.media_ = playlist; // trigger media change if the active media has been updated
31236
31237      if (mediaChange) {
31238        this.trigger('mediachanging');
31239        if (startingState === 'HAVE_MAIN_MANIFEST') {
31240          // The initial playlist was a main manifest, and the first media selected was
31241          // also provided (in the form of a resolved playlist object) as part of the
31242          // source object (rather than just a URL). Therefore, since the media playlist
31243          // doesn't need to be requested, loadedmetadata won't trigger as part of the
31244          // normal flow, and needs an explicit trigger here.
31245          this.trigger('loadedmetadata');
31246        } else {
31247          this.trigger('mediachange');
31248        }
31249      }
31250      return;
31251    } // We update/set the timeout here so that live playlists
31252    // that are not a media change will "start" the loader as expected.
31253    // We expect that this function will start the media update timeout
31254    // cycle again. This also prevents a playlist switch failure from
31255    // causing us to stall during live.
31256
31257    this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
31258
31259    if (!mediaChange) {
31260      return;
31261    }
31262    this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
31263
31264    if (this.request) {
31265      if (playlist.resolvedUri === this.request.url) {
31266        // requesting to switch to the same playlist multiple times
31267        // has no effect after the first
31268        return;
31269      }
31270      this.request.onreadystatechange = null;
31271      this.request.abort();
31272      this.request = null;
31273    } // request the new playlist
31274
31275    if (this.media_) {
31276      this.trigger('mediachanging');
31277    }
31278    this.pendingMedia_ = playlist;
31279    const metadata = {
31280      playlistInfo: {
31281        type: 'media',
31282        uri: playlist.uri,
31283      },
31284    };
31285    this.trigger({
31286      type: 'playlistrequeststart',
31287      metadata,
31288    });
31289    this.request = this.vhs_.xhr(
31290      {
31291        uri: playlist.resolvedUri,
31292        withCredentials: this.withCredentials,
31293        requestType: 'hls-playlist',
31294      },
31295      (error, req) => {
31296        // disposed
31297        if (!this.request) {
31298          return;
31299        }
31300        playlist.lastRequest = Date.now();
31301        playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
31302        if (error) {
31303          return this.playlistRequestError(this.request, playlist, startingState);
31304        }
31305        this.trigger({
31306          type: 'playlistrequestcomplete',
31307          metadata,
31308        });
31309        this.haveMetadata({
31310          playlistString: req.responseText,
31311          url: playlist.uri,
31312          id: playlist.id,
31313        }); // fire loadedmetadata the first time a media playlist is loaded
31314
31315        if (startingState === 'HAVE_MAIN_MANIFEST') {
31316          this.trigger('loadedmetadata');
31317        } else {
31318          this.trigger('mediachange');
31319        }
31320      }
31321    );
31322  }
31323  /**
31324   * pause loading of the playlist
31325   */
31326
31327  pause() {
31328    if (this.mediaUpdateTimeout) {
31329      window__default['default'].clearTimeout(this.mediaUpdateTimeout);
31330      this.mediaUpdateTimeout = null;
31331    }
31332    this.stopRequest();
31333    if (this.state === 'HAVE_NOTHING') {
31334      // If we pause the loader before any data has been retrieved, its as if we never
31335      // started, so reset to an unstarted state.
31336      this.started = false;
31337    } // Need to restore state now that no activity is happening
31338
31339    if (this.state === 'SWITCHING_MEDIA') {
31340      // if the loader was in the process of switching media, it should either return to
31341      // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
31342      // playlist yet. This is determined by the existence of loader.media_
31343      if (this.media_) {
31344        this.state = 'HAVE_METADATA';
31345      } else {
31346        this.state = 'HAVE_MAIN_MANIFEST';
31347      }
31348    } else if (this.state === 'HAVE_CURRENT_METADATA') {
31349      this.state = 'HAVE_METADATA';
31350    }
31351  }
31352  /**
31353   * start loading of the playlist
31354   */
31355
31356  load(shouldDelay) {
31357    if (this.mediaUpdateTimeout) {
31358      window__default['default'].clearTimeout(this.mediaUpdateTimeout);
31359      this.mediaUpdateTimeout = null;
31360    }
31361    const media = this.media();
31362    if (shouldDelay) {
31363      const delay = media
31364        ? ((media.partTargetDuration || media.targetDuration) / 2) * 1000
31365        : 5 * 1000;
31366      this.mediaUpdateTimeout = window__default['default'].setTimeout(() => {
31367        this.mediaUpdateTimeout = null;
31368        this.load();
31369      }, delay);
31370      return;
31371    }
31372    if (!this.started) {
31373      this.start();
31374      return;
31375    }
31376    if (media && !media.endList) {
31377      this.trigger('mediaupdatetimeout');
31378    } else {
31379      this.trigger('loadedplaylist');
31380    }
31381  }
31382  updateMediaUpdateTimeout_(delay) {
31383    if (this.mediaUpdateTimeout) {
31384      window__default['default'].clearTimeout(this.mediaUpdateTimeout);
31385      this.mediaUpdateTimeout = null;
31386    } // we only have use mediaupdatetimeout for live playlists.
31387
31388    if (!this.media() || this.media().endList) {
31389      return;
31390    }
31391    this.mediaUpdateTimeout = window__default['default'].setTimeout(() => {
31392      this.mediaUpdateTimeout = null;
31393      this.trigger('mediaupdatetimeout');
31394      this.updateMediaUpdateTimeout_(delay);
31395    }, delay);
31396  }
31397  /**
31398   * start loading of the playlist
31399   */
31400
31401  start() {
31402    this.started = true;
31403    if (typeof this.src === 'object') {
31404      // in the case of an entirely constructed manifest object (meaning there's no actual
31405      // manifest on a server), default the uri to the page's href
31406      if (!this.src.uri) {
31407        this.src.uri = window__default['default'].location.href;
31408      } // resolvedUri is added on internally after the initial request. Since there's no
31409      // request for pre-resolved manifests, add on resolvedUri here.
31410
31411      this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
31412      // request can be skipped (since the top level of the manifest, at a minimum, is
31413      // already available as a parsed manifest object). However, if the manifest object
31414      // represents a main playlist, some media playlists may need to be resolved before
31415      // the starting segment list is available. Therefore, go directly to setup of the
31416      // initial playlist, and let the normal flow continue from there.
31417      //
31418      // Note that the call to setup is asynchronous, as other sections of VHS may assume
31419      // that the first request is asynchronous.
31420
31421      setTimeout(() => {
31422        this.setupInitialPlaylist(this.src);
31423      }, 0);
31424      return;
31425    }
31426    const metadata = {
31427      playlistInfo: {
31428        type: 'multivariant',
31429        uri: this.src,
31430      },
31431    };
31432    this.trigger({
31433      type: 'playlistrequeststart',
31434      metadata,
31435    }); // request the specified URL
31436
31437    this.request = this.vhs_.xhr(
31438      {
31439        uri: this.src,
31440        withCredentials: this.withCredentials,
31441        requestType: 'hls-playlist',
31442      },
31443      (error, req) => {
31444        // disposed
31445        if (!this.request) {
31446          return;
31447        } // clear the loader's request reference
31448
31449        this.request = null;
31450        if (error) {
31451          this.error = {
31452            status: req.status,
31453            message: `HLS playlist request error at URL: ${this.src}.`,
31454            responseText: req.responseText,
31455            // MEDIA_ERR_NETWORK
31456            code: 2,
31457            metadata: getStreamingNetworkErrorMetadata({
31458              requestType: req.requestType,
31459              request: req,
31460              error,
31461            }),
31462          };
31463          if (this.state === 'HAVE_NOTHING') {
31464            this.started = false;
31465          }
31466          return this.trigger('error');
31467        }
31468        this.trigger({
31469          type: 'playlistrequestcomplete',
31470          metadata,
31471        });
31472        this.src = resolveManifestRedirect(this.src, req);
31473        this.trigger({
31474          type: 'playlistparsestart',
31475          metadata,
31476        });
31477        const manifest = this.parseManifest_({
31478          manifestString: req.responseText,
31479          url: this.src,
31480        }); // we haven't loaded any variant playlists here so we default to false for isLive.
31481
31482        metadata.parsedPlaylist = playlistMetadataPayload(
31483          manifest.playlists,
31484          metadata.playlistInfo.type,
31485          false
31486        );
31487        this.trigger({
31488          type: 'playlistparsecomplete',
31489          metadata,
31490        });
31491        this.setupInitialPlaylist(manifest);
31492      }
31493    );
31494  }
31495  srcUri() {
31496    return typeof this.src === 'string' ? this.src : this.src.uri;
31497  }
31498  /**
31499   * Given a manifest object that's either a main or media playlist, trigger the proper
31500   * events and set the state of the playlist loader.
31501   *
31502   * If the manifest object represents a main playlist, `loadedplaylist` will be
31503   * triggered to allow listeners to select a playlist. If none is selected, the loader
31504   * will default to the first one in the playlists array.
31505   *
31506   * If the manifest object represents a media playlist, `loadedplaylist` will be
31507   * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
31508   *
31509   * In the case of a media playlist, a main playlist object wrapper with one playlist
31510   * will be created so that all logic can handle playlists in the same fashion (as an
31511   * assumed manifest object schema).
31512   *
31513   * @param {Object} manifest
31514   *        The parsed manifest object
31515   */
31516
31517  setupInitialPlaylist(manifest) {
31518    this.state = 'HAVE_MAIN_MANIFEST';
31519    if (manifest.playlists) {
31520      this.main = manifest;
31521      addPropertiesToMain(this.main, this.srcUri()); // If the initial main playlist has playlists wtih segments already resolved,
31522      // then resolve URIs in advance, as they are usually done after a playlist request,
31523      // which may not happen if the playlist is resolved.
31524
vendor: 6,978 bytes, lines 31525-31692
31525      manifest.playlists.forEach((playlist) => {
31526        playlist.segments = getAllSegments(playlist);
31527        playlist.segments.forEach((segment) => {
31528          resolveSegmentUris(segment, playlist.resolvedUri);
31529        });
31530      });
31531      this.trigger('loadedplaylist');
31532      if (!this.request) {
31533        // no media playlist was specifically selected so start
31534        // from the first listed one
31535        this.media(this.main.playlists[0]);
31536      }
31537      return;
31538    } // In order to support media playlists passed in as vhs-json, the case where the uri
31539    // is not provided as part of the manifest should be considered, and an appropriate
31540    // default used.
31541
31542    const uri = this.srcUri() || window__default['default'].location.href;
31543    this.main = mainForMedia(manifest, uri);
31544    this.haveMetadata({
31545      playlistObject: manifest,
31546      url: uri,
31547      id: this.main.playlists[0].id,
31548    });
31549    this.trigger('loadedmetadata');
31550  }
31551  /**
31552   * Updates or deletes a preexisting pathway clone.
31553   * Ensures that all playlists related to the old pathway clone are
31554   * either updated or deleted.
31555   *
31556   * @param {Object} clone On update, the pathway clone object for the newly updated pathway clone.
31557   *        On delete, the old pathway clone object to be deleted.
31558   * @param {boolean} isUpdate True if the pathway is to be updated,
31559   *        false if it is meant to be deleted.
31560   */
31561
31562  updateOrDeleteClone(clone, isUpdate) {
31563    const main = this.main;
31564    const pathway = clone.ID;
31565    let i = main.playlists.length; // Iterate backwards through the playlist so we can remove playlists if necessary.
31566
31567    while (i--) {
31568      const p = main.playlists[i];
31569      if (p.attributes['PATHWAY-ID'] === pathway) {
31570        const oldPlaylistUri = p.resolvedUri;
31571        const oldPlaylistId = p.id; // update the indexed playlist and add new playlists by ID and URI
31572
31573        if (isUpdate) {
31574          const newPlaylistUri = this.createCloneURI_(p.resolvedUri, clone);
31575          const newPlaylistId = createPlaylistID(pathway, newPlaylistUri);
31576          const attributes = this.createCloneAttributes_(pathway, p.attributes);
31577          const updatedPlaylist = this.createClonePlaylist_(p, newPlaylistId, clone, attributes);
31578          main.playlists[i] = updatedPlaylist;
31579          main.playlists[newPlaylistId] = updatedPlaylist;
31580          main.playlists[newPlaylistUri] = updatedPlaylist;
31581        } else {
31582          // Remove the indexed playlist.
31583          main.playlists.splice(i, 1);
31584        } // Remove playlists by the old ID and URI.
31585
31586        delete main.playlists[oldPlaylistId];
31587        delete main.playlists[oldPlaylistUri];
31588      }
31589    }
31590    this.updateOrDeleteCloneMedia(clone, isUpdate);
31591  }
31592  /**
31593   * Updates or deletes media data based on the pathway clone object.
31594   * Due to the complexity of the media groups and playlists, in all cases
31595   * we remove all of the old media groups and playlists.
31596   * On updates, we then create new media groups and playlists based on the
31597   * new pathway clone object.
31598   *
31599   * @param {Object} clone The pathway clone object for the newly updated pathway clone.
31600   * @param {boolean} isUpdate True if the pathway is to be updated,
31601   *        false if it is meant to be deleted.
31602   */
31603
31604  updateOrDeleteCloneMedia(clone, isUpdate) {
31605    const main = this.main;
31606    const id = clone.ID;
31607    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
31608      if (!main.mediaGroups[mediaType] || !main.mediaGroups[mediaType][id]) {
31609        return;
31610      }
31611      for (const groupKey in main.mediaGroups[mediaType]) {
31612        // Remove all media playlists for the media group for this pathway clone.
31613        if (groupKey === id) {
31614          for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
31615            const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
31616            oldMedia.playlists.forEach((p, i) => {
31617              const oldMediaPlaylist = main.playlists[p.id];
31618              const oldPlaylistId = oldMediaPlaylist.id;
31619              const oldPlaylistUri = oldMediaPlaylist.resolvedUri;
31620              delete main.playlists[oldPlaylistId];
31621              delete main.playlists[oldPlaylistUri];
31622            });
31623          } // Delete the old media group.
31624
31625          delete main.mediaGroups[mediaType][groupKey];
31626        }
31627      }
31628    }); // Create the new media groups and playlists if there is an update.
31629
31630    if (isUpdate) {
31631      this.createClonedMediaGroups_(clone);
31632    }
31633  }
31634  /**
31635   * Given a pathway clone object, clones all necessary playlists.
31636   *
31637   * @param {Object} clone The pathway clone object.
31638   * @param {Object} basePlaylist The original playlist to clone from.
31639   */
31640
31641  addClonePathway(clone, basePlaylist = {}) {
31642    const main = this.main;
31643    const index = main.playlists.length;
31644    const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
31645    const playlistId = createPlaylistID(clone.ID, uri);
31646    const attributes = this.createCloneAttributes_(clone.ID, basePlaylist.attributes);
31647    const playlist = this.createClonePlaylist_(basePlaylist, playlistId, clone, attributes);
31648    main.playlists[index] = playlist; // add playlist by ID and URI
31649
31650    main.playlists[playlistId] = playlist;
31651    main.playlists[uri] = playlist;
31652    this.createClonedMediaGroups_(clone);
31653  }
31654  /**
31655   * Given a pathway clone object we create clones of all media.
31656   * In this function, all necessary information and updated playlists
31657   * are added to the `mediaGroup` object.
31658   * Playlists are also added to the `playlists` array so the media groups
31659   * will be properly linked.
31660   *
31661   * @param {Object} clone The pathway clone object.
31662   */
31663
31664  createClonedMediaGroups_(clone) {
31665    const id = clone.ID;
31666    const baseID = clone['BASE-ID'];
31667    const main = this.main;
31668    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
31669      // If the media type doesn't exist, or there is already a clone, skip
31670      // to the next media type.
31671      if (!main.mediaGroups[mediaType] || main.mediaGroups[mediaType][id]) {
31672        return;
31673      }
31674      for (const groupKey in main.mediaGroups[mediaType]) {
31675        if (groupKey === baseID) {
31676          // Create the group.
31677          main.mediaGroups[mediaType][id] = {};
31678        } else {
31679          // There is no need to iterate over label keys in this case.
31680          continue;
31681        }
31682        for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
31683          const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
31684          main.mediaGroups[mediaType][id][labelKey] = _extends__default['default']({}, oldMedia);
31685          const newMedia = main.mediaGroups[mediaType][id][labelKey]; // update URIs on the media
31686
31687          const newUri = this.createCloneURI_(oldMedia.resolvedUri, clone);
31688          newMedia.resolvedUri = newUri;
31689          newMedia.uri = newUri; // Reset playlists in the new media group.
31690
31691          newMedia.playlists = []; // Create new playlists in the newly cloned media group.
31692
31693          oldMedia.playlists.forEach((p, i) => {
31694            const oldMediaPlaylist = main.playlists[p.id];
31695            const group = groupID(mediaType, id, labelKey);
31696            const newPlaylistID = createPlaylistID(id, group); // Check to see if it already exists
31697
31698            if (oldMediaPlaylist && !main.playlists[newPlaylistID]) {
31699              const newMediaPlaylist = this.createClonePlaylist_(
31700                oldMediaPlaylist,
31701                newPlaylistID,
31702                clone
31703              );
31704              const newPlaylistUri = newMediaPlaylist.resolvedUri;
31705              main.playlists[newPlaylistID] = newMediaPlaylist;
31706              main.playlists[newPlaylistUri] = newMediaPlaylist;
31707            }
31708            newMedia.playlists[i] = this.createClonePlaylist_(p, newPlaylistID, clone);
31709          });
31710        }
31711      }
31712    });
31713  }
31714  /**
31715   * Using the original playlist to be cloned, and the pathway clone object
31716   * information, we create a new playlist.
31717   *
31718   * @param {Object} basePlaylist  The original playlist to be cloned from.
31719   * @param {string} id The desired id of the newly cloned playlist.
31720   * @param {Object} clone The pathway clone object.
31721   * @param {Object} attributes An optional object to populate the `attributes` property in the playlist.
31722   *
31723   * @return {Object} The combined cloned playlist.
31724   */
31725
31726  createClonePlaylist_(basePlaylist, id, clone, attributes) {
31727    const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
31728    const newProps = {
31729      resolvedUri: uri,
31730      uri,
31731      id,
31732    }; // Remove all segments from previous playlist in the clone.
31733
31734    if (basePlaylist.segments) {
31735      newProps.segments = [];
31736    }
31737    if (attributes) {
31738      newProps.attributes = attributes;
31739    }
31740    return merge(basePlaylist, newProps);
31741  }
31742  /**
31743   * Generates an updated URI for a cloned pathway based on the original
31744   * pathway's URI and the paramaters from the pathway clone object in the
31745   * content steering server response.
31746   *
31747   * @param {string} baseUri URI to be updated in the cloned pathway.
31748   * @param {Object} clone The pathway clone object.
31749   *
31750   * @return {string} The updated URI for the cloned pathway.
31751   */
31752
31753  createCloneURI_(baseURI, clone) {
31754    const uri = new URL(baseURI);
31755    uri.hostname = clone['URI-REPLACEMENT'].HOST;
31756    const params = clone['URI-REPLACEMENT'].PARAMS; // Add params to the cloned URL.
31757
31758    for (const key of Object.keys(params)) {
31759      uri.searchParams.set(key, params[key]);
31760    }
31761    return uri.href;
31762  }
31763  /**
31764   * Helper function to create the attributes needed for the new clone.
31765   * This mainly adds the necessary media attributes.
31766   *
31767   * @param {string} id The pathway clone object ID.
31768   * @param {Object} oldAttributes The old attributes to compare to.
31769   * @return {Object} The new attributes to add to the playlist.
31770   */
31771
31772  createCloneAttributes_(id, oldAttributes) {
31773    const attributes = {
31774      ['PATHWAY-ID']: id,
31775    };
31776    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
31777      if (oldAttributes[mediaType]) {
31778        attributes[mediaType] = id;
31779      }
31780    });
31781    return attributes;
31782  }
31783  /**
31784   * Returns the key ID set from a playlist
31785   *
31786   * @param {playlist} playlist to fetch the key ID set from.
31787   * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
31788   */
31789
31790  getKeyIdSet(playlist) {
31791    if (playlist.contentProtection) {
31792      const keyIds = new Set();
31793      for (const keysystem in playlist.contentProtection) {
31794        const keyId = playlist.contentProtection[keysystem].attributes.keyId;
31795        if (keyId) {
31796          keyIds.add(keyId.toLowerCase());
31797        }
31798      }
31799      return keyIds;
31800    }
31801  }
31802}
31803
31804/**
31805 * @file xhr.js
31806 */
31807
31808const callbackWrapper = function (request, error, response, callback) {
31809  const reqResponse =
31810    request.responseType === 'arraybuffer' ? request.response : request.responseText;
31811  if (!error && reqResponse) {
31812    request.responseTime = Date.now();
31813    request.roundTripTime = request.responseTime - request.requestTime;
31814    request.bytesReceived = reqResponse.byteLength || reqResponse.length;
31815    if (!request.bandwidth) {
31816      request.bandwidth = Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
31817    }
31818  }
31819  if (response.headers) {
31820    request.responseHeaders = response.headers;
31821  } // videojs.xhr now uses a specific code on the error
vendor: 4,835 bytes, lines 31822-31946
31822  // object to signal that a request has timed out instead
31823  // of setting a boolean on the request object
31824
31825  if (error && error.code === 'ETIMEDOUT') {
31826    request.timedout = true;
31827  } // videojs.xhr no longer considers status codes outside of 200 and 0
31828  // (for file uris) to be errors, but the old XHR did, so emulate that
31829  // behavior. Status 206 may be used in response to byterange requests.
31830
31831  if (
31832    !error &&
31833    !request.aborted &&
31834    response.statusCode !== 200 &&
31835    response.statusCode !== 206 &&
31836    response.statusCode !== 0
31837  ) {
31838    error = new Error(
31839      'XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))
31840    );
31841  }
31842  callback(error, request);
31843};
31844/**
31845 * Iterates over the request hooks Set and calls them in order
31846 *
31847 * @param {Set} hooks the hook Set to iterate over
31848 * @param {Object} options the request options to pass to the xhr wrapper
31849 * @return the callback hook function return value, the modified or new options Object.
31850 */
31851
31852const callAllRequestHooks = (requestSet, options) => {
31853  if (!requestSet || !requestSet.size) {
31854    return;
31855  }
31856  let newOptions = options;
31857  requestSet.forEach((requestCallback) => {
31858    newOptions = requestCallback(newOptions);
31859  });
31860  return newOptions;
31861};
31862/**
31863 * Iterates over the response hooks Set and calls them in order.
31864 *
31865 * @param {Set} hooks the hook Set to iterate over
31866 * @param {Object} request the xhr request object
31867 * @param {Object} error the xhr error object
31868 * @param {Object} response the xhr response object
31869 */
31870
31871const callAllResponseHooks = (responseSet, request, error, response) => {
31872  if (!responseSet || !responseSet.size) {
31873    return;
31874  }
31875  responseSet.forEach((responseCallback) => {
31876    responseCallback(request, error, response);
31877  });
31878};
31879const xhrFactory = function () {
31880  const xhr = function XhrFunction(options, callback) {
31881    // Add a default timeout
31882    options = merge(
31883      {
31884        timeout: 45e3,
31885      },
31886      options
31887    ); // Allow an optional user-specified function to modify the option
31888    // object before we construct the xhr request
31889    // TODO: Remove beforeRequest in the next major release.
31890
31891    const beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest; // onRequest and onResponse hooks as a Set, at either the player or global level.
31892    // TODO: new Set added here for beforeRequest alias. Remove this when beforeRequest is removed.
31893
31894    const _requestCallbackSet =
31895      XhrFunction._requestCallbackSet || videojs.Vhs.xhr._requestCallbackSet || new Set();
31896    const _responseCallbackSet =
31897      XhrFunction._responseCallbackSet || videojs.Vhs.xhr._responseCallbackSet;
31898    if (beforeRequest && typeof beforeRequest === 'function') {
31899      videojs.log.warn('beforeRequest is deprecated, use onRequest instead.');
31900      _requestCallbackSet.add(beforeRequest);
31901    } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
31902    // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
31903
31904    const xhrMethod = videojs.Vhs.xhr.original === true ? videojs.xhr : videojs.Vhs.xhr; // call all registered onRequest hooks, assign new options.
31905
31906    const beforeRequestOptions = callAllRequestHooks(_requestCallbackSet, options); // Remove the beforeRequest function from the hooks set so stale beforeRequest functions are not called.
31907
31908    _requestCallbackSet.delete(beforeRequest); // xhrMethod will call XMLHttpRequest.open and XMLHttpRequest.send
31909
31910    const request = xhrMethod(beforeRequestOptions || options, function (error, response) {
31911      // call all registered onResponse hooks
31912      callAllResponseHooks(_responseCallbackSet, request, error, response);
31913      return callbackWrapper(request, error, response, callback);
31914    });
31915    const originalAbort = request.abort;
31916    request.abort = function () {
31917      request.aborted = true;
31918      return originalAbort.apply(request, arguments);
31919    };
31920    request.uri = options.uri;
31921    request.requestType = options.requestType;
31922    request.requestTime = Date.now();
31923    return request;
31924  };
31925  xhr.original = true;
31926  return xhr;
31927};
31928/**
31929 * Turns segment byterange into a string suitable for use in
31930 * HTTP Range requests
31931 *
31932 * @param {Object} byterange - an object with two values defining the start and end
31933 *                             of a byte-range
31934 */
31935
31936const byterangeStr = function (byterange) {
31937  // `byterangeEnd` is one less than `offset + length` because the HTTP range
31938  // header uses inclusive ranges
31939  let byterangeEnd;
31940  const byterangeStart = byterange.offset;
31941  if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
31942    byterangeEnd =
31943      window__default['default'].BigInt(byterange.offset) +
31944      window__default['default'].BigInt(byterange.length) -
31945      window__default['default'].BigInt(1);
31946  } else {
vendor: 4,101 bytes, lines 31947-32104
31947    byterangeEnd = byterange.offset + byterange.length - 1;
31948  }
31949  return 'bytes=' + byterangeStart + '-' + byterangeEnd;
31950};
31951/**
31952 * Defines headers for use in the xhr request for a particular segment.
31953 *
31954 * @param {Object} segment - a simplified copy of the segmentInfo object
31955 *                           from SegmentLoader
31956 */
31957
31958const segmentXhrHeaders = function (segment) {
31959  const headers = {};
31960  if (segment.byterange) {
31961    headers.Range = byterangeStr(segment.byterange);
31962  }
31963  return headers;
31964};
31965
31966/**
31967 * @file bin-utils.js
31968 */
31969
31970/**
31971 * convert a TimeRange to text
31972 *
31973 * @param {TimeRange} range the timerange to use for conversion
31974 * @param {number} i the iterator on the range to convert
31975 * @return {string} the range in string format
31976 */
31977
31978const textRange = function (range, i) {
31979  return range.start(i) + '-' + range.end(i);
31980};
31981/**
31982 * format a number as hex string
31983 *
31984 * @param {number} e The number
31985 * @param {number} i the iterator
31986 * @return {string} the hex formatted number as a string
31987 */
31988
31989const formatHexString = function (e, i) {
31990  const value = e.toString(16);
31991  return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
31992};
31993const formatAsciiString = function (e) {
31994  if (e >= 0x20 && e < 0x7e) {
31995    return String.fromCharCode(e);
31996  }
31997  return '.';
31998};
31999/**
32000 * Creates an object for sending to a web worker modifying properties that are TypedArrays
32001 * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
32002 *
32003 * @param {Object} message
32004 *        Object of properties and values to send to the web worker
32005 * @return {Object}
32006 *         Modified message with TypedArray values expanded
32007 * @function createTransferableMessage
32008 */
32009
32010const createTransferableMessage = function (message) {
32011  const transferable = {};
32012  Object.keys(message).forEach((key) => {
32013    const value = message[key];
32014    if (byteHelpers.isArrayBufferView(value)) {
32015      transferable[key] = {
32016        bytes: value.buffer,
32017        byteOffset: value.byteOffset,
32018        byteLength: value.byteLength,
32019      };
32020    } else {
32021      transferable[key] = value;
32022    }
32023  });
32024  return transferable;
32025};
32026/**
32027 * Returns a unique string identifier for a media initialization
32028 * segment.
32029 *
32030 * @param {Object} initSegment
32031 *        the init segment object.
32032 *
32033 * @return {string} the generated init segment id
32034 */
32035
32036const initSegmentId = function (initSegment) {
32037  const byterange = initSegment.byterange || {
32038    length: Infinity,
32039    offset: 0,
32040  };
32041  return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
32042};
32043/**
32044 * Returns a unique string identifier for a media segment key.
32045 *
32046 * @param {Object} key the encryption key
32047 * @return {string} the unique id for the media segment key.
32048 */
32049
32050const segmentKeyId = function (key) {
32051  return key.resolvedUri;
32052};
32053/**
32054 * utils to help dump binary data to the console
32055 *
32056 * @param {Array|TypedArray} data
32057 *        data to dump to a string
32058 *
32059 * @return {string} the data as a hex string.
32060 */
32061
32062const hexDump = (data) => {
32063  const bytes = Array.prototype.slice.call(data);
32064  const step = 16;
32065  let result = '';
32066  let hex;
32067  let ascii;
32068  for (let j = 0; j < bytes.length / step; j++) {
32069    hex = bytes
32070      .slice(j * step, j * step + step)
32071      .map(formatHexString)
32072      .join('');
32073    ascii = bytes
32074      .slice(j * step, j * step + step)
32075      .map(formatAsciiString)
32076      .join('');
32077    result += hex + ' ' + ascii + '\n';
32078  }
32079  return result;
32080};
32081const tagDump = ({ bytes }) => hexDump(bytes);
32082const textRanges = (ranges) => {
32083  let result = '';
32084  let i;
32085  for (i = 0; i < ranges.length; i++) {
32086    result += textRange(ranges, i) + ' ';
32087  }
32088  return result;
32089};
32090var utils = /*#__PURE__*/ Object.freeze({
32091  __proto__: null,
32092  createTransferableMessage: createTransferableMessage,
32093  initSegmentId: initSegmentId,
32094  segmentKeyId: segmentKeyId,
32095  hexDump: hexDump,
32096  tagDump: tagDump,
32097  textRanges: textRanges,
32098});
32099
32100// TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
32101// 25% was arbitrarily chosen, and may need to be refined over time.
32102
32103const SEGMENT_END_FUDGE_PERCENT = 0.25;
32104/**
32105 * Converts a player time (any time that can be gotten/set from player.currentTime(),
32106 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
32107 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
32108 *
32109 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
32110 * point" (a point where we have a mapping from program time to player time, with player
32111 * time being the post transmux start of the segment).
32112 *
32113 * For more details, see [this doc](../../docs/program-time-from-player-time.md).
32114 *
32115 * @param {number} playerTime the player time
32116 * @param {Object} segment the segment which contains the player time
32117 * @return {Date} program time
32118 */
32119
32120const playerTimeToProgramTime = (playerTime, segment) => {
32121  if (!segment.dateTimeObject) {
32122    // Can't convert without an "anchor point" for the program time (i.e., a time that can
32123    // be used to map the start of a segment with a real world time).
32124    return null;
32125  }
32126  const transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
32127  const transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
32128
32129  const startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
32130  const offsetFromSegmentStart = playerTime - startOfSegment;
32131  return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
32132};
32133const originalSegmentVideoDuration = (videoTimingInfo) => {
32134  return (
32135    videoTimingInfo.transmuxedPresentationEnd -
32136    videoTimingInfo.transmuxedPresentationStart -
32137    videoTimingInfo.transmuxerPrependedSeconds
32138  );
32139};
32140/**
32141 * Finds a segment that contains the time requested given as an ISO-8601 string. The
32142 * returned segment might be an estimate or an accurate match.
32143 *
32144 * @param {string} programTime The ISO-8601 programTime to find a match for
32145 * @param {Object} playlist A playlist object to search within
32146 */
32147
32148const findSegmentForProgramTime = (programTime, playlist) => {
32149  // Assumptions:
32150  //  - verifyProgramDateTimeTags has already been run
32151  //  - live streams have been started
32152  let dateTimeObject;
32153  try {
32154    dateTimeObject = new Date(programTime);
32155  } catch (e) {
32156    return null;
32157  }
32158  if (!playlist || !playlist.segments || playlist.segments.length === 0) {
32159    return null;
32160  }
32161  let segment = playlist.segments[0];
32162  if (dateTimeObject < new Date(segment.dateTimeObject)) {
32163    // Requested time is before stream start.
32164    return null;
32165  }
32166  for (let i = 0; i < playlist.segments.length - 1; i++) {
32167    segment = playlist.segments[i];
32168    const nextSegmentStart = new Date(playlist.segments[i + 1].dateTimeObject);
32169    if (dateTimeObject < nextSegmentStart) {
32170      break;
32171    }
32172  }
32173  const lastSegment = playlist.segments[playlist.segments.length - 1];
32174  const lastSegmentStart = lastSegment.dateTimeObject;
32175  const lastSegmentDuration = lastSegment.videoTimingInfo
32176    ? originalSegmentVideoDuration(lastSegment.videoTimingInfo)
32177    : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
32178  const lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
32179  if (dateTimeObject > lastSegmentEnd) {
32180    // Beyond the end of the stream, or our best guess of the end of the stream.
32181    return null;
32182  }
32183  if (dateTimeObject > new Date(lastSegmentStart)) {
32184    segment = lastSegment;
32185  }
32186  return {
32187    segment,
32188    estimatedStart: segment.videoTimingInfo
32189      ? segment.videoTimingInfo.transmuxedPresentationStart
32190      : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
32191    // Although, given that all segments have accurate date time objects, the segment
32192    // selected should be accurate, unless the video has been transmuxed at some point
32193    // (determined by the presence of the videoTimingInfo object), the segment's "player
32194    // time" (the start time in the player) can't be considered accurate.
32195    type: segment.videoTimingInfo ? 'accurate' : 'estimate',
32196  };
32197};
32198/**
32199 * Finds a segment that contains the given player time(in seconds).
32200 *
32201 * @param {number} time The player time to find a match for
32202 * @param {Object} playlist A playlist object to search within
32203 */
32204
32205const findSegmentForPlayerTime = (time, playlist) => {
32206  // Assumptions:
32207  // - there will always be a segment.duration
32208  // - we can start from zero
32209  // - segments are in time order
32210  if (!playlist || !playlist.segments || playlist.segments.length === 0) {
32211    return null;
32212  }
32213  let segmentEnd = 0;
32214  let segment;
32215  for (let i = 0; i < playlist.segments.length; i++) {
32216    segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
32217    // should contain the most accurate values we have for the segment's player times.
32218    //
32219    // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
32220    // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
32221    // calculate an end value.
32222
32223    segmentEnd = segment.videoTimingInfo
32224      ? segment.videoTimingInfo.transmuxedPresentationEnd
32225      : segmentEnd + segment.duration;
32226    if (time <= segmentEnd) {
32227      break;
32228    }
32229  }
32230  const lastSegment = playlist.segments[playlist.segments.length - 1];
32231  if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
32232    // The time requested is beyond the stream end.
32233    return null;
32234  }
32235  if (time > segmentEnd) {
32236    // The time is within or beyond the last segment.
32237    //
32238    // Check to see if the time is beyond a reasonable guess of the end of the stream.
32239    if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
32240      // Technically, because the duration value is only an estimate, the time may still
32241      // exist in the last segment, however, there isn't enough information to make even
32242      // a reasonable estimate.
32243      return null;
32244    }
32245    segment = lastSegment;
32246  }
32247  return {
32248    segment,
32249    estimatedStart: segment.videoTimingInfo
32250      ? segment.videoTimingInfo.transmuxedPresentationStart
32251      : segmentEnd - segment.duration,
32252    // Because videoTimingInfo is only set after transmux, it is the only way to get
32253    // accurate timing values.
32254    type: segment.videoTimingInfo ? 'accurate' : 'estimate',
32255  };
32256};
32257/**
32258 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
32259 * If the offset returned is positive, the programTime occurs after the
32260 * comparisonTimestamp.
32261 * If the offset is negative, the programTime occurs before the comparisonTimestamp.
32262 *
32263 * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
32264 * @param {string} programTime The programTime as an ISO-8601 string
32265 * @return {number} offset
32266 */
32267
32268const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
32269  let segmentDateTime;
32270  let programDateTime;
32271  try {
32272    segmentDateTime = new Date(comparisonTimeStamp);
32273    programDateTime = new Date(programTime);
32274  } catch (e) {
32275    // TODO handle error
32276  }
32277  const segmentTimeEpoch = segmentDateTime.getTime();
32278  const programTimeEpoch = programDateTime.getTime();
32279  return (programTimeEpoch - segmentTimeEpoch) / 1000;
32280};
32281/**
32282 * Checks that all segments in this playlist have programDateTime tags.
32283 *
32284 * @param {Object} playlist A playlist object
32285 */
32286
32287const verifyProgramDateTimeTags = (playlist) => {
32288  if (!playlist.segments || playlist.segments.length === 0) {
32289    return false;
32290  }
32291  for (let i = 0; i < playlist.segments.length; i++) {
32292    const segment = playlist.segments[i];
32293    if (!segment.dateTimeObject) {
32294      return false;
32295    }
32296  }
32297  return true;
32298};
32299/**
32300 * Returns the programTime of the media given a playlist and a playerTime.
32301 * The playlist must have programDateTime tags for a programDateTime tag to be returned.
32302 * If the segments containing the time requested have not been buffered yet, an estimate
32303 * may be returned to the callback.
32304 *
32305 * @param {Object} args
32306 * @param {Object} args.playlist A playlist object to search within
32307 * @param {number} time A playerTime in seconds
32308 * @param {Function} callback(err, programTime)
32309 * @return {string} err.message A detailed error message
32310 * @return {Object} programTime
32311 * @return {number} programTime.mediaSeconds The streamTime in seconds
32312 * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
32313 */
32314
32315const getProgramTime = ({ playlist, time = undefined, callback }) => {
32316  if (!callback) {
32317    throw new Error('getProgramTime: callback must be provided');
32318  }
32319  if (!playlist || time === undefined) {
32320    return callback({
32321      message: 'getProgramTime: playlist and time must be provided',
32322    });
32323  }
32324  const matchedSegment = findSegmentForPlayerTime(time, playlist);
32325  if (!matchedSegment) {
32326    return callback({
32327      message: 'valid programTime was not found',
32328    });
32329  }
32330  if (matchedSegment.type === 'estimate') {
32331    return callback({
32332      message:
32333        'Accurate programTime could not be determined.' +
32334        ' Please seek to e.seekTime and try again',
32335      seekTime: matchedSegment.estimatedStart,
32336    });
32337  }
32338  const programTimeObject = {
32339    mediaSeconds: time,
32340  };
32341  const programTime = playerTimeToProgramTime(time, matchedSegment.segment);
32342  if (programTime) {
32343    programTimeObject.programDateTime = programTime.toISOString();
32344  }
32345  return callback(null, programTimeObject);
32346};
32347/**
32348 * Seeks in the player to a time that matches the given programTime ISO-8601 string.
32349 *
32350 * @param {Object} args
32351 * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
32352 * @param {Object} args.playlist A playlist to look within
32353 * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
32354 * @param {Function} args.seekTo A method to perform a seek
32355 * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
32356 * @param {Object} args.tech The tech to seek on
32357 * @param {Function} args.callback(err, newTime) A callback to return the new time to
32358 * @return {string} err.message A detailed error message
32359 * @return {number} newTime The exact time that was seeked to in seconds
32360 */
32361
32362const seekToProgramTime = ({
32363  programTime,
32364  playlist,
32365  retryCount = 2,
32366  seekTo,
32367  pauseAfterSeek = true,
32368  tech,
32369  callback,
32370}) => {
32371  if (!callback) {
32372    throw new Error('seekToProgramTime: callback must be provided');
32373  }
32374  if (typeof programTime === 'undefined' || !playlist || !seekTo) {
32375    return callback({
32376      message: 'seekToProgramTime: programTime, seekTo and playlist must be provided',
32377    });
32378  }
32379  if (!playlist.endList && !tech.hasStarted_) {
32380    return callback({
32381      message: 'player must be playing a live stream to start buffering',
32382    });
32383  }
32384  if (!verifyProgramDateTimeTags(playlist)) {
32385    return callback({
32386      message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri,
32387    });
32388  }
32389  const matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
32390
32391  if (!matchedSegment) {
32392    return callback({
32393      message: `${programTime} was not found in the stream`,
32394    });
32395  }
32396  const segment = matchedSegment.segment;
32397  const mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
32398  if (matchedSegment.type === 'estimate') {
32399    // we've run out of retries
32400    if (retryCount === 0) {
32401      return callback({
32402        message: `${programTime} is not buffered yet. Try again`,
32403      });
32404    }
32405    seekTo(matchedSegment.estimatedStart + mediaOffset);
32406    tech.one('seeked', () => {
32407      seekToProgramTime({
32408        programTime,
32409        playlist,
32410        retryCount: retryCount - 1,
32411        seekTo,
32412        pauseAfterSeek,
32413        tech,
32414        callback,
32415      });
32416    });
32417    return;
32418  } // Since the segment.start value is determined from the buffered end or ending time
32419  // of the prior segment, the seekToTime doesn't need to account for any transmuxer
32420  // modifications.
32421
32422  const seekToTime = segment.start + mediaOffset;
32423  const seekedCallback = () => {
32424    return callback(null, tech.currentTime());
32425  }; // listen for seeked event
32426
32427  tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
32428
32429  if (pauseAfterSeek) {
32430    tech.pause();
32431  }
32432  seekTo(seekToTime);
32433};
32434
32435// which will only happen if the request is complete.
32436
32437const callbackOnCompleted = (request, cb) => {
32438  if (request.readyState === 4) {
32439    return cb();
32440  }
32441  return;
32442};
32443const containerRequest = (uri, xhr, cb, requestType) => {
32444  let bytes = [];
32445  let id3Offset;
32446  let finished = false;
32447  const endRequestAndCallback = function (err, req, type, _bytes) {
32448    req.abort();
32449    finished = true;
32450    return cb(err, req, type, _bytes);
32451  };
32452  const progressListener = function (error, request) {
32453    if (finished) {
32454      return;
32455    }
32456    if (error) {
32457      error.metadata = getStreamingNetworkErrorMetadata({
32458        requestType,
32459        request,
32460        error,
32461      });
32462      return endRequestAndCallback(error, request, '', bytes);
32463    } // grap the new part of content that was just downloaded
32464
32465    const newPart = request.responseText.substring(
32466      (bytes && bytes.byteLength) || 0,
32467      request.responseText.length
32468    ); // add that onto bytes
32469
32470    bytes = byteHelpers.concatTypedArrays(bytes, byteHelpers.stringToBytes(newPart, true));
32471    id3Offset = id3Offset || id3Helpers.getId3Offset(bytes); // we need at least 10 bytes to determine a type
32472    // or we need at least two bytes after an id3Offset
32473
32474    if (bytes.length < 10 || (id3Offset && bytes.length < id3Offset + 2)) {
32475      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
32476    }
32477    const type = containers.detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
32478    // to see the second sync byte, wait until we have enough data
32479    // before declaring it ts
32480
32481    if (type === 'ts' && bytes.length < 188) {
32482      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
32483    } // this may be an unsynced ts segment
32484    // wait for 376 bytes before detecting no container
32485
32486    if (!type && bytes.length < 376) {
32487      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
32488    }
32489    return endRequestAndCallback(null, request, type, bytes);
32490  };
32491  const options = {
32492    uri,
32493    beforeSend(request) {
32494      // this forces the browser to pass the bytes to us unprocessed
32495      request.overrideMimeType('text/plain; charset=x-user-defined');
32496      request.addEventListener('progress', function ({ total, loaded }) {
32497        return callbackWrapper(
32498          request,
32499          null,
32500          {
32501            statusCode: request.status,
32502          },
32503          progressListener
32504        );
32505      });
32506    },
32507  };
32508  const request = xhr(options, function (error, response) {
32509    return callbackWrapper(request, error, response, progressListener);
32510  });
32511  return request;
32512};
32513const { EventTarget } = videojs;
32514const dashPlaylistUnchanged = function (a, b) {
32515  if (!isPlaylistUnchanged(a, b)) {
32516    return false;
32517  } // for dash the above check will often return true in scenarios where
32518  // the playlist actually has changed because mediaSequence isn't a
32519  // dash thing, and we often set it to 1. So if the playlists have the same amount
32520  // of segments we return true.
32521  // So for dash we need to make sure that the underlying segments are different.
32522  // if sidx changed then the playlists are different.
32523
32524  if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
32525    return false;
32526  } else if ((!a.sidx && b.sidx) || (a.sidx && !b.sidx)) {
32527    return false;
32528  } // one or the other does not have segments
32529  // there was a change.
32530
32531  if ((a.segments && !b.segments) || (!a.segments && b.segments)) {
32532    return false;
32533  } // neither has segments nothing changed
32534
32535  if (!a.segments && !b.segments) {
32536    return true;
32537  } // check segments themselves
32538
32539  for (let i = 0; i < a.segments.length; i++) {
32540    const aSegment = a.segments[i];
32541    const bSegment = b.segments[i]; // if uris are different between segments there was a change
32542
32543    if (aSegment.uri !== bSegment.uri) {
32544      return false;
vendor: 19,516 bytes, lines 32545-33107
32545    } // neither segment has a byterange, there will be no byterange change.
32546
32547    if (!aSegment.byterange && !bSegment.byterange) {
32548      continue;
32549    }
32550    const aByterange = aSegment.byterange;
32551    const bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
32552
32553    if ((aByterange && !bByterange) || (!aByterange && bByterange)) {
32554      return false;
32555    } // if both segments have byterange with different offsets, there was a change.
32556
32557    if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
32558      return false;
32559    }
32560  } // if everything was the same with segments, this is the same playlist.
32561
32562  return true;
32563};
32564/**
32565 * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
32566 * ID in the parser. This allows for continuous playout across periods with the same representation IDs
32567 * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
32568 * periods without continuous playback this function may need modification as well as the parser.
32569 */
32570
32571const dashGroupId = (type, group, label, playlist) => {
32572  // If the manifest somehow does not have an ID (non-dash compliant), use the label.
32573  const playlistId = playlist.attributes.NAME || label;
32574  return `placeholder-uri-${type}-${group}-${playlistId}`;
32575};
32576/**
32577 * Parses the main XML string and updates playlist URI references.
32578 *
32579 * @param {Object} config
32580 *        Object of arguments
32581 * @param {string} config.mainXml
32582 *        The mpd XML
32583 * @param {string} config.srcUrl
32584 *        The mpd URL
32585 * @param {Date} config.clientOffset
32586 *         A time difference between server and client
32587 * @param {Object} config.sidxMapping
32588 *        SIDX mappings for moof/mdat URIs and byte ranges
32589 * @return {Object}
32590 *         The parsed mpd manifest object
32591 */
32592
32593const parseMainXml = ({ mainXml, srcUrl, clientOffset, sidxMapping, previousManifest }) => {
32594  const manifest = mpdParser.parse(mainXml, {
32595    manifestUri: srcUrl,
32596    clientOffset,
32597    sidxMapping,
32598    previousManifest,
32599  });
32600  addPropertiesToMain(manifest, srcUrl, dashGroupId);
32601  return manifest;
32602};
32603/**
32604 * Removes any mediaGroup labels that no longer exist in the newMain
32605 *
32606 * @param {Object} update
32607 *         The previous mpd object being updated
32608 * @param {Object} newMain
32609 *         The new mpd object
32610 */
32611
32612const removeOldMediaGroupLabels = (update, newMain) => {
32613  forEachMediaGroup(update, (properties, type, group, label) => {
32614    if (!newMain.mediaGroups[type][group] || !(label in newMain.mediaGroups[type][group])) {
32615      delete update.mediaGroups[type][group][label];
32616    }
32617  });
32618};
32619/**
32620 * Returns a new main manifest that is the result of merging an updated main manifest
32621 * into the original version.
32622 *
32623 * @param {Object} oldMain
32624 *        The old parsed mpd object
32625 * @param {Object} newMain
32626 *        The updated parsed mpd object
32627 * @return {Object}
32628 *         A new object representing the original main manifest with the updated media
32629 *         playlists merged in
32630 */
32631
32632const updateMain = (oldMain, newMain, sidxMapping) => {
32633  let noChanges = true;
32634  let update = merge(oldMain, {
32635    // These are top level properties that can be updated
32636    duration: newMain.duration,
32637    minimumUpdatePeriod: newMain.minimumUpdatePeriod,
32638    timelineStarts: newMain.timelineStarts,
32639  }); // First update the playlists in playlist list
32640
32641  for (let i = 0; i < newMain.playlists.length; i++) {
32642    const playlist = newMain.playlists[i];
32643    if (playlist.sidx) {
32644      const sidxKey = mpdParser.generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
32645
32646      if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
32647        mpdParser.addSidxSegmentsToPlaylist(
32648          playlist,
32649          sidxMapping[sidxKey].sidx,
32650          playlist.sidx.resolvedUri
32651        );
32652      }
32653    }
32654    const playlistUpdate = updateMain$1(update, playlist, dashPlaylistUnchanged);
32655    if (playlistUpdate) {
32656      update = playlistUpdate;
32657      noChanges = false;
32658    }
32659  } // Then update media group playlists
32660
32661  forEachMediaGroup(newMain, (properties, type, group, label) => {
32662    if (properties.playlists && properties.playlists.length) {
32663      const id = properties.playlists[0].id;
32664      const playlistUpdate = updateMain$1(update, properties.playlists[0], dashPlaylistUnchanged);
32665      if (playlistUpdate) {
32666        update = playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
32667
32668        if (!(label in update.mediaGroups[type][group])) {
32669          update.mediaGroups[type][group][label] = properties;
32670        } // update the playlist reference within media groups
32671
32672        update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
32673        noChanges = false;
32674      }
32675    }
32676  }); // remove mediaGroup labels and references that no longer exist in the newMain
32677
32678  removeOldMediaGroupLabels(update, newMain);
32679  if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
32680    noChanges = false;
32681  }
32682  if (noChanges) {
32683    return null;
32684  }
32685  return update;
32686}; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
32687// If the SIDXs have maps, the two maps should match,
32688// both `a` and `b` missing SIDXs is considered matching.
32689// If `a` or `b` but not both have a map, they aren't matching.
32690
32691const equivalentSidx = (a, b) => {
32692  const neitherMap = Boolean(!a.map && !b.map);
32693  const equivalentMap =
32694    neitherMap ||
32695    Boolean(
32696      a.map &&
32697      b.map &&
32698      a.map.byterange.offset === b.map.byterange.offset &&
32699      a.map.byterange.length === b.map.byterange.length
32700    );
32701  return (
32702    equivalentMap &&
32703    a.uri === b.uri &&
32704    a.byterange.offset === b.byterange.offset &&
32705    a.byterange.length === b.byterange.length
32706  );
32707}; // exported for testing
32708
32709const compareSidxEntry = (playlists, oldSidxMapping) => {
32710  const newSidxMapping = {};
32711  for (const id in playlists) {
32712    const playlist = playlists[id];
32713    const currentSidxInfo = playlist.sidx;
32714    if (currentSidxInfo) {
32715      const key = mpdParser.generateSidxKey(currentSidxInfo);
32716      if (!oldSidxMapping[key]) {
32717        break;
32718      }
32719      const savedSidxInfo = oldSidxMapping[key].sidxInfo;
32720      if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
32721        newSidxMapping[key] = oldSidxMapping[key];
32722      }
32723    }
32724  }
32725  return newSidxMapping;
32726};
32727/**
32728 *  A function that filters out changed items as they need to be requested separately.
32729 *
32730 *  The method is exported for testing
32731 *
32732 *  @param {Object} main the parsed mpd XML returned via mpd-parser
32733 *  @param {Object} oldSidxMapping the SIDX to compare against
32734 */
32735
32736const filterChangedSidxMappings = (main, oldSidxMapping) => {
32737  const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
32738  let mediaGroupSidx = videoSidx;
32739  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
32740    if (properties.playlists && properties.playlists.length) {
32741      const playlists = properties.playlists;
32742      mediaGroupSidx = merge(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
32743    }
32744  });
32745  return mediaGroupSidx;
32746};
32747class DashPlaylistLoader extends EventTarget {
32748  // DashPlaylistLoader must accept either a src url or a playlist because subsequent
32749  // playlist loader setups from media groups will expect to be able to pass a playlist
32750  // (since there aren't external URLs to media playlists with DASH)
32751  constructor(srcUrlOrPlaylist, vhs, options = {}, mainPlaylistLoader) {
32752    super();
32753    this.mainPlaylistLoader_ = mainPlaylistLoader || this;
32754    if (!mainPlaylistLoader) {
32755      this.isMain_ = true;
32756    }
32757    const { withCredentials = false } = options;
32758    this.vhs_ = vhs;
32759    this.withCredentials = withCredentials;
32760    this.addMetadataToTextTrack = options.addMetadataToTextTrack;
32761    if (!srcUrlOrPlaylist) {
32762      throw new Error('A non-empty playlist URL or object is required');
32763    } // event naming?
32764
32765    this.on('minimumUpdatePeriod', () => {
32766      this.refreshXml_();
32767    }); // live playlist staleness timeout
32768
32769    this.on('mediaupdatetimeout', () => {
32770      this.refreshMedia_(this.media().id);
32771    });
32772    this.state = 'HAVE_NOTHING';
32773    this.loadedPlaylists_ = {};
32774    this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
32775    // The mainPlaylistLoader will be created with a string
32776
32777    if (this.isMain_) {
32778      this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
32779      // once multi-period is refactored
32780
32781      this.mainPlaylistLoader_.sidxMapping_ = {};
32782    } else {
32783      this.childPlaylist_ = srcUrlOrPlaylist;
32784    }
32785  }
32786  requestErrored_(err, request, startingState) {
32787    // disposed
32788    if (!this.request) {
32789      return true;
32790    } // pending request is cleared
32791
32792    this.request = null;
32793    if (err) {
32794      // use the provided error object or create one
32795      // based on the request/response
32796      this.error =
32797        typeof err === 'object' && !(err instanceof Error)
32798          ? err
32799          : {
32800              status: request.status,
32801              message: 'DASH request error at URL: ' + request.uri,
32802              response: request.response,
32803              // MEDIA_ERR_NETWORK
32804              code: 2,
32805              metadata: err.metadata,
32806            };
32807      if (startingState) {
32808        this.state = startingState;
32809      }
32810      this.trigger('error');
32811      return true;
32812    }
32813  }
32814  /**
32815   * Verify that the container of the sidx segment can be parsed
32816   * and if it can, get and parse that segment.
32817   */
32818
32819  addSidxSegments_(playlist, startingState, cb) {
32820    const sidxKey = playlist.sidx && mpdParser.generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
32821
32822    if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
32823      // keep this function async
32824      this.mediaRequest_ = window__default['default'].setTimeout(() => cb(false), 0);
32825      return;
32826    } // resolve the segment URL relative to the playlist
32827
32828    const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
32829    const fin = (err, request) => {
32830      if (this.requestErrored_(err, request, startingState)) {
32831        return;
32832      }
32833      const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
32834      const { requestType } = request;
32835      let sidx;
32836      try {
32837        sidx = parseSidx__default['default'](byteHelpers.toUint8(request.response).subarray(8));
32838      } catch (e) {
32839        e.metadata = getStreamingNetworkErrorMetadata({
32840          requestType,
32841          request,
32842          parseFailure: true,
32843        }); // sidx parsing failed.
32844
32845        this.requestErrored_(e, request, startingState);
32846        return;
32847      }
32848      sidxMapping[sidxKey] = {
32849        sidxInfo: playlist.sidx,
32850        sidx,
32851      };
32852      mpdParser.addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri);
32853      return cb(true);
32854    };
32855    const REQUEST_TYPE = 'dash-sidx';
32856    this.request = containerRequest(
32857      uri,
32858      this.vhs_.xhr,
32859      (err, request, container, bytes) => {
32860        if (err) {
32861          return fin(err, request);
32862        }
32863        if (!container || container !== 'mp4') {
32864          const sidxContainer = container || 'unknown';
32865          return fin(
32866            {
32867              status: request.status,
32868              message: `Unsupported ${sidxContainer} container type for sidx segment at URL: ${uri}`,
32869              // response is just bytes in this case
32870              // but we really don't want to return that.
32871              response: '',
32872              playlist,
32873              internal: true,
32874              playlistExclusionDuration: Infinity,
32875              // MEDIA_ERR_NETWORK
32876              code: 2,
32877            },
32878            request
32879          );
32880        } // if we already downloaded the sidx bytes in the container request, use them
32881
32882        const { offset, length } = playlist.sidx.byterange;
32883        if (bytes.length >= length + offset) {
32884          return fin(err, {
32885            response: bytes.subarray(offset, offset + length),
32886            status: request.status,
32887            uri: request.uri,
32888          });
32889        } // otherwise request sidx bytes
32890
32891        this.request = this.vhs_.xhr(
32892          {
32893            uri,
32894            responseType: 'arraybuffer',
32895            requestType: 'dash-sidx',
32896            headers: segmentXhrHeaders({
32897              byterange: playlist.sidx.byterange,
32898            }),
32899          },
32900          fin
32901        );
32902      },
32903      REQUEST_TYPE
32904    );
32905  }
32906  dispose() {
32907    this.trigger('dispose');
32908    this.stopRequest();
32909    this.loadedPlaylists_ = {};
32910    window__default['default'].clearTimeout(this.minimumUpdatePeriodTimeout_);
32911    window__default['default'].clearTimeout(this.mediaRequest_);
32912    window__default['default'].clearTimeout(this.mediaUpdateTimeout);
32913    this.mediaUpdateTimeout = null;
32914    this.mediaRequest_ = null;
32915    this.minimumUpdatePeriodTimeout_ = null;
32916    if (this.mainPlaylistLoader_.createMupOnMedia_) {
32917      this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
32918      this.mainPlaylistLoader_.createMupOnMedia_ = null;
32919    }
32920    this.off();
32921  }
32922  hasPendingRequest() {
32923    return this.request || this.mediaRequest_;
32924  }
32925  stopRequest() {
32926    if (this.request) {
32927      const oldRequest = this.request;
32928      this.request = null;
32929      oldRequest.onreadystatechange = null;
32930      oldRequest.abort();
32931    }
32932  }
32933  media(playlist) {
32934    // getter
32935    if (!playlist) {
32936      return this.media_;
32937    } // setter
32938
32939    if (this.state === 'HAVE_NOTHING') {
32940      throw new Error('Cannot switch media playlist from ' + this.state);
32941    }
32942    const startingState = this.state; // find the playlist object if the target playlist has been specified by URI
32943
32944    if (typeof playlist === 'string') {
32945      if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
32946        throw new Error('Unknown playlist URI: ' + playlist);
32947      }
32948      playlist = this.mainPlaylistLoader_.main.playlists[playlist];
32949    }
32950    const mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
32951
32952    if (
32953      mediaChange &&
32954      this.loadedPlaylists_[playlist.id] &&
32955      this.loadedPlaylists_[playlist.id].endList
32956    ) {
32957      this.state = 'HAVE_METADATA';
32958      this.media_ = playlist; // trigger media change if the active media has been updated
32959
32960      if (mediaChange) {
32961        this.trigger('mediachanging');
32962        this.trigger('mediachange');
32963      }
32964      return;
32965    } // switching to the active playlist is a no-op
32966
32967    if (!mediaChange) {
32968      return;
32969    } // switching from an already loaded playlist
32970
32971    if (this.media_) {
32972      this.trigger('mediachanging');
32973    }
32974    this.addSidxSegments_(playlist, startingState, (sidxChanged) => {
32975      // everything is ready just continue to haveMetadata
32976      this.haveMetadata({
32977        startingState,
32978        playlist,
32979      });
32980    });
32981  }
32982  haveMetadata({ startingState, playlist }) {
32983    this.state = 'HAVE_METADATA';
32984    this.loadedPlaylists_[playlist.id] = playlist;
32985    this.mediaRequest_ = null; // This will trigger loadedplaylist
32986
32987    this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
32988    // to resolve setup of media groups
32989
32990    if (startingState === 'HAVE_MAIN_MANIFEST') {
32991      this.trigger('loadedmetadata');
32992    } else {
32993      // trigger media change if the active media has been updated
32994      this.trigger('mediachange');
32995    }
32996  }
32997  pause() {
32998    if (this.mainPlaylistLoader_.createMupOnMedia_) {
32999      this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
33000      this.mainPlaylistLoader_.createMupOnMedia_ = null;
33001    }
33002    this.stopRequest();
33003    window__default['default'].clearTimeout(this.mediaUpdateTimeout);
33004    this.mediaUpdateTimeout = null;
33005    if (this.isMain_) {
33006      window__default['default'].clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
33007      this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
33008    }
33009    if (this.state === 'HAVE_NOTHING') {
33010      // If we pause the loader before any data has been retrieved, its as if we never
33011      // started, so reset to an unstarted state.
33012      this.started = false;
33013    }
33014  }
33015  load(isFinalRendition) {
33016    window__default['default'].clearTimeout(this.mediaUpdateTimeout);
33017    this.mediaUpdateTimeout = null;
33018    const media = this.media();
33019    if (isFinalRendition) {
33020      const delay = media ? (media.targetDuration / 2) * 1000 : 5 * 1000;
33021      this.mediaUpdateTimeout = window__default['default'].setTimeout(() => this.load(), delay);
33022      return;
33023    } // because the playlists are internal to the manifest, load should either load the
33024    // main manifest, or do nothing but trigger an event
33025
33026    if (!this.started) {
33027      this.start();
33028      return;
33029    }
33030    if (media && !media.endList) {
33031      // Check to see if this is the main loader and the MUP was cleared (this happens
33032      // when the loader was paused). `media` should be set at this point since one is always
33033      // set during `start()`.
33034      if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
33035        // Trigger minimumUpdatePeriod to refresh the main manifest
33036        this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
33037
33038        this.updateMinimumUpdatePeriodTimeout_();
33039      }
33040      this.trigger('mediaupdatetimeout');
33041    } else {
33042      this.trigger('loadedplaylist');
33043    }
33044  }
33045  start() {
33046    this.started = true; // We don't need to request the main manifest again
33047    // Call this asynchronously to match the xhr request behavior below
33048
33049    if (!this.isMain_) {
33050      this.mediaRequest_ = window__default['default'].setTimeout(() => this.haveMain_(), 0);
33051      return;
33052    }
33053    this.requestMain_((req, mainChanged) => {
33054      this.haveMain_();
33055      if (!this.hasPendingRequest() && !this.media_) {
33056        this.media(this.mainPlaylistLoader_.main.playlists[0]);
33057      }
33058    });
33059  }
33060  requestMain_(cb) {
33061    const metadata = {
33062      manifestInfo: {
33063        uri: this.mainPlaylistLoader_.srcUrl,
33064      },
33065    };
33066    this.trigger({
33067      type: 'manifestrequeststart',
33068      metadata,
33069    });
33070    this.request = this.vhs_.xhr(
33071      {
33072        uri: this.mainPlaylistLoader_.srcUrl,
33073        withCredentials: this.withCredentials,
33074        requestType: 'dash-manifest',
33075      },
33076      (error, req) => {
33077        if (error) {
33078          const { requestType } = req;
33079          error.metadata = getStreamingNetworkErrorMetadata({
33080            requestType,
33081            request: req,
33082            error,
33083          });
33084        }
33085        if (this.requestErrored_(error, req)) {
33086          if (this.state === 'HAVE_NOTHING') {
33087            this.started = false;
33088          }
33089          return;
33090        }
33091        this.trigger({
33092          type: 'manifestrequestcomplete',
33093          metadata,
33094        });
33095        const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
33096        this.mainPlaylistLoader_.mainXml_ = req.responseText;
33097        if (req.responseHeaders && req.responseHeaders.date) {
33098          this.mainLoaded_ = Date.parse(req.responseHeaders.date);
33099        } else {
33100          this.mainLoaded_ = Date.now();
33101        }
33102        this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(
33103          this.mainPlaylistLoader_.srcUrl,
33104          req
33105        );
33106        if (mainChanged) {
33107          this.handleMain_();
33108          this.syncClientServerClock_(() => {
33109            return cb(req, mainChanged);
33110          });
33111          return;
33112        }
33113        return cb(req, mainChanged);
33114      }
33115    );
33116  }
33117  /**
33118   * Parses the main xml for UTCTiming node to sync the client clock to the server
33119   * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
33120   *
33121   * @param {Function} done
33122   *        Function to call when clock sync has completed
33123   */
33124
33125  syncClientServerClock_(done) {
33126    const utcTiming = mpdParser.parseUTCTiming(this.mainPlaylistLoader_.mainXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
33127    // server clock
33128
33129    if (utcTiming === null) {
33130      this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
33131      return done();
33132    }
33133    if (utcTiming.method === 'DIRECT') {
33134      this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
33135      return done();
33136    }
33137    this.request = this.vhs_.xhr(
33138      {
33139        uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
33140        method: utcTiming.method,
33141        withCredentials: this.withCredentials,
33142        requestType: 'dash-clock-sync',
33143      },
33144      (error, req) => {
33145        // disposed
33146        if (!this.request) {
33147          return;
33148        }
33149        if (error) {
33150          const { requestType } = req;
33151          this.error.metadata = getStreamingNetworkErrorMetadata({
33152            requestType,
33153            request: req,
33154            error,
33155          }); // sync request failed, fall back to using date header from mpd
33156          // TODO: log warning
33157
33158          this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
33159          return done();
33160        }
33161        let serverTime;
33162        if (utcTiming.method === 'HEAD') {
33163          if (!req.responseHeaders || !req.responseHeaders.date) {
33164            // expected date header not preset, fall back to using date header from mpd
33165            // TODO: log warning
33166            serverTime = this.mainLoaded_;
33167          } else {
33168            serverTime = Date.parse(req.responseHeaders.date);
33169          }
33170        } else {
33171          serverTime = Date.parse(req.responseText);
33172        }
33173        this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
33174        done();
33175      }
33176    );
33177  }
33178  haveMain_() {
33179    this.state = 'HAVE_MAIN_MANIFEST';
33180    if (this.isMain_) {
33181      // We have the main playlist at this point, so
33182      // trigger this to allow PlaylistController
33183      // to make an initial playlist selection
33184      this.trigger('loadedplaylist');
33185    } else if (!this.media_) {
33186      // no media playlist was specifically selected so select
33187      // the one the child playlist loader was created with
33188      this.media(this.childPlaylist_);
33189    }
33190  }
33191  handleMain_() {
33192    // clear media request
33193    this.mediaRequest_ = null;
33194    const oldMain = this.mainPlaylistLoader_.main;
33195    const metadata = {
33196      manifestInfo: {
33197        uri: this.mainPlaylistLoader_.srcUrl,
33198      },
33199    };
33200    this.trigger({
33201      type: 'manifestparsestart',
33202      metadata,
33203    });
33204    let newMain;
33205    try {
33206      newMain = parseMainXml({
33207        mainXml: this.mainPlaylistLoader_.mainXml_,
33208        srcUrl: this.mainPlaylistLoader_.srcUrl,
33209        clientOffset: this.mainPlaylistLoader_.clientOffset_,
33210        sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
33211        previousManifest: oldMain,
33212      });
33213    } catch (error) {
33214      this.error = error;
33215      this.error.metadata = {
33216        errorType: videojs.Error.StreamingDashManifestParserError,
33217        error,
33218      };
33219      this.trigger('error');
33220    } // if we have an old main to compare the new main against
33221
33222    if (oldMain) {
33223      newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
33224    } // only update main if we have a new main
33225
33226    this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
33227    const location =
33228      this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
33229    if (location && location !== this.mainPlaylistLoader_.srcUrl) {
33230      this.mainPlaylistLoader_.srcUrl = location;
33231    }
33232    if (!oldMain || (newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod)) {
33233      this.updateMinimumUpdatePeriodTimeout_();
33234    }
33235    this.addEventStreamToMetadataTrack_(newMain);
33236    if (newMain) {
33237      const { duration, endList } = newMain;
33238      const renditions = [];
vendor: 5,588 bytes, lines 33239-33405
33239      newMain.playlists.forEach((playlist) => {
33240        renditions.push({
33241          id: playlist.id,
33242          bandwidth: playlist.attributes.BANDWIDTH,
33243          resolution: playlist.attributes.RESOLUTION,
33244          codecs: playlist.attributes.CODECS,
33245        });
33246      });
33247      const parsedManifest = {
33248        duration,
33249        isLive: !endList,
33250        renditions,
33251      };
33252      metadata.parsedManifest = parsedManifest;
33253      this.trigger({
33254        type: 'manifestparsecomplete',
33255        metadata,
33256      });
33257    }
33258    return Boolean(newMain);
33259  }
33260  updateMinimumUpdatePeriodTimeout_() {
33261    const mpl = this.mainPlaylistLoader_; // cancel any pending creation of mup on media
33262    // a new one will be added if needed.
33263
33264    if (mpl.createMupOnMedia_) {
33265      mpl.off('loadedmetadata', mpl.createMupOnMedia_);
33266      mpl.createMupOnMedia_ = null;
33267    } // clear any pending timeouts
33268
33269    if (mpl.minimumUpdatePeriodTimeout_) {
33270      window__default['default'].clearTimeout(mpl.minimumUpdatePeriodTimeout_);
33271      mpl.minimumUpdatePeriodTimeout_ = null;
33272    }
33273    let mup = mpl.main && mpl.main.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
33274    // MPD has no future validity, so a new one will need to be acquired when new
33275    // media segments are to be made available. Thus, we use the target duration
33276    // in this case
33277
33278    if (mup === 0) {
33279      if (mpl.media()) {
33280        mup = mpl.media().targetDuration * 1000;
33281      } else {
33282        mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
33283        mpl.one('loadedmetadata', mpl.createMupOnMedia_);
33284      }
33285    } // if minimumUpdatePeriod is invalid or <= zero, which
33286    // can happen when a live video becomes VOD. skip timeout
33287    // creation.
33288
33289    if (typeof mup !== 'number' || mup <= 0) {
33290      if (mup < 0) {
33291        this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
33292      }
33293      return;
33294    }
33295    this.createMUPTimeout_(mup);
33296  }
33297  createMUPTimeout_(mup) {
33298    const mpl = this.mainPlaylistLoader_;
33299    mpl.minimumUpdatePeriodTimeout_ = window__default['default'].setTimeout(() => {
33300      mpl.minimumUpdatePeriodTimeout_ = null;
33301      mpl.trigger('minimumUpdatePeriod');
33302      mpl.createMUPTimeout_(mup);
33303    }, mup);
33304  }
33305  /**
33306   * Sends request to refresh the main xml and updates the parsed main manifest
33307   */
33308
33309  refreshXml_() {
33310    this.requestMain_((req, mainChanged) => {
33311      if (!mainChanged) {
33312        return;
33313      }
33314      if (this.media_) {
33315        this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
33316      } // This will filter out updated sidx info from the mapping
33317
33318      this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(
33319        this.mainPlaylistLoader_.main,
33320        this.mainPlaylistLoader_.sidxMapping_
33321      );
33322      this.addSidxSegments_(this.media(), this.state, (sidxChanged) => {
33323        // TODO: do we need to reload the current playlist?
33324        this.refreshMedia_(this.media().id);
33325      });
33326    });
33327  }
33328  /**
33329   * Refreshes the media playlist by re-parsing the main xml and updating playlist
33330   * references. If this is an alternate loader, the updated parsed manifest is retrieved
33331   * from the main loader.
33332   */
33333
33334  refreshMedia_(mediaID) {
33335    if (!mediaID) {
33336      throw new Error('refreshMedia_ must take a media id');
33337    } // for main we have to reparse the main xml
33338    // to re-create segments based on current timing values
33339    // which may change media. We only skip updating the main manifest
33340    // if this is the first time this.media_ is being set.
33341    // as main was just parsed in that case.
33342
33343    if (this.media_ && this.isMain_) {
33344      this.handleMain_();
33345    }
33346    const playlists = this.mainPlaylistLoader_.main.playlists;
33347    const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
33348    if (mediaChanged) {
33349      this.media_ = playlists[mediaID];
33350    } else {
33351      this.trigger('playlistunchanged');
33352    }
33353    if (!this.mediaUpdateTimeout) {
33354      const createMediaUpdateTimeout = () => {
33355        if (this.media().endList) {
33356          return;
33357        }
33358        this.mediaUpdateTimeout = window__default['default'].setTimeout(
33359          () => {
33360            this.trigger('mediaupdatetimeout');
33361            createMediaUpdateTimeout();
33362          },
33363          refreshDelay(this.media(), Boolean(mediaChanged))
33364        );
33365      };
33366      createMediaUpdateTimeout();
33367    }
33368    this.trigger('loadedplaylist');
33369  }
33370  /**
33371   * Takes eventstream data from a parsed DASH manifest and adds it to the metadata text track.
33372   *
33373   * @param {manifest} newMain the newly parsed manifest
33374   */
33375
33376  addEventStreamToMetadataTrack_(newMain) {
33377    // Only add new event stream metadata if we have a new manifest.
33378    if (newMain && this.mainPlaylistLoader_.main.eventStream) {
33379      // convert EventStream to ID3-like data.
33380      const metadataArray = this.mainPlaylistLoader_.main.eventStream.map((eventStreamNode) => {
33381        return {
33382          cueTime: eventStreamNode.start,
33383          frames: [
33384            {
33385              data: eventStreamNode.messageData,
33386            },
33387          ],
33388        };
33389      });
33390      this.addMetadataToTextTrack(
33391        'EventStream',
33392        metadataArray,
33393        this.mainPlaylistLoader_.main.duration
33394      );
33395    }
33396  }
33397  /**
33398   * Returns the key ID set from a playlist
33399   *
33400   * @param {playlist} playlist to fetch the key ID set from.
33401   * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
33402   */
33403
33404  getKeyIdSet(playlist) {
33405    if (playlist.contentProtection) {
vendor: 10,169 bytes, lines 33406-33754
33406      const keyIds = new Set();
33407      for (const keysystem in playlist.contentProtection) {
33408        const defaultKID = playlist.contentProtection[keysystem].attributes['cenc:default_KID'];
33409        if (defaultKID) {
33410          // DASH keyIds are separated by dashes.
33411          keyIds.add(defaultKID.replace(/-/g, '').toLowerCase());
33412        }
33413      }
33414      return keyIds;
33415    }
33416  }
33417}
33418var Config = {
33419  GOAL_BUFFER_LENGTH: 30,
33420  MAX_GOAL_BUFFER_LENGTH: 60,
33421  BACK_BUFFER_LENGTH: 30,
33422  GOAL_BUFFER_LENGTH_RATE: 1,
33423  // 0.5 MB/s
33424  INITIAL_BANDWIDTH: 4194304,
33425  // A fudge factor to apply to advertised playlist bitrates to account for
33426  // temporary flucations in client bandwidth
33427  BANDWIDTH_VARIANCE: 1.2,
33428  // How much of the buffer must be filled before we consider upswitching
33429  BUFFER_LOW_WATER_LINE: 0,
33430  MAX_BUFFER_LOW_WATER_LINE: 30,
33431  // TODO: Remove this when experimentalBufferBasedABR is removed
33432  EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
33433  BUFFER_LOW_WATER_LINE_RATE: 1,
33434  // If the buffer is greater than the high water line, we won't switch down
33435  BUFFER_HIGH_WATER_LINE: 30,
33436};
33437const stringToArrayBuffer = (string) => {
33438  const view = new Uint8Array(new ArrayBuffer(string.length));
33439  for (let i = 0; i < string.length; i++) {
33440    view[i] = string.charCodeAt(i);
33441  }
33442  return view.buffer;
33443};
33444
33445/* global Blob, BlobBuilder, Worker */
33446// unify worker interface
33447const browserWorkerPolyFill = function (workerObj) {
33448  // node only supports on/off
33449  workerObj.on = workerObj.addEventListener;
33450  workerObj.off = workerObj.removeEventListener;
33451  return workerObj;
33452};
33453const createObjectURL = function (str) {
33454  try {
33455    return URL.createObjectURL(
33456      new Blob([str], {
33457        type: 'application/javascript',
33458      })
33459    );
33460  } catch (e) {
33461    const blob = new BlobBuilder();
33462    blob.append(str);
33463    return URL.createObjectURL(blob.getBlob());
33464  }
33465};
33466const factory = function (code) {
33467  return function () {
33468    const objectUrl = createObjectURL(code);
33469    const worker = browserWorkerPolyFill(new Worker(objectUrl));
33470    worker.objURL = objectUrl;
33471    const terminate = worker.terminate;
33472    worker.on = worker.addEventListener;
33473    worker.off = worker.removeEventListener;
33474    worker.terminate = function () {
33475      URL.revokeObjectURL(objectUrl);
33476      return terminate.call(this);
33477    };
33478    return worker;
33479  };
33480};
33481const transform = function (code) {
33482  return (
33483    `var browserWorkerPolyFill = ${browserWorkerPolyFill.toString()};\n` +
33484    'browserWorkerPolyFill(self);\n' +
33485    code
33486  );
33487};
33488const getWorkerString = function (fn) {
33489  return fn
33490    .toString()
33491    .replace(/^function.+?{/, '')
33492    .slice(0, -1);
33493};
33494
33495/* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
33496const workerCode$1 = transform(
33497  getWorkerString(function () {
33498    var commonjsGlobal =
33499      typeof globalThis !== 'undefined'
33500        ? globalThis
33501        : typeof window !== 'undefined'
33502          ? window
33503          : typeof global !== 'undefined'
33504            ? global
33505            : typeof self !== 'undefined'
33506              ? self
33507              : {};
33508    /**
33509     * mux.js
33510     *
33511     * Copyright (c) Brightcove
33512     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
33513     *
33514     * A lightweight readable stream implemention that handles event dispatching.
33515     * Objects that inherit from streams should call init in their constructors.
33516     */
33517
33518    var Stream$8 = function () {
33519      this.init = function () {
33520        var listeners = {};
33521        /**
33522         * Add a listener for a specified event type.
33523         * @param type {string} the event name
33524         * @param listener {function} the callback to be invoked when an event of
33525         * the specified type occurs
33526         */
33527
33528        this.on = function (type, listener) {
33529          if (!listeners[type]) {
33530            listeners[type] = [];
33531          }
33532          listeners[type] = listeners[type].concat(listener);
33533        };
33534        /**
33535         * Remove a listener for a specified event type.
33536         * @param type {string} the event name
33537         * @param listener {function} a function previously registered for this
33538         * type of event through `on`
33539         */
33540
33541        this.off = function (type, listener) {
33542          var index;
33543          if (!listeners[type]) {
33544            return false;
33545          }
33546          index = listeners[type].indexOf(listener);
33547          listeners[type] = listeners[type].slice();
33548          listeners[type].splice(index, 1);
33549          return index > -1;
33550        };
33551        /**
33552         * Trigger an event of the specified type on this stream. Any additional
33553         * arguments to this function are passed as parameters to event listeners.
33554         * @param type {string} the event name
33555         */
33556
33557        this.trigger = function (type) {
33558          var callbacks, i, length, args;
33559          callbacks = listeners[type];
33560          if (!callbacks) {
33561            return;
33562          } // Slicing the arguments on every invocation of this method
33563          // can add a significant amount of overhead. Avoid the
33564          // intermediate object creation for the common case of a
33565          // single callback argument
33566
33567          if (arguments.length === 2) {
33568            length = callbacks.length;
33569            for (i = 0; i < length; ++i) {
33570              callbacks[i].call(this, arguments[1]);
33571            }
33572          } else {
33573            args = [];
33574            i = arguments.length;
33575            for (i = 1; i < arguments.length; ++i) {
33576              args.push(arguments[i]);
33577            }
33578            length = callbacks.length;
33579            for (i = 0; i < length; ++i) {
33580              callbacks[i].apply(this, args);
33581            }
33582          }
33583        };
33584        /**
33585         * Destroys the stream and cleans up.
33586         */
33587
33588        this.dispose = function () {
33589          listeners = {};
33590        };
33591      };
33592    };
33593    /**
33594     * Forwards all `data` events on this stream to the destination stream. The
33595     * destination stream should provide a method `push` to receive the data
33596     * events as they arrive.
33597     * @param destination {stream} the stream that will receive all `data` events
33598     * @param autoFlush {boolean} if false, we will not call `flush` on the destination
33599     *                            when the current stream emits a 'done' event
33600     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
33601     */
33602
33603    Stream$8.prototype.pipe = function (destination) {
33604      this.on('data', function (data) {
33605        destination.push(data);
33606      });
33607      this.on('done', function (flushSource) {
33608        destination.flush(flushSource);
33609      });
33610      this.on('partialdone', function (flushSource) {
33611        destination.partialFlush(flushSource);
33612      });
33613      this.on('endedtimeline', function (flushSource) {
33614        destination.endTimeline(flushSource);
33615      });
33616      this.on('reset', function (flushSource) {
33617        destination.reset(flushSource);
33618      });
33619      return destination;
33620    }; // Default stream functions that are expected to be overridden to perform
33621    // actual work. These are provided by the prototype as a sort of no-op
33622    // implementation so that we don't have to check for their existence in the
33623    // `pipe` function above.
33624
33625    Stream$8.prototype.push = function (data) {
33626      this.trigger('data', data);
33627    };
33628    Stream$8.prototype.flush = function (flushSource) {
33629      this.trigger('done', flushSource);
33630    };
33631    Stream$8.prototype.partialFlush = function (flushSource) {
33632      this.trigger('partialdone', flushSource);
33633    };
33634    Stream$8.prototype.endTimeline = function (flushSource) {
33635      this.trigger('endedtimeline', flushSource);
33636    };
33637    Stream$8.prototype.reset = function (flushSource) {
33638      this.trigger('reset', flushSource);
33639    };
33640    var stream = Stream$8;
33641    var MAX_UINT32$1 = Math.pow(2, 32);
33642    var getUint64$5 = function (uint8) {
33643      var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
33644      var value;
33645      if (dv.getBigUint64) {
33646        value = dv.getBigUint64(0);
33647        if (value < Number.MAX_SAFE_INTEGER) {
33648          return Number(value);
33649        }
33650        return value;
33651      }
33652      return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
33653    };
33654    var numbers = {
33655      getUint64: getUint64$5,
33656      MAX_UINT32: MAX_UINT32$1,
33657    };
33658    /**
33659     * mux.js
33660     *
33661     * Copyright (c) Brightcove
33662     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
33663     *
33664     * Functions that generate fragmented MP4s suitable for use with Media
33665     * Source Extensions.
33666     */
33667
33668    var MAX_UINT32 = numbers.MAX_UINT32;
33669    var box,
33670      dinf,
33671      esds,
33672      ftyp,
33673      mdat,
33674      mfhd,
33675      minf,
33676      moof,
33677      moov,
33678      mvex,
33679      mvhd,
33680      trak,
33681      tkhd,
33682      mdia,
33683      mdhd,
33684      hdlr,
33685      sdtp,
33686      stbl,
33687      stsd,
33688      traf,
33689      trex,
33690      trun$1,
33691      types,
33692      MAJOR_BRAND,
33693      MINOR_VERSION,
33694      AVC1_BRAND,
33695      VIDEO_HDLR,
33696      AUDIO_HDLR,
33697      HDLR_TYPES,
33698      VMHD,
33699      SMHD,
33700      DREF,
33701      STCO,
33702      STSC,
33703      STSZ,
33704      STTS; // pre-calculate constants
33705
33706    (function () {
33707      var i;
33708      types = {
33709        avc1: [],
33710        // codingname
33711        avcC: [],
33712        btrt: [],
33713        dinf: [],
33714        dref: [],
33715        esds: [],
33716        ftyp: [],
33717        hdlr: [],
33718        mdat: [],
33719        mdhd: [],
33720        mdia: [],
33721        mfhd: [],
33722        minf: [],
33723        moof: [],
33724        moov: [],
33725        mp4a: [],
33726        // codingname
33727        mvex: [],
33728        mvhd: [],
33729        pasp: [],
33730        sdtp: [],
33731        smhd: [],
33732        stbl: [],
33733        stco: [],
33734        stsc: [],
33735        stsd: [],
33736        stsz: [],
33737        stts: [],
33738        styp: [],
33739        tfdt: [],
33740        tfhd: [],
33741        traf: [],
33742        trak: [],
33743        trun: [],
33744        trex: [],
33745        tkhd: [],
33746        vmhd: [],
33747      }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
33748      // don't throw an error
33749
33750      if (typeof Uint8Array === 'undefined') {
33751        return;
33752      }
33753      for (i in types) {
33754        if (types.hasOwnProperty(i)) {
33755          types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
33756        }
33757      }
33758      MAJOR_BRAND = new Uint8Array([
33759        'i'.charCodeAt(0),
33760        's'.charCodeAt(0),
33761        'o'.charCodeAt(0),
33762        'm'.charCodeAt(0),
33763      ]);
33764      AVC1_BRAND = new Uint8Array([
33765        'a'.charCodeAt(0),
33766        'v'.charCodeAt(0),
33767        'c'.charCodeAt(0),
33768        '1'.charCodeAt(0),
33769      ]);
33770      MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
33771      VIDEO_HDLR = new Uint8Array([
33772        0x00,
33773        // version 0
33774        0x00,
33775        0x00,
33776        0x00,
33777        // flags
33778        0x00,
33779        0x00,
33780        0x00,
33781        0x00,
33782        // pre_defined
33783        0x76,
33784        0x69,
33785        0x64,
33786        0x65,
33787        // handler_type: 'vide'
33788        0x00,
33789        0x00,
33790        0x00,
33791        0x00,
33792        // reserved
33793        0x00,
33794        0x00,
33795        0x00,
33796        0x00,
33797        // reserved
33798        0x00,
33799        0x00,
33800        0x00,
33801        0x00,
33802        // reserved
33803        0x56,
33804        0x69,
33805        0x64,
33806        0x65,
33807        0x6f,
33808        0x48,
33809        0x61,
33810        0x6e,
33811        0x64,
33812        0x6c,
33813        0x65,
33814        0x72,
33815        0x00, // name: 'VideoHandler'
33816      ]);
33817      AUDIO_HDLR = new Uint8Array([
33818        0x00,
33819        // version 0
33820        0x00,
33821        0x00,
33822        0x00,
33823        // flags
33824        0x00,
33825        0x00,
33826        0x00,
33827        0x00,
33828        // pre_defined
33829        0x73,
33830        0x6f,
33831        0x75,
33832        0x6e,
33833        // handler_type: 'soun'
33834        0x00,
33835        0x00,
33836        0x00,
33837        0x00,
33838        // reserved
33839        0x00,
33840        0x00,
33841        0x00,
33842        0x00,
33843        // reserved
33844        0x00,
33845        0x00,
33846        0x00,
33847        0x00,
33848        // reserved
33849        0x53,
33850        0x6f,
33851        0x75,
33852        0x6e,
33853        0x64,
33854        0x48,
33855        0x61,
33856        0x6e,
33857        0x64,
33858        0x6c,
33859        0x65,
33860        0x72,
33861        0x00, // name: 'SoundHandler'
33862      ]);
33863      HDLR_TYPES = {
33864        video: VIDEO_HDLR,
33865        audio: AUDIO_HDLR,
33866      };
33867      DREF = new Uint8Array([
33868        0x00,
33869        // version 0
33870        0x00,
33871        0x00,
33872        0x00,
33873        // flags
33874        0x00,
33875        0x00,
33876        0x00,
33877        0x01,
33878        // entry_count
33879        0x00,
33880        0x00,
33881        0x00,
33882        0x0c,
33883        // entry_size
33884        0x75,
33885        0x72,
33886        0x6c,
33887        0x20,
33888        // 'url' type
33889        0x00,
33890        // version 0
33891        0x00,
33892        0x00,
33893        0x01, // entry_flags
33894      ]);
33895      SMHD = new Uint8Array([
33896        0x00,
33897        // version
33898        0x00,
33899        0x00,
33900        0x00,
33901        // flags
33902        0x00,
33903        0x00,
33904        // balance, 0 means centered
33905        0x00,
33906        0x00, // reserved
33907      ]);
33908      STCO = new Uint8Array([
33909        0x00,
33910        // version
33911        0x00,
33912        0x00,
33913        0x00,
33914        // flags
33915        0x00,
33916        0x00,
33917        0x00,
33918        0x00, // entry_count
33919      ]);
33920      STSC = STCO;
33921      STSZ = new Uint8Array([
33922        0x00,
33923        // version
33924        0x00,
33925        0x00,
33926        0x00,
33927        // flags
33928        0x00,
33929        0x00,
33930        0x00,
33931        0x00,
33932        // sample_size
33933        0x00,
33934        0x00,
33935        0x00,
33936        0x00, // sample_count
33937      ]);
33938      STTS = STCO;
33939      VMHD = new Uint8Array([
33940        0x00,
33941        // version
33942        0x00,
33943        0x00,
33944        0x01,
33945        // flags
33946        0x00,
33947        0x00,
33948        // graphicsmode
33949        0x00,
33950        0x00,
33951        0x00,
33952        0x00,
33953        0x00,
33954        0x00, // opcolor
33955      ]);
33956    })();
33957    box = function (type) {
33958      var payload = [],
33959        size = 0,
33960        i,
33961        result,
33962        view;
33963      for (i = 1; i < arguments.length; i++) {
33964        payload.push(arguments[i]);
33965      }
33966      i = payload.length; // calculate the total size we need to allocate
33967
33968      while (i--) {
33969        size += payload[i].byteLength;
33970      }
33971      result = new Uint8Array(size + 8);
33972      view = new DataView(result.buffer, result.byteOffset, result.byteLength);
33973      view.setUint32(0, result.byteLength);
33974      result.set(type, 4); // copy the payload into the result
33975
33976      for (i = 0, size = 8; i < payload.length; i++) {
33977        result.set(payload[i], size);
33978        size += payload[i].byteLength;
33979      }
33980      return result;
33981    };
33982    dinf = function () {
33983      return box(types.dinf, box(types.dref, DREF));
33984    };
33985    esds = function (track) {
33986      return box(
33987        types.esds,
33988        new Uint8Array([
33989          0x00,
33990          // version
33991          0x00,
33992          0x00,
33993          0x00,
33994          // flags
33995          // ES_Descriptor
33996          0x03,
33997          // tag, ES_DescrTag
33998          0x19,
33999          // length
34000          0x00,
34001          0x00,
34002          // ES_ID
34003          0x00,
34004          // streamDependenceFlag, URL_flag, reserved, streamPriority
34005          // DecoderConfigDescriptor
34006          0x04,
34007          // tag, DecoderConfigDescrTag
34008          0x11,
34009          // length
34010          0x40,
34011          // object type
34012          0x15,
34013          // streamType
34014          0x00,
34015          0x06,
34016          0x00,
34017          // bufferSizeDB
34018          0x00,
34019          0x00,
34020          0xda,
34021          0xc0,
34022          // maxBitrate
34023          0x00,
34024          0x00,
34025          0xda,
34026          0xc0,
34027          // avgBitrate
34028          // DecoderSpecificInfo
34029          0x05,
34030          // tag, DecoderSpecificInfoTag
34031          0x02,
34032          // length
34033          // ISO/IEC 14496-3, AudioSpecificConfig
34034          // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
34035          (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1),
34036          (track.samplingfrequencyindex << 7) | (track.channelcount << 3),
34037          0x06,
34038          0x01,
34039          0x02, // GASpecificConfig
34040        ])
34041      );
34042    };
34043    ftyp = function () {
34044      return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
34045    };
34046    hdlr = function (type) {
34047      return box(types.hdlr, HDLR_TYPES[type]);
34048    };
34049    mdat = function (data) {
34050      return box(types.mdat, data);
34051    };
34052    mdhd = function (track) {
34053      var result = new Uint8Array([
34054        0x00,
34055        // version 0
34056        0x00,
34057        0x00,
34058        0x00,
34059        // flags
34060        0x00,
34061        0x00,
34062        0x00,
34063        0x02,
34064        // creation_time
34065        0x00,
34066        0x00,
34067        0x00,
34068        0x03,
34069        // modification_time
34070        0x00,
34071        0x01,
34072        0x5f,
34073        0x90,
34074        // timescale, 90,000 "ticks" per second
34075        (track.duration >>> 24) & 0xff,
34076        (track.duration >>> 16) & 0xff,
34077        (track.duration >>> 8) & 0xff,
34078        track.duration & 0xff,
34079        // duration
34080        0x55,
34081        0xc4,
34082        // 'und' language (undetermined)
34083        0x00,
34084        0x00,
34085      ]); // Use the sample rate from the track metadata, when it is
34086      // defined. The sample rate can be parsed out of an ADTS header, for
34087      // instance.
34088
34089      if (track.samplerate) {
34090        result[12] = (track.samplerate >>> 24) & 0xff;
vendor: 51,941 bytes, lines 34091-35772
34091        result[13] = (track.samplerate >>> 16) & 0xff;
34092        result[14] = (track.samplerate >>> 8) & 0xff;
34093        result[15] = track.samplerate & 0xff;
34094      }
34095      return box(types.mdhd, result);
34096    };
34097    mdia = function (track) {
34098      return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
34099    };
34100    mfhd = function (sequenceNumber) {
34101      return box(
34102        types.mfhd,
34103        new Uint8Array([
34104          0x00,
34105          0x00,
34106          0x00,
34107          0x00,
34108          // flags
34109          (sequenceNumber & 0xff000000) >> 24,
34110          (sequenceNumber & 0xff0000) >> 16,
34111          (sequenceNumber & 0xff00) >> 8,
34112          sequenceNumber & 0xff, // sequence_number
34113        ])
34114      );
34115    };
34116    minf = function (track) {
34117      return box(
34118        types.minf,
34119        track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
34120        dinf(),
34121        stbl(track)
34122      );
34123    };
34124    moof = function (sequenceNumber, tracks) {
34125      var trackFragments = [],
34126        i = tracks.length; // build traf boxes for each track fragment
34127
34128      while (i--) {
34129        trackFragments[i] = traf(tracks[i]);
34130      }
34131      return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
34132    };
34133    /**
34134     * Returns a movie box.
34135     * @param tracks {array} the tracks associated with this movie
34136     * @see ISO/IEC 14496-12:2012(E), section 8.2.1
34137     */
34138
34139    moov = function (tracks) {
34140      var i = tracks.length,
34141        boxes = [];
34142      while (i--) {
34143        boxes[i] = trak(tracks[i]);
34144      }
34145      return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
34146    };
34147    mvex = function (tracks) {
34148      var i = tracks.length,
34149        boxes = [];
34150      while (i--) {
34151        boxes[i] = trex(tracks[i]);
34152      }
34153      return box.apply(null, [types.mvex].concat(boxes));
34154    };
34155    mvhd = function (duration) {
34156      var bytes = new Uint8Array([
34157        0x00,
34158        // version 0
34159        0x00,
34160        0x00,
34161        0x00,
34162        // flags
34163        0x00,
34164        0x00,
34165        0x00,
34166        0x01,
34167        // creation_time
34168        0x00,
34169        0x00,
34170        0x00,
34171        0x02,
34172        // modification_time
34173        0x00,
34174        0x01,
34175        0x5f,
34176        0x90,
34177        // timescale, 90,000 "ticks" per second
34178        (duration & 0xff000000) >> 24,
34179        (duration & 0xff0000) >> 16,
34180        (duration & 0xff00) >> 8,
34181        duration & 0xff,
34182        // duration
34183        0x00,
34184        0x01,
34185        0x00,
34186        0x00,
34187        // 1.0 rate
34188        0x01,
34189        0x00,
34190        // 1.0 volume
34191        0x00,
34192        0x00,
34193        // reserved
34194        0x00,
34195        0x00,
34196        0x00,
34197        0x00,
34198        // reserved
34199        0x00,
34200        0x00,
34201        0x00,
34202        0x00,
34203        // reserved
34204        0x00,
34205        0x01,
34206        0x00,
34207        0x00,
34208        0x00,
34209        0x00,
34210        0x00,
34211        0x00,
34212        0x00,
34213        0x00,
34214        0x00,
34215        0x00,
34216        0x00,
34217        0x00,
34218        0x00,
34219        0x00,
34220        0x00,
34221        0x01,
34222        0x00,
34223        0x00,
34224        0x00,
34225        0x00,
34226        0x00,
34227        0x00,
34228        0x00,
34229        0x00,
34230        0x00,
34231        0x00,
34232        0x00,
34233        0x00,
34234        0x00,
34235        0x00,
34236        0x40,
34237        0x00,
34238        0x00,
34239        0x00,
34240        // transformation: unity matrix
34241        0x00,
34242        0x00,
34243        0x00,
34244        0x00,
34245        0x00,
34246        0x00,
34247        0x00,
34248        0x00,
34249        0x00,
34250        0x00,
34251        0x00,
34252        0x00,
34253        0x00,
34254        0x00,
34255        0x00,
34256        0x00,
34257        0x00,
34258        0x00,
34259        0x00,
34260        0x00,
34261        0x00,
34262        0x00,
34263        0x00,
34264        0x00,
34265        // pre_defined
34266        0xff,
34267        0xff,
34268        0xff,
34269        0xff, // next_track_ID
34270      ]);
34271      return box(types.mvhd, bytes);
34272    };
34273    sdtp = function (track) {
34274      var samples = track.samples || [],
34275        bytes = new Uint8Array(4 + samples.length),
34276        flags,
34277        i; // leave the full box header (4 bytes) all zero
34278      // write the sample table
34279
34280      for (i = 0; i < samples.length; i++) {
34281        flags = samples[i].flags;
34282        bytes[i + 4] = (flags.dependsOn << 4) | (flags.isDependedOn << 2) | flags.hasRedundancy;
34283      }
34284      return box(types.sdtp, bytes);
34285    };
34286    stbl = function (track) {
34287      return box(
34288        types.stbl,
34289        stsd(track),
34290        box(types.stts, STTS),
34291        box(types.stsc, STSC),
34292        box(types.stsz, STSZ),
34293        box(types.stco, STCO)
34294      );
34295    };
34296    (function () {
34297      var videoSample, audioSample;
34298      stsd = function (track) {
34299        return box(
34300          types.stsd,
34301          new Uint8Array([
34302            0x00,
34303            // version 0
34304            0x00, 0x00, 0x00,
34305            // flags
34306            0x00, 0x00, 0x00, 0x01,
34307          ]),
34308          track.type === 'video' ? videoSample(track) : audioSample(track)
34309        );
34310      };
34311      videoSample = function (track) {
34312        var sps = track.sps || [],
34313          pps = track.pps || [],
34314          sequenceParameterSets = [],
34315          pictureParameterSets = [],
34316          i,
34317          avc1Box; // assemble the SPSs
34318
34319        for (i = 0; i < sps.length; i++) {
34320          sequenceParameterSets.push((sps[i].byteLength & 0xff00) >>> 8);
34321          sequenceParameterSets.push(sps[i].byteLength & 0xff); // sequenceParameterSetLength
34322
34323          sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
34324        } // assemble the PPSs
34325
34326        for (i = 0; i < pps.length; i++) {
34327          pictureParameterSets.push((pps[i].byteLength & 0xff00) >>> 8);
34328          pictureParameterSets.push(pps[i].byteLength & 0xff);
34329          pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
34330        }
34331        avc1Box = [
34332          types.avc1,
34333          new Uint8Array([
34334            0x00,
34335            0x00,
34336            0x00,
34337            0x00,
34338            0x00,
34339            0x00,
34340            // reserved
34341            0x00,
34342            0x01,
34343            // data_reference_index
34344            0x00,
34345            0x00,
34346            // pre_defined
34347            0x00,
34348            0x00,
34349            // reserved
34350            0x00,
34351            0x00,
34352            0x00,
34353            0x00,
34354            0x00,
34355            0x00,
34356            0x00,
34357            0x00,
34358            0x00,
34359            0x00,
34360            0x00,
34361            0x00,
34362            // pre_defined
34363            (track.width & 0xff00) >> 8,
34364            track.width & 0xff,
34365            // width
34366            (track.height & 0xff00) >> 8,
34367            track.height & 0xff,
34368            // height
34369            0x00,
34370            0x48,
34371            0x00,
34372            0x00,
34373            // horizresolution
34374            0x00,
34375            0x48,
34376            0x00,
34377            0x00,
34378            // vertresolution
34379            0x00,
34380            0x00,
34381            0x00,
34382            0x00,
34383            // reserved
34384            0x00,
34385            0x01,
34386            // frame_count
34387            0x13,
34388            0x76,
34389            0x69,
34390            0x64,
34391            0x65,
34392            0x6f,
34393            0x6a,
34394            0x73,
34395            0x2d,
34396            0x63,
34397            0x6f,
34398            0x6e,
34399            0x74,
34400            0x72,
34401            0x69,
34402            0x62,
34403            0x2d,
34404            0x68,
34405            0x6c,
34406            0x73,
34407            0x00,
34408            0x00,
34409            0x00,
34410            0x00,
34411            0x00,
34412            0x00,
34413            0x00,
34414            0x00,
34415            0x00,
34416            0x00,
34417            0x00,
34418            0x00,
34419            // compressorname
34420            0x00,
34421            0x18,
34422            // depth = 24
34423            0x11,
34424            0x11, // pre_defined = -1
34425          ]),
34426          box(
34427            types.avcC,
34428            new Uint8Array(
34429              [
34430                0x01,
34431                // configurationVersion
34432                track.profileIdc,
34433                // AVCProfileIndication
34434                track.profileCompatibility,
34435                // profile_compatibility
34436                track.levelIdc,
34437                // AVCLevelIndication
34438                0xff, // lengthSizeMinusOne, hard-coded to 4 bytes
34439              ].concat(
34440                [sps.length],
34441                // numOfSequenceParameterSets
34442                sequenceParameterSets,
34443                // "SPS"
34444                [pps.length],
34445                // numOfPictureParameterSets
34446                pictureParameterSets // "PPS"
34447              )
34448            )
34449          ),
34450          box(
34451            types.btrt,
34452            new Uint8Array([
34453              0x00,
34454              0x1c,
34455              0x9c,
34456              0x80,
34457              // bufferSizeDB
34458              0x00,
34459              0x2d,
34460              0xc6,
34461              0xc0,
34462              // maxBitrate
34463              0x00,
34464              0x2d,
34465              0xc6,
34466              0xc0, // avgBitrate
34467            ])
34468          ),
34469        ];
34470        if (track.sarRatio) {
34471          var hSpacing = track.sarRatio[0],
34472            vSpacing = track.sarRatio[1];
34473          avc1Box.push(
34474            box(
34475              types.pasp,
34476              new Uint8Array([
34477                (hSpacing & 0xff000000) >> 24,
34478                (hSpacing & 0xff0000) >> 16,
34479                (hSpacing & 0xff00) >> 8,
34480                hSpacing & 0xff,
34481                (vSpacing & 0xff000000) >> 24,
34482                (vSpacing & 0xff0000) >> 16,
34483                (vSpacing & 0xff00) >> 8,
34484                vSpacing & 0xff,
34485              ])
34486            )
34487          );
34488        }
34489        return box.apply(null, avc1Box);
34490      };
34491      audioSample = function (track) {
34492        return box(
34493          types.mp4a,
34494          new Uint8Array([
34495            // SampleEntry, ISO/IEC 14496-12
34496            0x00,
34497            0x00,
34498            0x00,
34499            0x00,
34500            0x00,
34501            0x00,
34502            // reserved
34503            0x00,
34504            0x01,
34505            // data_reference_index
34506            // AudioSampleEntry, ISO/IEC 14496-12
34507            0x00,
34508            0x00,
34509            0x00,
34510            0x00,
34511            // reserved
34512            0x00,
34513            0x00,
34514            0x00,
34515            0x00,
34516            // reserved
34517            (track.channelcount & 0xff00) >> 8,
34518            track.channelcount & 0xff,
34519            // channelcount
34520            (track.samplesize & 0xff00) >> 8,
34521            track.samplesize & 0xff,
34522            // samplesize
34523            0x00,
34524            0x00,
34525            // pre_defined
34526            0x00,
34527            0x00,
34528            // reserved
34529            (track.samplerate & 0xff00) >> 8,
34530            track.samplerate & 0xff,
34531            0x00,
34532            0x00, // samplerate, 16.16
34533            // MP4AudioSampleEntry, ISO/IEC 14496-14
34534          ]),
34535          esds(track)
34536        );
34537      };
34538    })();
34539    tkhd = function (track) {
34540      var result = new Uint8Array([
34541        0x00,
34542        // version 0
34543        0x00,
34544        0x00,
34545        0x07,
34546        // flags
34547        0x00,
34548        0x00,
34549        0x00,
34550        0x00,
34551        // creation_time
34552        0x00,
34553        0x00,
34554        0x00,
34555        0x00,
34556        // modification_time
34557        (track.id & 0xff000000) >> 24,
34558        (track.id & 0xff0000) >> 16,
34559        (track.id & 0xff00) >> 8,
34560        track.id & 0xff,
34561        // track_ID
34562        0x00,
34563        0x00,
34564        0x00,
34565        0x00,
34566        // reserved
34567        (track.duration & 0xff000000) >> 24,
34568        (track.duration & 0xff0000) >> 16,
34569        (track.duration & 0xff00) >> 8,
34570        track.duration & 0xff,
34571        // duration
34572        0x00,
34573        0x00,
34574        0x00,
34575        0x00,
34576        0x00,
34577        0x00,
34578        0x00,
34579        0x00,
34580        // reserved
34581        0x00,
34582        0x00,
34583        // layer
34584        0x00,
34585        0x00,
34586        // alternate_group
34587        0x01,
34588        0x00,
34589        // non-audio track volume
34590        0x00,
34591        0x00,
34592        // reserved
34593        0x00,
34594        0x01,
34595        0x00,
34596        0x00,
34597        0x00,
34598        0x00,
34599        0x00,
34600        0x00,
34601        0x00,
34602        0x00,
34603        0x00,
34604        0x00,
34605        0x00,
34606        0x00,
34607        0x00,
34608        0x00,
34609        0x00,
34610        0x01,
34611        0x00,
34612        0x00,
34613        0x00,
34614        0x00,
34615        0x00,
34616        0x00,
34617        0x00,
34618        0x00,
34619        0x00,
34620        0x00,
34621        0x00,
34622        0x00,
34623        0x00,
34624        0x00,
34625        0x40,
34626        0x00,
34627        0x00,
34628        0x00,
34629        // transformation: unity matrix
34630        (track.width & 0xff00) >> 8,
34631        track.width & 0xff,
34632        0x00,
34633        0x00,
34634        // width
34635        (track.height & 0xff00) >> 8,
34636        track.height & 0xff,
34637        0x00,
34638        0x00, // height
34639      ]);
34640      return box(types.tkhd, result);
34641    };
34642    /**
34643     * Generate a track fragment (traf) box. A traf box collects metadata
34644     * about tracks in a movie fragment (moof) box.
34645     */
34646
34647    traf = function (track) {
34648      var trackFragmentHeader,
34649        trackFragmentDecodeTime,
34650        trackFragmentRun,
34651        sampleDependencyTable,
34652        dataOffset,
34653        upperWordBaseMediaDecodeTime,
34654        lowerWordBaseMediaDecodeTime;
34655      trackFragmentHeader = box(
34656        types.tfhd,
34657        new Uint8Array([
34658          0x00,
34659          // version 0
34660          0x00,
34661          0x00,
34662          0x3a,
34663          // flags
34664          (track.id & 0xff000000) >> 24,
34665          (track.id & 0xff0000) >> 16,
34666          (track.id & 0xff00) >> 8,
34667          track.id & 0xff,
34668          // track_ID
34669          0x00,
34670          0x00,
34671          0x00,
34672          0x01,
34673          // sample_description_index
34674          0x00,
34675          0x00,
34676          0x00,
34677          0x00,
34678          // default_sample_duration
34679          0x00,
34680          0x00,
34681          0x00,
34682          0x00,
34683          // default_sample_size
34684          0x00,
34685          0x00,
34686          0x00,
34687          0x00, // default_sample_flags
34688        ])
34689      );
34690      upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
34691      lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
34692      trackFragmentDecodeTime = box(
34693        types.tfdt,
34694        new Uint8Array([
34695          0x01,
34696          // version 1
34697          0x00,
34698          0x00,
34699          0x00,
34700          // flags
34701          // baseMediaDecodeTime
34702          (upperWordBaseMediaDecodeTime >>> 24) & 0xff,
34703          (upperWordBaseMediaDecodeTime >>> 16) & 0xff,
34704          (upperWordBaseMediaDecodeTime >>> 8) & 0xff,
34705          upperWordBaseMediaDecodeTime & 0xff,
34706          (lowerWordBaseMediaDecodeTime >>> 24) & 0xff,
34707          (lowerWordBaseMediaDecodeTime >>> 16) & 0xff,
34708          (lowerWordBaseMediaDecodeTime >>> 8) & 0xff,
34709          lowerWordBaseMediaDecodeTime & 0xff,
34710        ])
34711      ); // the data offset specifies the number of bytes from the start of
34712      // the containing moof to the first payload byte of the associated
34713      // mdat
34714
34715      dataOffset =
34716        32 +
34717        // tfhd
34718        20 +
34719        // tfdt
34720        8 +
34721        // traf header
34722        16 +
34723        // mfhd
34724        8 +
34725        // moof header
34726        8; // mdat header
34727      // audio tracks require less metadata
34728
34729      if (track.type === 'audio') {
34730        trackFragmentRun = trun$1(track, dataOffset);
34731        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
34732      } // video tracks should contain an independent and disposable samples
34733      // box (sdtp)
34734      // generate one and adjust offsets to match
34735
34736      sampleDependencyTable = sdtp(track);
34737      trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
34738      return box(
34739        types.traf,
34740        trackFragmentHeader,
34741        trackFragmentDecodeTime,
34742        trackFragmentRun,
34743        sampleDependencyTable
34744      );
34745    };
34746    /**
34747     * Generate a track box.
34748     * @param track {object} a track definition
34749     * @return {Uint8Array} the track box
34750     */
34751
34752    trak = function (track) {
34753      track.duration = track.duration || 0xffffffff;
34754      return box(types.trak, tkhd(track), mdia(track));
34755    };
34756    trex = function (track) {
34757      var result = new Uint8Array([
34758        0x00,
34759        // version 0
34760        0x00,
34761        0x00,
34762        0x00,
34763        // flags
34764        (track.id & 0xff000000) >> 24,
34765        (track.id & 0xff0000) >> 16,
34766        (track.id & 0xff00) >> 8,
34767        track.id & 0xff,
34768        // track_ID
34769        0x00,
34770        0x00,
34771        0x00,
34772        0x01,
34773        // default_sample_description_index
34774        0x00,
34775        0x00,
34776        0x00,
34777        0x00,
34778        // default_sample_duration
34779        0x00,
34780        0x00,
34781        0x00,
34782        0x00,
34783        // default_sample_size
34784        0x00,
34785        0x01,
34786        0x00,
34787        0x01, // default_sample_flags
34788      ]); // the last two bytes of default_sample_flags is the sample
34789      // degradation priority, a hint about the importance of this sample
34790      // relative to others. Lower the degradation priority for all sample
34791      // types other than video.
34792
34793      if (track.type !== 'video') {
34794        result[result.length - 1] = 0x00;
34795      }
34796      return box(types.trex, result);
34797    };
34798    (function () {
34799      var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
34800      // duration is present for the first sample, it will be present for
34801      // all subsequent samples.
34802      // see ISO/IEC 14496-12:2012, Section 8.8.8.1
34803
34804      trunHeader = function (samples, offset) {
34805        var durationPresent = 0,
34806          sizePresent = 0,
34807          flagsPresent = 0,
34808          compositionTimeOffset = 0; // trun flag constants
34809
34810        if (samples.length) {
34811          if (samples[0].duration !== undefined) {
34812            durationPresent = 0x1;
34813          }
34814          if (samples[0].size !== undefined) {
34815            sizePresent = 0x2;
34816          }
34817          if (samples[0].flags !== undefined) {
34818            flagsPresent = 0x4;
34819          }
34820          if (samples[0].compositionTimeOffset !== undefined) {
34821            compositionTimeOffset = 0x8;
34822          }
34823        }
34824        return [
34825          0x00,
34826          // version 0
34827          0x00,
34828          durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
34829          0x01,
34830          // flags
34831          (samples.length & 0xff000000) >>> 24,
34832          (samples.length & 0xff0000) >>> 16,
34833          (samples.length & 0xff00) >>> 8,
34834          samples.length & 0xff,
34835          // sample_count
34836          (offset & 0xff000000) >>> 24,
34837          (offset & 0xff0000) >>> 16,
34838          (offset & 0xff00) >>> 8,
34839          offset & 0xff, // data_offset
34840        ];
34841      };
34842      videoTrun = function (track, offset) {
34843        var bytesOffest, bytes, header, samples, sample, i;
34844        samples = track.samples || [];
34845        offset += 8 + 12 + 16 * samples.length;
34846        header = trunHeader(samples, offset);
34847        bytes = new Uint8Array(header.length + samples.length * 16);
34848        bytes.set(header);
34849        bytesOffest = header.length;
34850        for (i = 0; i < samples.length; i++) {
34851          sample = samples[i];
34852          bytes[bytesOffest++] = (sample.duration & 0xff000000) >>> 24;
34853          bytes[bytesOffest++] = (sample.duration & 0xff0000) >>> 16;
34854          bytes[bytesOffest++] = (sample.duration & 0xff00) >>> 8;
34855          bytes[bytesOffest++] = sample.duration & 0xff; // sample_duration
34856
34857          bytes[bytesOffest++] = (sample.size & 0xff000000) >>> 24;
34858          bytes[bytesOffest++] = (sample.size & 0xff0000) >>> 16;
34859          bytes[bytesOffest++] = (sample.size & 0xff00) >>> 8;
34860          bytes[bytesOffest++] = sample.size & 0xff; // sample_size
34861
34862          bytes[bytesOffest++] = (sample.flags.isLeading << 2) | sample.flags.dependsOn;
34863          bytes[bytesOffest++] =
34864            (sample.flags.isDependedOn << 6) |
34865            (sample.flags.hasRedundancy << 4) |
34866            (sample.flags.paddingValue << 1) |
34867            sample.flags.isNonSyncSample;
34868          bytes[bytesOffest++] = sample.flags.degradationPriority & (0xf0 << 8);
34869          bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0f; // sample_flags
34870
34871          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xff000000) >>> 24;
34872          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xff0000) >>> 16;
34873          bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xff00) >>> 8;
34874          bytes[bytesOffest++] = sample.compositionTimeOffset & 0xff; // sample_composition_time_offset
34875        }
34876        return box(types.trun, bytes);
34877      };
34878      audioTrun = function (track, offset) {
34879        var bytes, bytesOffest, header, samples, sample, i;
34880        samples = track.samples || [];
34881        offset += 8 + 12 + 8 * samples.length;
34882        header = trunHeader(samples, offset);
34883        bytes = new Uint8Array(header.length + samples.length * 8);
34884        bytes.set(header);
34885        bytesOffest = header.length;
34886        for (i = 0; i < samples.length; i++) {
34887          sample = samples[i];
34888          bytes[bytesOffest++] = (sample.duration & 0xff000000) >>> 24;
34889          bytes[bytesOffest++] = (sample.duration & 0xff0000) >>> 16;
34890          bytes[bytesOffest++] = (sample.duration & 0xff00) >>> 8;
34891          bytes[bytesOffest++] = sample.duration & 0xff; // sample_duration
34892
34893          bytes[bytesOffest++] = (sample.size & 0xff000000) >>> 24;
34894          bytes[bytesOffest++] = (sample.size & 0xff0000) >>> 16;
34895          bytes[bytesOffest++] = (sample.size & 0xff00) >>> 8;
34896          bytes[bytesOffest++] = sample.size & 0xff; // sample_size
34897        }
34898        return box(types.trun, bytes);
34899      };
34900      trun$1 = function (track, offset) {
34901        if (track.type === 'audio') {
34902          return audioTrun(track, offset);
34903        }
34904        return videoTrun(track, offset);
34905      };
34906    })();
34907    var mp4Generator = {
34908      ftyp: ftyp,
34909      mdat: mdat,
34910      moof: moof,
34911      moov: moov,
34912      initSegment: function (tracks) {
34913        var fileType = ftyp(),
34914          movie = moov(tracks),
34915          result;
34916        result = new Uint8Array(fileType.byteLength + movie.byteLength);
34917        result.set(fileType);
34918        result.set(movie, fileType.byteLength);
34919        return result;
34920      },
34921    };
34922    /**
34923     * mux.js
34924     *
34925     * Copyright (c) Brightcove
34926     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
34927     */
34928    // composed of the nal units that make up that frame
34929    // Also keep track of cummulative data about the frame from the nal units such
34930    // as the frame duration, starting pts, etc.
34931
34932    var groupNalsIntoFrames = function (nalUnits) {
34933      var i,
34934        currentNal,
34935        currentFrame = [],
34936        frames = []; // TODO added for LHLS, make sure this is OK
34937
34938      frames.byteLength = 0;
34939      frames.nalCount = 0;
34940      frames.duration = 0;
34941      currentFrame.byteLength = 0;
34942      for (i = 0; i < nalUnits.length; i++) {
34943        currentNal = nalUnits[i]; // Split on 'aud'-type nal units
34944
34945        if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
34946          // Since the very first nal unit is expected to be an AUD
34947          // only push to the frames array when currentFrame is not empty
34948          if (currentFrame.length) {
34949            currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
34950
34951            frames.byteLength += currentFrame.byteLength;
34952            frames.nalCount += currentFrame.length;
34953            frames.duration += currentFrame.duration;
34954            frames.push(currentFrame);
34955          }
34956          currentFrame = [currentNal];
34957          currentFrame.byteLength = currentNal.data.byteLength;
34958          currentFrame.pts = currentNal.pts;
34959          currentFrame.dts = currentNal.dts;
34960        } else {
34961          // Specifically flag key frames for ease of use later
34962          if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
34963            currentFrame.keyFrame = true;
34964          }
34965          currentFrame.duration = currentNal.dts - currentFrame.dts;
34966          currentFrame.byteLength += currentNal.data.byteLength;
34967          currentFrame.push(currentNal);
34968        }
34969      } // For the last frame, use the duration of the previous frame if we
34970      // have nothing better to go on
34971
34972      if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
34973        currentFrame.duration = frames[frames.length - 1].duration;
34974      } // Push the final frame
34975      // TODO added for LHLS, make sure this is OK
34976
34977      frames.byteLength += currentFrame.byteLength;
34978      frames.nalCount += currentFrame.length;
34979      frames.duration += currentFrame.duration;
34980      frames.push(currentFrame);
34981      return frames;
34982    }; // Convert an array of frames into an array of Gop with each Gop being composed
34983    // of the frames that make up that Gop
34984    // Also keep track of cummulative data about the Gop from the frames such as the
34985    // Gop duration, starting pts, etc.
34986
34987    var groupFramesIntoGops = function (frames) {
34988      var i,
34989        currentFrame,
34990        currentGop = [],
34991        gops = []; // We must pre-set some of the values on the Gop since we
34992      // keep running totals of these values
34993
34994      currentGop.byteLength = 0;
34995      currentGop.nalCount = 0;
34996      currentGop.duration = 0;
34997      currentGop.pts = frames[0].pts;
34998      currentGop.dts = frames[0].dts; // store some metadata about all the Gops
34999
35000      gops.byteLength = 0;
35001      gops.nalCount = 0;
35002      gops.duration = 0;
35003      gops.pts = frames[0].pts;
35004      gops.dts = frames[0].dts;
35005      for (i = 0; i < frames.length; i++) {
35006        currentFrame = frames[i];
35007        if (currentFrame.keyFrame) {
35008          // Since the very first frame is expected to be an keyframe
35009          // only push to the gops array when currentGop is not empty
35010          if (currentGop.length) {
35011            gops.push(currentGop);
35012            gops.byteLength += currentGop.byteLength;
35013            gops.nalCount += currentGop.nalCount;
35014            gops.duration += currentGop.duration;
35015          }
35016          currentGop = [currentFrame];
35017          currentGop.nalCount = currentFrame.length;
35018          currentGop.byteLength = currentFrame.byteLength;
35019          currentGop.pts = currentFrame.pts;
35020          currentGop.dts = currentFrame.dts;
35021          currentGop.duration = currentFrame.duration;
35022        } else {
35023          currentGop.duration += currentFrame.duration;
35024          currentGop.nalCount += currentFrame.length;
35025          currentGop.byteLength += currentFrame.byteLength;
35026          currentGop.push(currentFrame);
35027        }
35028      }
35029      if (gops.length && currentGop.duration <= 0) {
35030        currentGop.duration = gops[gops.length - 1].duration;
35031      }
35032      gops.byteLength += currentGop.byteLength;
35033      gops.nalCount += currentGop.nalCount;
35034      gops.duration += currentGop.duration; // push the final Gop
35035
35036      gops.push(currentGop);
35037      return gops;
35038    };
35039    /*
35040     * Search for the first keyframe in the GOPs and throw away all frames
35041     * until that keyframe. Then extend the duration of the pulled keyframe
35042     * and pull the PTS and DTS of the keyframe so that it covers the time
35043     * range of the frames that were disposed.
35044     *
35045     * @param {Array} gops video GOPs
35046     * @returns {Array} modified video GOPs
35047     */
35048
35049    var extendFirstKeyFrame = function (gops) {
35050      var currentGop;
35051      if (!gops[0][0].keyFrame && gops.length > 1) {
35052        // Remove the first GOP
35053        currentGop = gops.shift();
35054        gops.byteLength -= currentGop.byteLength;
35055        gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
35056        // first gop to cover the time period of the
35057        // frames we just removed
35058
35059        gops[0][0].dts = currentGop.dts;
35060        gops[0][0].pts = currentGop.pts;
35061        gops[0][0].duration += currentGop.duration;
35062      }
35063      return gops;
35064    };
35065    /**
35066     * Default sample object
35067     * see ISO/IEC 14496-12:2012, section 8.6.4.3
35068     */
35069
35070    var createDefaultSample = function () {
35071      return {
35072        size: 0,
35073        flags: {
35074          isLeading: 0,
35075          dependsOn: 1,
35076          isDependedOn: 0,
35077          hasRedundancy: 0,
35078          degradationPriority: 0,
35079          isNonSyncSample: 1,
35080        },
35081      };
35082    };
35083    /*
35084     * Collates information from a video frame into an object for eventual
35085     * entry into an MP4 sample table.
35086     *
35087     * @param {Object} frame the video frame
35088     * @param {Number} dataOffset the byte offset to position the sample
35089     * @return {Object} object containing sample table info for a frame
35090     */
35091
35092    var sampleForFrame = function (frame, dataOffset) {
35093      var sample = createDefaultSample();
35094      sample.dataOffset = dataOffset;
35095      sample.compositionTimeOffset = frame.pts - frame.dts;
35096      sample.duration = frame.duration;
35097      sample.size = 4 * frame.length; // Space for nal unit size
35098
35099      sample.size += frame.byteLength;
35100      if (frame.keyFrame) {
35101        sample.flags.dependsOn = 2;
35102        sample.flags.isNonSyncSample = 0;
35103      }
35104      return sample;
35105    }; // generate the track's sample table from an array of gops
35106
35107    var generateSampleTable$1 = function (gops, baseDataOffset) {
35108      var h,
35109        i,
35110        sample,
35111        currentGop,
35112        currentFrame,
35113        dataOffset = baseDataOffset || 0,
35114        samples = [];
35115      for (h = 0; h < gops.length; h++) {
35116        currentGop = gops[h];
35117        for (i = 0; i < currentGop.length; i++) {
35118          currentFrame = currentGop[i];
35119          sample = sampleForFrame(currentFrame, dataOffset);
35120          dataOffset += sample.size;
35121          samples.push(sample);
35122        }
35123      }
35124      return samples;
35125    }; // generate the track's raw mdat data from an array of gops
35126
35127    var concatenateNalData = function (gops) {
35128      var h,
35129        i,
35130        j,
35131        currentGop,
35132        currentFrame,
35133        currentNal,
35134        dataOffset = 0,
35135        nalsByteLength = gops.byteLength,
35136        numberOfNals = gops.nalCount,
35137        totalByteLength = nalsByteLength + 4 * numberOfNals,
35138        data = new Uint8Array(totalByteLength),
35139        view = new DataView(data.buffer); // For each Gop..
35140
35141      for (h = 0; h < gops.length; h++) {
35142        currentGop = gops[h]; // For each Frame..
35143
35144        for (i = 0; i < currentGop.length; i++) {
35145          currentFrame = currentGop[i]; // For each NAL..
35146
35147          for (j = 0; j < currentFrame.length; j++) {
35148            currentNal = currentFrame[j];
35149            view.setUint32(dataOffset, currentNal.data.byteLength);
35150            dataOffset += 4;
35151            data.set(currentNal.data, dataOffset);
35152            dataOffset += currentNal.data.byteLength;
35153          }
35154        }
35155      }
35156      return data;
35157    }; // generate the track's sample table from a frame
35158
35159    var generateSampleTableForFrame = function (frame, baseDataOffset) {
35160      var sample,
35161        dataOffset = baseDataOffset || 0,
35162        samples = [];
35163      sample = sampleForFrame(frame, dataOffset);
35164      samples.push(sample);
35165      return samples;
35166    }; // generate the track's raw mdat data from a frame
35167
35168    var concatenateNalDataForFrame = function (frame) {
35169      var i,
35170        currentNal,
35171        dataOffset = 0,
35172        nalsByteLength = frame.byteLength,
35173        numberOfNals = frame.length,
35174        totalByteLength = nalsByteLength + 4 * numberOfNals,
35175        data = new Uint8Array(totalByteLength),
35176        view = new DataView(data.buffer); // For each NAL..
35177
35178      for (i = 0; i < frame.length; i++) {
35179        currentNal = frame[i];
35180        view.setUint32(dataOffset, currentNal.data.byteLength);
35181        dataOffset += 4;
35182        data.set(currentNal.data, dataOffset);
35183        dataOffset += currentNal.data.byteLength;
35184      }
35185      return data;
35186    };
35187    var frameUtils$1 = {
35188      groupNalsIntoFrames: groupNalsIntoFrames,
35189      groupFramesIntoGops: groupFramesIntoGops,
35190      extendFirstKeyFrame: extendFirstKeyFrame,
35191      generateSampleTable: generateSampleTable$1,
35192      concatenateNalData: concatenateNalData,
35193      generateSampleTableForFrame: generateSampleTableForFrame,
35194      concatenateNalDataForFrame: concatenateNalDataForFrame,
35195    };
35196    /**
35197     * mux.js
35198     *
35199     * Copyright (c) Brightcove
35200     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35201     */
35202
35203    var highPrefix = [33, 16, 5, 32, 164, 27];
35204    var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
35205    var zeroFill = function (count) {
35206      var a = [];
35207      while (count--) {
35208        a.push(0);
35209      }
35210      return a;
35211    };
35212    var makeTable = function (metaTable) {
35213      return Object.keys(metaTable).reduce(function (obj, key) {
35214        obj[key] = new Uint8Array(
35215          metaTable[key].reduce(function (arr, part) {
35216            return arr.concat(part);
35217          }, [])
35218        );
35219        return obj;
35220      }, {});
35221    };
35222    var silence;
35223    var silence_1 = function () {
35224      if (!silence) {
35225        // Frames-of-silence to use for filling in missing AAC frames
35226        var coneOfSilence = {
35227          96000: [highPrefix, [227, 64], zeroFill(154), [56]],
35228          88200: [highPrefix, [231], zeroFill(170), [56]],
35229          64000: [highPrefix, [248, 192], zeroFill(240), [56]],
35230          48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
35231          44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
35232          32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
35233          24000: [
35234            highPrefix,
35235            [255, 192],
35236            zeroFill(268),
35237            [55, 255, 128],
35238            zeroFill(268),
35239            [111, 112],
35240            zeroFill(126),
35241            [224],
35242          ],
35243          16000: [
35244            highPrefix,
35245            [255, 192],
35246            zeroFill(268),
35247            [55, 255, 128],
35248            zeroFill(268),
35249            [111, 255],
35250            zeroFill(269),
35251            [223, 108],
35252            zeroFill(195),
35253            [1, 192],
35254          ],
35255          12000: [
35256            lowPrefix,
35257            zeroFill(268),
35258            [3, 127, 248],
35259            zeroFill(268),
35260            [6, 255, 240],
35261            zeroFill(268),
35262            [13, 255, 224],
35263            zeroFill(268),
35264            [27, 253, 128],
35265            zeroFill(259),
35266            [56],
35267          ],
35268          11025: [
35269            lowPrefix,
35270            zeroFill(268),
35271            [3, 127, 248],
35272            zeroFill(268),
35273            [6, 255, 240],
35274            zeroFill(268),
35275            [13, 255, 224],
35276            zeroFill(268),
35277            [27, 255, 192],
35278            zeroFill(268),
35279            [55, 175, 128],
35280            zeroFill(108),
35281            [112],
35282          ],
35283          8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]],
35284        };
35285        silence = makeTable(coneOfSilence);
35286      }
35287      return silence;
35288    };
35289    /**
35290     * mux.js
35291     *
35292     * Copyright (c) Brightcove
35293     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35294     */
35295
35296    var ONE_SECOND_IN_TS$4 = 90000,
35297      // 90kHz clock
35298      secondsToVideoTs,
35299      secondsToAudioTs,
35300      videoTsToSeconds,
35301      audioTsToSeconds,
35302      audioTsToVideoTs,
35303      videoTsToAudioTs,
35304      metadataTsToSeconds;
35305    secondsToVideoTs = function (seconds) {
35306      return seconds * ONE_SECOND_IN_TS$4;
35307    };
35308    secondsToAudioTs = function (seconds, sampleRate) {
35309      return seconds * sampleRate;
35310    };
35311    videoTsToSeconds = function (timestamp) {
35312      return timestamp / ONE_SECOND_IN_TS$4;
35313    };
35314    audioTsToSeconds = function (timestamp, sampleRate) {
35315      return timestamp / sampleRate;
35316    };
35317    audioTsToVideoTs = function (timestamp, sampleRate) {
35318      return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
35319    };
35320    videoTsToAudioTs = function (timestamp, sampleRate) {
35321      return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
35322    };
35323    /**
35324     * Adjust ID3 tag or caption timing information by the timeline pts values
35325     * (if keepOriginalTimestamps is false) and convert to seconds
35326     */
35327
35328    metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
35329      return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
35330    };
35331    var clock$2 = {
35332      ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
35333      secondsToVideoTs: secondsToVideoTs,
35334      secondsToAudioTs: secondsToAudioTs,
35335      videoTsToSeconds: videoTsToSeconds,
35336      audioTsToSeconds: audioTsToSeconds,
35337      audioTsToVideoTs: audioTsToVideoTs,
35338      videoTsToAudioTs: videoTsToAudioTs,
35339      metadataTsToSeconds: metadataTsToSeconds,
35340    };
35341    /**
35342     * mux.js
35343     *
35344     * Copyright (c) Brightcove
35345     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35346     */
35347
35348    var coneOfSilence = silence_1;
35349    var clock$1 = clock$2;
35350    /**
35351     * Sum the `byteLength` properties of the data in each AAC frame
35352     */
35353
35354    var sumFrameByteLengths = function (array) {
35355      var i,
35356        currentObj,
35357        sum = 0; // sum the byteLength's all each nal unit in the frame
35358
35359      for (i = 0; i < array.length; i++) {
35360        currentObj = array[i];
35361        sum += currentObj.data.byteLength;
35362      }
35363      return sum;
35364    }; // Possibly pad (prefix) the audio track with silence if appending this track
35365    // would lead to the introduction of a gap in the audio buffer
35366
35367    var prefixWithSilence = function (track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
35368      var baseMediaDecodeTimeTs,
35369        frameDuration = 0,
35370        audioGapDuration = 0,
35371        audioFillFrameCount = 0,
35372        audioFillDuration = 0,
35373        silentFrame,
35374        i,
35375        firstFrame;
35376      if (!frames.length) {
35377        return;
35378      }
35379      baseMediaDecodeTimeTs = clock$1.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
35380
35381      frameDuration = Math.ceil(clock$1.ONE_SECOND_IN_TS / (track.samplerate / 1024));
35382      if (audioAppendStartTs && videoBaseMediaDecodeTime) {
35383        // insert the shortest possible amount (audio gap or audio to video gap)
35384        audioGapDuration =
35385          baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
35386
35387        audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
35388        audioFillDuration = audioFillFrameCount * frameDuration;
35389      } // don't attempt to fill gaps smaller than a single frame or larger
35390      // than a half second
35391
35392      if (audioFillFrameCount < 1 || audioFillDuration > clock$1.ONE_SECOND_IN_TS / 2) {
35393        return;
35394      }
35395      silentFrame = coneOfSilence()[track.samplerate];
35396      if (!silentFrame) {
35397        // we don't have a silent frame pregenerated for the sample rate, so use a frame
35398        // from the content instead
35399        silentFrame = frames[0].data;
35400      }
35401      for (i = 0; i < audioFillFrameCount; i++) {
35402        firstFrame = frames[0];
35403        frames.splice(0, 0, {
35404          data: silentFrame,
35405          dts: firstFrame.dts - frameDuration,
35406          pts: firstFrame.pts - frameDuration,
35407        });
35408      }
35409      track.baseMediaDecodeTime -= Math.floor(
35410        clock$1.videoTsToAudioTs(audioFillDuration, track.samplerate)
35411      );
35412      return audioFillDuration;
35413    }; // If the audio segment extends before the earliest allowed dts
35414    // value, remove AAC frames until starts at or after the earliest
35415    // allowed DTS so that we don't end up with a negative baseMedia-
35416    // DecodeTime for the audio track
35417
35418    var trimAdtsFramesByEarliestDts = function (adtsFrames, track, earliestAllowedDts) {
35419      if (track.minSegmentDts >= earliestAllowedDts) {
35420        return adtsFrames;
35421      } // We will need to recalculate the earliest segment Dts
35422
35423      track.minSegmentDts = Infinity;
35424      return adtsFrames.filter(function (currentFrame) {
35425        // If this is an allowed frame, keep it and record it's Dts
35426        if (currentFrame.dts >= earliestAllowedDts) {
35427          track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
35428          track.minSegmentPts = track.minSegmentDts;
35429          return true;
35430        } // Otherwise, discard it
35431
35432        return false;
35433      });
35434    }; // generate the track's raw mdat data from an array of frames
35435
35436    var generateSampleTable = function (frames) {
35437      var i,
35438        currentFrame,
35439        samples = [];
35440      for (i = 0; i < frames.length; i++) {
35441        currentFrame = frames[i];
35442        samples.push({
35443          size: currentFrame.data.byteLength,
35444          duration: 1024, // For AAC audio, all samples contain 1024 samples
35445        });
35446      }
35447      return samples;
35448    }; // generate the track's sample table from an array of frames
35449
35450    var concatenateFrameData = function (frames) {
35451      var i,
35452        currentFrame,
35453        dataOffset = 0,
35454        data = new Uint8Array(sumFrameByteLengths(frames));
35455      for (i = 0; i < frames.length; i++) {
35456        currentFrame = frames[i];
35457        data.set(currentFrame.data, dataOffset);
35458        dataOffset += currentFrame.data.byteLength;
35459      }
35460      return data;
35461    };
35462    var audioFrameUtils$1 = {
35463      prefixWithSilence: prefixWithSilence,
35464      trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
35465      generateSampleTable: generateSampleTable,
35466      concatenateFrameData: concatenateFrameData,
35467    };
35468    /**
35469     * mux.js
35470     *
35471     * Copyright (c) Brightcove
35472     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35473     */
35474
35475    var ONE_SECOND_IN_TS$3 = clock$2.ONE_SECOND_IN_TS;
35476    /**
35477     * Store information about the start and end of the track and the
35478     * duration for each frame/sample we process in order to calculate
35479     * the baseMediaDecodeTime
35480     */
35481
35482    var collectDtsInfo = function (track, data) {
35483      if (typeof data.pts === 'number') {
35484        if (track.timelineStartInfo.pts === undefined) {
35485          track.timelineStartInfo.pts = data.pts;
35486        }
35487        if (track.minSegmentPts === undefined) {
35488          track.minSegmentPts = data.pts;
35489        } else {
35490          track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
35491        }
35492        if (track.maxSegmentPts === undefined) {
35493          track.maxSegmentPts = data.pts;
35494        } else {
35495          track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
35496        }
35497      }
35498      if (typeof data.dts === 'number') {
35499        if (track.timelineStartInfo.dts === undefined) {
35500          track.timelineStartInfo.dts = data.dts;
35501        }
35502        if (track.minSegmentDts === undefined) {
35503          track.minSegmentDts = data.dts;
35504        } else {
35505          track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
35506        }
35507        if (track.maxSegmentDts === undefined) {
35508          track.maxSegmentDts = data.dts;
35509        } else {
35510          track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
35511        }
35512      }
35513    };
35514    /**
35515     * Clear values used to calculate the baseMediaDecodeTime between
35516     * tracks
35517     */
35518
35519    var clearDtsInfo = function (track) {
35520      delete track.minSegmentDts;
35521      delete track.maxSegmentDts;
35522      delete track.minSegmentPts;
35523      delete track.maxSegmentPts;
35524    };
35525    /**
35526     * Calculate the track's baseMediaDecodeTime based on the earliest
35527     * DTS the transmuxer has ever seen and the minimum DTS for the
35528     * current track
35529     * @param track {object} track metadata configuration
35530     * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
35531     *        in the source; false to adjust the first segment to start at 0.
35532     */
35533
35534    var calculateTrackBaseMediaDecodeTime = function (track, keepOriginalTimestamps) {
35535      var baseMediaDecodeTime,
35536        scale,
35537        minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
35538
35539      if (!keepOriginalTimestamps) {
35540        minSegmentDts -= track.timelineStartInfo.dts;
35541      } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
35542      // we want the start of the first segment to be placed
35543
35544      baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
35545
35546      baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
35547
35548      baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
35549      if (track.type === 'audio') {
35550        // Audio has a different clock equal to the sampling_rate so we need to
35551        // scale the PTS values into the clock rate of the track
35552        scale = track.samplerate / ONE_SECOND_IN_TS$3;
35553        baseMediaDecodeTime *= scale;
35554        baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
35555      }
35556      return baseMediaDecodeTime;
35557    };
35558    var trackDecodeInfo$1 = {
35559      clearDtsInfo: clearDtsInfo,
35560      calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
35561      collectDtsInfo: collectDtsInfo,
35562    };
35563    /**
35564     * mux.js
35565     *
35566     * Copyright (c) Brightcove
35567     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35568     *
35569     * Reads in-band caption information from a video elementary
35570     * stream. Captions must follow the CEA-708 standard for injection
35571     * into an MPEG-2 transport streams.
35572     * @see https://en.wikipedia.org/wiki/CEA-708
35573     * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
35574     */
35575    // payload type field to indicate how they are to be
35576    // interpreted. CEAS-708 caption content is always transmitted with
35577    // payload type 0x04.
35578
35579    var USER_DATA_REGISTERED_ITU_T_T35 = 4,
35580      RBSP_TRAILING_BITS = 128;
35581    /**
35582     * Parse a supplemental enhancement information (SEI) NAL unit.
35583     * Stops parsing once a message of type ITU T T35 has been found.
35584     *
35585     * @param bytes {Uint8Array} the bytes of a SEI NAL unit
35586     * @return {object} the parsed SEI payload
35587     * @see Rec. ITU-T H.264, 7.3.2.3.1
35588     */
35589
35590    var parseSei = function (bytes) {
35591      var i = 0,
35592        result = {
35593          payloadType: -1,
35594          payloadSize: 0,
35595        },
35596        payloadType = 0,
35597        payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
35598
35599      while (i < bytes.byteLength) {
35600        // stop once we have hit the end of the sei_rbsp
35601        if (bytes[i] === RBSP_TRAILING_BITS) {
35602          break;
35603        } // Parse payload type
35604
35605        while (bytes[i] === 0xff) {
35606          payloadType += 255;
35607          i++;
35608        }
35609        payloadType += bytes[i++]; // Parse payload size
35610
35611        while (bytes[i] === 0xff) {
35612          payloadSize += 255;
35613          i++;
35614        }
35615        payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
35616        // there can only ever be one caption message in a frame's sei
35617
35618        if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
35619          var userIdentifier = String.fromCharCode(
35620            bytes[i + 3],
35621            bytes[i + 4],
35622            bytes[i + 5],
35623            bytes[i + 6]
35624          );
35625          if (userIdentifier === 'GA94') {
35626            result.payloadType = payloadType;
35627            result.payloadSize = payloadSize;
35628            result.payload = bytes.subarray(i, i + payloadSize);
35629            break;
35630          } else {
35631            result.payload = void 0;
35632          }
35633        } // skip the payload and parse the next message
35634
35635        i += payloadSize;
35636        payloadType = 0;
35637        payloadSize = 0;
35638      }
35639      return result;
35640    }; // see ANSI/SCTE 128-1 (2013), section 8.1
35641
35642    var parseUserData = function (sei) {
35643      // itu_t_t35_contry_code must be 181 (United States) for
35644      // captions
35645      if (sei.payload[0] !== 181) {
35646        return null;
35647      } // itu_t_t35_provider_code should be 49 (ATSC) for captions
35648
35649      if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) {
35650        return null;
35651      } // the user_identifier should be "GA94" to indicate ATSC1 data
35652
35653      if (
35654        String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !==
35655        'GA94'
35656      ) {
35657        return null;
35658      } // finally, user_data_type_code should be 0x03 for caption data
35659
35660      if (sei.payload[7] !== 0x03) {
35661        return null;
35662      } // return the user_data_type_structure and strip the trailing
35663      // marker bits
35664
35665      return sei.payload.subarray(8, sei.payload.length - 1);
35666    }; // see CEA-708-D, section 4.4
35667
35668    var parseCaptionPackets = function (pts, userData) {
35669      var results = [],
35670        i,
35671        count,
35672        offset,
35673        data; // if this is just filler, return immediately
35674
35675      if (!(userData[0] & 0x40)) {
35676        return results;
35677      } // parse out the cc_data_1 and cc_data_2 fields
35678
35679      count = userData[0] & 0x1f;
35680      for (i = 0; i < count; i++) {
35681        offset = i * 3;
35682        data = {
35683          type: userData[offset + 2] & 0x03,
35684          pts: pts,
35685        }; // capture cc data when cc_valid is 1
35686
35687        if (userData[offset + 2] & 0x04) {
35688          data.ccData = (userData[offset + 3] << 8) | userData[offset + 4];
35689          results.push(data);
35690        }
35691      }
35692      return results;
35693    };
35694    var discardEmulationPreventionBytes$1 = function (data) {
35695      var length = data.byteLength,
35696        emulationPreventionBytesPositions = [],
35697        i = 1,
35698        newLength,
35699        newData; // Find all `Emulation Prevention Bytes`
35700
35701      while (i < length - 2) {
35702        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
35703          emulationPreventionBytesPositions.push(i + 2);
35704          i += 2;
35705        } else {
35706          i++;
35707        }
35708      } // If no Emulation Prevention Bytes were found just return the original
35709      // array
35710
35711      if (emulationPreventionBytesPositions.length === 0) {
35712        return data;
35713      } // Create a new array to hold the NAL unit data
35714
35715      newLength = length - emulationPreventionBytesPositions.length;
35716      newData = new Uint8Array(newLength);
35717      var sourceIndex = 0;
35718      for (i = 0; i < newLength; sourceIndex++, i++) {
35719        if (sourceIndex === emulationPreventionBytesPositions[0]) {
35720          // Skip this byte
35721          sourceIndex++; // Remove this position index
35722
35723          emulationPreventionBytesPositions.shift();
35724        }
35725        newData[i] = data[sourceIndex];
35726      }
35727      return newData;
35728    }; // exports
35729
35730    var captionPacketParser = {
35731      parseSei: parseSei,
35732      parseUserData: parseUserData,
35733      parseCaptionPackets: parseCaptionPackets,
35734      discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
35735      USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35,
35736    };
35737    /**
35738     * mux.js
35739     *
35740     * Copyright (c) Brightcove
35741     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35742     *
35743     * Reads in-band caption information from a video elementary
35744     * stream. Captions must follow the CEA-708 standard for injection
35745     * into an MPEG-2 transport streams.
35746     * @see https://en.wikipedia.org/wiki/CEA-708
35747     * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
35748     */
35749    // Link To Transport
35750    // -----------------
35751
35752    var Stream$7 = stream;
35753    var cea708Parser = captionPacketParser;
35754    var CaptionStream$2 = function (options) {
35755      options = options || {};
35756      CaptionStream$2.prototype.init.call(this); // parse708captions flag, default to true
35757
35758      this.parse708captions_ =
35759        typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
35760      this.captionPackets_ = [];
35761      this.ccStreams_ = [
35762        new Cea608Stream(0, 0),
35763        // eslint-disable-line no-use-before-define
35764        new Cea608Stream(0, 1),
35765        // eslint-disable-line no-use-before-define
35766        new Cea608Stream(1, 0),
35767        // eslint-disable-line no-use-before-define
35768        new Cea608Stream(1, 1), // eslint-disable-line no-use-before-define
35769      ];
35770      if (this.parse708captions_) {
35771        this.cc708Stream_ = new Cea708Stream({
35772          captionServices: options.captionServices,
vendor: 5,343 bytes, lines 35772-35899
35772
35773        }); // eslint-disable-line no-use-before-define
35774      }
35775      this.reset(); // forward data and done events from CCs to this CaptionStream
35776
35777      this.ccStreams_.forEach(function (cc) {
35778        cc.on('data', this.trigger.bind(this, 'data'));
35779        cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
35780        cc.on('done', this.trigger.bind(this, 'done'));
35781      }, this);
35782      if (this.parse708captions_) {
35783        this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
35784        this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
35785        this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
35786      }
35787    };
35788    CaptionStream$2.prototype = new Stream$7();
35789    CaptionStream$2.prototype.push = function (event) {
35790      var sei, userData, newCaptionPackets; // only examine SEI NALs
35791
35792      if (event.nalUnitType !== 'sei_rbsp') {
35793        return;
35794      } // parse the sei
35795
35796      sei = cea708Parser.parseSei(event.escapedRBSP); // no payload data, skip
35797
35798      if (!sei.payload) {
35799        return;
35800      } // ignore everything but user_data_registered_itu_t_t35
35801
35802      if (sei.payloadType !== cea708Parser.USER_DATA_REGISTERED_ITU_T_T35) {
35803        return;
35804      } // parse out the user data payload
35805
35806      userData = cea708Parser.parseUserData(sei); // ignore unrecognized userData
35807
35808      if (!userData) {
35809        return;
35810      } // Sometimes, the same segment # will be downloaded twice. To stop the
35811      // caption data from being processed twice, we track the latest dts we've
35812      // received and ignore everything with a dts before that. However, since
35813      // data for a specific dts can be split across packets on either side of
35814      // a segment boundary, we need to make sure we *don't* ignore the packets
35815      // from the *next* segment that have dts === this.latestDts_. By constantly
35816      // tracking the number of packets received with dts === this.latestDts_, we
35817      // know how many should be ignored once we start receiving duplicates.
35818
35819      if (event.dts < this.latestDts_) {
35820        // We've started getting older data, so set the flag.
35821        this.ignoreNextEqualDts_ = true;
35822        return;
35823      } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
35824        this.numSameDts_--;
35825        if (!this.numSameDts_) {
35826          // We've received the last duplicate packet, time to start processing again
35827          this.ignoreNextEqualDts_ = false;
35828        }
35829        return;
35830      } // parse out CC data packets and save them for later
35831
35832      newCaptionPackets = cea708Parser.parseCaptionPackets(event.pts, userData);
35833      this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
35834      if (this.latestDts_ !== event.dts) {
35835        this.numSameDts_ = 0;
35836      }
35837      this.numSameDts_++;
35838      this.latestDts_ = event.dts;
35839    };
35840    CaptionStream$2.prototype.flushCCStreams = function (flushType) {
35841      this.ccStreams_.forEach(function (cc) {
35842        return flushType === 'flush' ? cc.flush() : cc.partialFlush();
35843      }, this);
35844    };
35845    CaptionStream$2.prototype.flushStream = function (flushType) {
35846      // make sure we actually parsed captions before proceeding
35847      if (!this.captionPackets_.length) {
35848        this.flushCCStreams(flushType);
35849        return;
35850      } // In Chrome, the Array#sort function is not stable so add a
35851      // presortIndex that we can use to ensure we get a stable-sort
35852
35853      this.captionPackets_.forEach(function (elem, idx) {
35854        elem.presortIndex = idx;
35855      }); // sort caption byte-pairs based on their PTS values
35856
35857      this.captionPackets_.sort(function (a, b) {
35858        if (a.pts === b.pts) {
35859          return a.presortIndex - b.presortIndex;
35860        }
35861        return a.pts - b.pts;
35862      });
35863      this.captionPackets_.forEach(function (packet) {
35864        if (packet.type < 2) {
35865          // Dispatch packet to the right Cea608Stream
35866          this.dispatchCea608Packet(packet);
35867        } else {
35868          // Dispatch packet to the Cea708Stream
35869          this.dispatchCea708Packet(packet);
35870        }
35871      }, this);
35872      this.captionPackets_.length = 0;
35873      this.flushCCStreams(flushType);
35874    };
35875    CaptionStream$2.prototype.flush = function () {
35876      return this.flushStream('flush');
35877    }; // Only called if handling partial data
35878
35879    CaptionStream$2.prototype.partialFlush = function () {
35880      return this.flushStream('partialFlush');
35881    };
35882    CaptionStream$2.prototype.reset = function () {
35883      this.latestDts_ = null;
35884      this.ignoreNextEqualDts_ = false;
35885      this.numSameDts_ = 0;
35886      this.activeCea608Channel_ = [null, null];
35887      this.ccStreams_.forEach(function (ccStream) {
35888        ccStream.reset();
35889      });
35890    }; // From the CEA-608 spec:
35891
35892    /*
35893     * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
35894     * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
35895     * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
35896     * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
35897     * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
35898     * to switch to captioning or Text.
35899    
35899 */
35900    // With that in mind, we ignore any data between an XDS control code and a
35901    // subsequent closed-captioning control code.
35902
35903    CaptionStream$2.prototype.dispatchCea608Packet = function (packet) {
35904      // NOTE: packet.type is the CEA608 field
35905      if (this.setsTextOrXDSActive(packet)) {
35906        this.activeCea608Channel_[packet.type] = null;
35907      } else if (this.setsChannel1Active(packet)) {
35908        this.activeCea608Channel_[packet.type] = 0;
35909      } else if (this.setsChannel2Active(packet)) {
35910        this.activeCea608Channel_[packet.type] = 1;
35911      }
35912      if (this.activeCea608Channel_[packet.type] === null) {
35913        // If we haven't received anything to set the active channel, or the
35914        // packets are Text/XDS data, discard the data; we don't want jumbled
35915        // captions
35916        return;
35917      }
35918      this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
35919    };
35920    CaptionStream$2.prototype.setsChannel1Active = function (packet) {
35921      return (packet.ccData & 0x7800) === 0x1000;
35922    };
35923    CaptionStream$2.prototype.setsChannel2Active = function (packet) {
35924      return (packet.ccData & 0x7800) === 0x1800;
35925    };
35926    CaptionStream$2.prototype.setsTextOrXDSActive = function (packet) {
35927      return (
35928        (packet.ccData & 0x7100) === 0x0100 ||
35929        (packet.ccData & 0x78fe) === 0x102a ||
35930        (packet.ccData & 0x78fe) === 0x182a
35931      );
35932    };
35933    CaptionStream$2.prototype.dispatchCea708Packet = function (packet) {
35934      if (this.parse708captions_) {
35935        this.cc708Stream_.push(packet);
35936      }
35937    }; // ----------------------
35938    // Session to Application
35939    // ----------------------
35940    // This hash maps special and extended character codes to their
35941    // proper Unicode equivalent. The first one-byte key is just a
35942    // non-standard character code. The two-byte keys that follow are
35943    // the extended CEA708 character codes, along with the preceding
35944    // 0x10 extended character byte to distinguish these codes from
35945    // non-extended character codes. Every CEA708 character code that
35946    // is not in this object maps directly to a standard unicode
35947    // character code.
35948    // The transparent space and non-breaking transparent space are
35949    // technically not fully supported since there is no code to
35950    // make them transparent, so they have normal non-transparent
35951    // stand-ins.
35952    // The special closed caption (CC) character isn't a standard
35953    // unicode character, so a fairly similar unicode character was
35954    // chosen in it's place.
35955
35956    var CHARACTER_TRANSLATION_708 = {
35957      0x7f: 0x266a,
35958      // ♪
35959      0x1020: 0x20,
35960      // Transparent Space
35961      0x1021: 0xa0,
35962      // Nob-breaking Transparent Space
35963      0x1025: 0x2026,
35964      // …
35965      0x102a: 0x0160,
35966      // Å 
35967      0x102c: 0x0152,
35968      // Œ
35969      0x1030: 0x2588,
35970      // █
35971      0x1031: 0x2018,
35972      // ‘
35973      0x1032: 0x2019,
35974      // ’
35975      0x1033: 0x201c,
35976      // “
35977      0x1034: 0x201d,
35978      // ”
35979      0x1035: 0x2022,
35980      // •
35981      0x1039: 0x2122,
35982      // ™
35983      0x103a: 0x0161,
35984      // Å¡
35985      0x103c: 0x0153,
35986      // œ
35987      0x103d: 0x2120,
35988      // ℠
35989      0x103f: 0x0178,
35990      // Ÿ
35991      0x1076: 0x215b,
35992      // ⅛
35993      0x1077: 0x215c,
35994      // ⅜
35995      0x1078: 0x215d,
35996      // ⅝
35997      0x1079: 0x215e,
35998      // ⅞
35999      0x107a: 0x23d0,
36000      // ⏐
36001      0x107b: 0x23a4,
36002      // ⎤
36003      0x107c: 0x23a3,
36004      // ⎣
36005      0x107d: 0x23af,
36006      // ⎯
36007      0x107e: 0x23a6,
36008      // ⎦
36009      0x107f: 0x23a1,
36010      // ⎡
36011      0x10a0: 0x3138, // ㄸ (CC char)
36012    };
36013    var get708CharFromCode = function (code) {
36014      var newCode = CHARACTER_TRANSLATION_708[code] || code;
36015      if (code & 0x1000 && code === newCode) {
36016        // Invalid extended code
36017        return '';
36018      }
36019      return String.fromCharCode(newCode);
36020    };
36021    var within708TextBlock = function (b) {
36022      return (0x20 <= b && b <= 0x7f) || (0xa0 <= b && b <= 0xff);
36023    };
36024    var Cea708Window = function (windowNum) {
36025      this.windowNum = windowNum;
36026      this.reset();
36027    };
36028    Cea708Window.prototype.reset = function () {
36029      this.clearText();
36030      this.pendingNewLine = false;
vendor: 26,050 bytes, lines 36031-36840
36031      this.winAttr = {};
36032      this.penAttr = {};
36033      this.penLoc = {};
36034      this.penColor = {}; // These default values are arbitrary,
36035      // defineWindow will usually override them
36036
36037      this.visible = 0;
36038      this.rowLock = 0;
36039      this.columnLock = 0;
36040      this.priority = 0;
36041      this.relativePositioning = 0;
36042      this.anchorVertical = 0;
36043      this.anchorHorizontal = 0;
36044      this.anchorPoint = 0;
36045      this.rowCount = 1;
36046      this.virtualRowCount = this.rowCount + 1;
36047      this.columnCount = 41;
36048      this.windowStyle = 0;
36049      this.penStyle = 0;
36050    };
36051    Cea708Window.prototype.getText = function () {
36052      return this.rows.join('\n');
36053    };
36054    Cea708Window.prototype.clearText = function () {
36055      this.rows = [''];
36056      this.rowIdx = 0;
36057    };
36058    Cea708Window.prototype.newLine = function (pts) {
36059      if (
36060        this.rows.length >= this.virtualRowCount &&
36061        typeof this.beforeRowOverflow === 'function'
36062      ) {
36063        this.beforeRowOverflow(pts);
36064      }
36065      if (this.rows.length > 0) {
36066        this.rows.push('');
36067        this.rowIdx++;
36068      } // Show all virtual rows since there's no visible scrolling
36069
36070      while (this.rows.length > this.virtualRowCount) {
36071        this.rows.shift();
36072        this.rowIdx--;
36073      }
36074    };
36075    Cea708Window.prototype.isEmpty = function () {
36076      if (this.rows.length === 0) {
36077        return true;
36078      } else if (this.rows.length === 1) {
36079        return this.rows[0] === '';
36080      }
36081      return false;
36082    };
36083    Cea708Window.prototype.addText = function (text) {
36084      this.rows[this.rowIdx] += text;
36085    };
36086    Cea708Window.prototype.backspace = function () {
36087      if (!this.isEmpty()) {
36088        var row = this.rows[this.rowIdx];
36089        this.rows[this.rowIdx] = row.substr(0, row.length - 1);
36090      }
36091    };
36092    var Cea708Service = function (serviceNum, encoding, stream) {
36093      this.serviceNum = serviceNum;
36094      this.text = '';
36095      this.currentWindow = new Cea708Window(-1);
36096      this.windows = [];
36097      this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
36098
36099      if (typeof encoding === 'string') {
36100        this.createTextDecoder(encoding);
36101      }
36102    };
36103    /**
36104     * Initialize service windows
36105     * Must be run before service use
36106     *
36107     * @param  {Integer}  pts               PTS value
36108     * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
36109     */
36110
36111    Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
36112      this.startPts = pts;
36113      for (var win = 0; win < 8; win++) {
36114        this.windows[win] = new Cea708Window(win);
36115        if (typeof beforeRowOverflow === 'function') {
36116          this.windows[win].beforeRowOverflow = beforeRowOverflow;
36117        }
36118      }
36119    };
36120    /**
36121     * Set current window of service to be affected by commands
36122     *
36123     * @param  {Integer} windowNum Window number
36124     */
36125
36126    Cea708Service.prototype.setCurrentWindow = function (windowNum) {
36127      this.currentWindow = this.windows[windowNum];
36128    };
36129    /**
36130     * Try to create a TextDecoder if it is natively supported
36131     */
36132
36133    Cea708Service.prototype.createTextDecoder = function (encoding) {
36134      if (typeof TextDecoder === 'undefined') {
36135        this.stream.trigger('log', {
36136          level: 'warn',
36137          message: 'The `encoding` option is unsupported without TextDecoder support',
36138        });
36139      } else {
36140        try {
36141          this.textDecoder_ = new TextDecoder(encoding);
36142        } catch (error) {
36143          this.stream.trigger('log', {
36144            level: 'warn',
36145            message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error,
36146          });
36147        }
36148      }
36149    };
36150    var Cea708Stream = function (options) {
36151      options = options || {};
36152      Cea708Stream.prototype.init.call(this);
36153      var self = this;
36154      var captionServices = options.captionServices || {};
36155      var captionServiceEncodings = {};
36156      var serviceProps; // Get service encodings from captionServices option block
36157
36158      Object.keys(captionServices).forEach((serviceName) => {
36159        serviceProps = captionServices[serviceName];
36160        if (/^SERVICE/.test(serviceName)) {
36161          captionServiceEncodings[serviceName] = serviceProps.encoding;
36162        }
36163      });
36164      this.serviceEncodings = captionServiceEncodings;
36165      this.current708Packet = null;
36166      this.services = {};
36167      this.push = function (packet) {
36168        if (packet.type === 3) {
36169          // 708 packet start
36170          self.new708Packet();
36171          self.add708Bytes(packet);
36172        } else {
36173          if (self.current708Packet === null) {
36174            // This should only happen at the start of a file if there's no packet start.
36175            self.new708Packet();
36176          }
36177          self.add708Bytes(packet);
36178        }
36179      };
36180    };
36181    Cea708Stream.prototype = new Stream$7();
36182    /**
36183     * Push current 708 packet, create new 708 packet.
36184     */
36185
36186    Cea708Stream.prototype.new708Packet = function () {
36187      if (this.current708Packet !== null) {
36188        this.push708Packet();
36189      }
36190      this.current708Packet = {
36191        data: [],
36192        ptsVals: [],
36193      };
36194    };
36195    /**
36196     * Add pts and both bytes from packet into current 708 packet.
36197     */
36198
36199    Cea708Stream.prototype.add708Bytes = function (packet) {
36200      var data = packet.ccData;
36201      var byte0 = data >>> 8;
36202      var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
36203      // that service blocks will always line up with byte pairs.
36204
36205      this.current708Packet.ptsVals.push(packet.pts);
36206      this.current708Packet.data.push(byte0);
36207      this.current708Packet.data.push(byte1);
36208    };
36209    /**
36210     * Parse completed 708 packet into service blocks and push each service block.
36211     */
36212
36213    Cea708Stream.prototype.push708Packet = function () {
36214      var packet708 = this.current708Packet;
36215      var packetData = packet708.data;
36216      var serviceNum = null;
36217      var blockSize = null;
36218      var i = 0;
36219      var b = packetData[i++];
36220      packet708.seq = b >> 6;
36221      packet708.sizeCode = b & 0x3f; // 0b00111111;
36222
36223      for (; i < packetData.length; i++) {
36224        b = packetData[i++];
36225        serviceNum = b >> 5;
36226        blockSize = b & 0x1f; // 0b00011111
36227
36228        if (serviceNum === 7 && blockSize > 0) {
36229          // Extended service num
36230          b = packetData[i++];
36231          serviceNum = b;
36232        }
36233        this.pushServiceBlock(serviceNum, i, blockSize);
36234        if (blockSize > 0) {
36235          i += blockSize - 1;
36236        }
36237      }
36238    };
36239    /**
36240     * Parse service block, execute commands, read text.
36241     *
36242     * Note: While many of these commands serve important purposes,
36243     * many others just parse out the parameters or attributes, but
36244     * nothing is done with them because this is not a full and complete
36245     * implementation of the entire 708 spec.
36246     *
36247     * @param  {Integer} serviceNum Service number
36248     * @param  {Integer} start      Start index of the 708 packet data
36249     * @param  {Integer} size       Block size
36250     */
36251
36252    Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
36253      var b;
36254      var i = start;
36255      var packetData = this.current708Packet.data;
36256      var service = this.services[serviceNum];
36257      if (!service) {
36258        service = this.initService(serviceNum, i);
36259      }
36260      for (; i < start + size && i < packetData.length; i++) {
36261        b = packetData[i];
36262        if (within708TextBlock(b)) {
36263          i = this.handleText(i, service);
36264        } else if (b === 0x18) {
36265          i = this.multiByteCharacter(i, service);
36266        } else if (b === 0x10) {
36267          i = this.extendedCommands(i, service);
36268        } else if (0x80 <= b && b <= 0x87) {
36269          i = this.setCurrentWindow(i, service);
36270        } else if (0x98 <= b && b <= 0x9f) {
36271          i = this.defineWindow(i, service);
36272        } else if (b === 0x88) {
36273          i = this.clearWindows(i, service);
36274        } else if (b === 0x8c) {
36275          i = this.deleteWindows(i, service);
36276        } else if (b === 0x89) {
36277          i = this.displayWindows(i, service);
36278        } else if (b === 0x8a) {
36279          i = this.hideWindows(i, service);
36280        } else if (b === 0x8b) {
36281          i = this.toggleWindows(i, service);
36282        } else if (b === 0x97) {
36283          i = this.setWindowAttributes(i, service);
36284        } else if (b === 0x90) {
36285          i = this.setPenAttributes(i, service);
36286        } else if (b === 0x91) {
36287          i = this.setPenColor(i, service);
36288        } else if (b === 0x92) {
36289          i = this.setPenLocation(i, service);
36290        } else if (b === 0x8f) {
36291          service = this.reset(i, service);
36292        } else if (b === 0x08) {
36293          // BS: Backspace
36294          service.currentWindow.backspace();
36295        } else if (b === 0x0c) {
36296          // FF: Form feed
36297          service.currentWindow.clearText();
36298        } else if (b === 0x0d) {
36299          // CR: Carriage return
36300          service.currentWindow.pendingNewLine = true;
36301        } else if (b === 0x0e) {
36302          // HCR: Horizontal carriage return
36303          service.currentWindow.clearText();
36304        } else if (b === 0x8d) {
36305          // DLY: Delay, nothing to do
36306          i++;
36307        } else;
36308      }
36309    };
36310    /**
36311     * Execute an extended command
36312     *
36313     * @param  {Integer} i        Current index in the 708 packet
36314     * @param  {Service} service  The service object to be affected
36315     * @return {Integer}          New index after parsing
36316     */
36317
36318    Cea708Stream.prototype.extendedCommands = function (i, service) {
36319      var packetData = this.current708Packet.data;
36320      var b = packetData[++i];
36321      if (within708TextBlock(b)) {
36322        i = this.handleText(i, service, {
36323          isExtended: true,
36324        });
36325      }
36326      return i;
36327    };
36328    /**
36329     * Get PTS value of a given byte index
36330     *
36331     * @param  {Integer} byteIndex  Index of the byte
36332     * @return {Integer}            PTS
36333     */
36334
36335    Cea708Stream.prototype.getPts = function (byteIndex) {
36336      // There's 1 pts value per 2 bytes
36337      return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
36338    };
36339    /**
36340     * Initializes a service
36341     *
36342     * @param  {Integer} serviceNum Service number
36343     * @return {Service}            Initialized service object
36344     */
36345
36346    Cea708Stream.prototype.initService = function (serviceNum, i) {
36347      var serviceName = 'SERVICE' + serviceNum;
36348      var self = this;
36349      var serviceName;
36350      var encoding;
36351      if (serviceName in this.serviceEncodings) {
36352        encoding = this.serviceEncodings[serviceName];
36353      }
36354      this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
36355      this.services[serviceNum].init(this.getPts(i), function (pts) {
36356        self.flushDisplayed(pts, self.services[serviceNum]);
36357      });
36358      return this.services[serviceNum];
36359    };
36360    /**
36361     * Execute text writing to current window
36362     *
36363     * @param  {Integer} i        Current index in the 708 packet
36364     * @param  {Service} service  The service object to be affected
36365     * @return {Integer}          New index after parsing
36366     */
36367
36368    Cea708Stream.prototype.handleText = function (i, service, options) {
36369      var isExtended = options && options.isExtended;
36370      var isMultiByte = options && options.isMultiByte;
36371      var packetData = this.current708Packet.data;
36372      var extended = isExtended ? 0x1000 : 0x0000;
36373      var currentByte = packetData[i];
36374      var nextByte = packetData[i + 1];
36375      var win = service.currentWindow;
36376      var char;
36377      var charCodeArray; // Converts an array of bytes to a unicode hex string.
36378
36379      function toHexString(byteArray) {
36380        return byteArray
36381          .map((byte) => {
36382            return ('0' + (byte & 0xff).toString(16)).slice(-2);
36383          })
36384          .join('');
36385      }
36386      if (isMultiByte) {
36387        charCodeArray = [currentByte, nextByte];
36388        i++;
36389      } else {
36390        charCodeArray = [currentByte];
36391      } // Use the TextDecoder if one was created for this service
36392
36393      if (service.textDecoder_ && !isExtended) {
36394        char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
36395      } else {
36396        // We assume any multi-byte char without a decoder is unicode.
36397        if (isMultiByte) {
36398          const unicode = toHexString(charCodeArray); // Takes a unicode hex string and creates a single character.
36399
36400          char = String.fromCharCode(parseInt(unicode, 16));
36401        } else {
36402          char = get708CharFromCode(extended | currentByte);
36403        }
36404      }
36405      if (win.pendingNewLine && !win.isEmpty()) {
36406        win.newLine(this.getPts(i));
36407      }
36408      win.pendingNewLine = false;
36409      win.addText(char);
36410      return i;
36411    };
36412    /**
36413     * Handle decoding of multibyte character
36414     *
36415     * @param  {Integer} i        Current index in the 708 packet
36416     * @param  {Service} service  The service object to be affected
36417     * @return {Integer}          New index after parsing
36418     */
36419
36420    Cea708Stream.prototype.multiByteCharacter = function (i, service) {
36421      var packetData = this.current708Packet.data;
36422      var firstByte = packetData[i + 1];
36423      var secondByte = packetData[i + 2];
36424      if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
36425        i = this.handleText(++i, service, {
36426          isMultiByte: true,
36427        });
36428      }
36429      return i;
36430    };
36431    /**
36432     * Parse and execute the CW# command.
36433     *
36434     * Set the current window.
36435     *
36436     * @param  {Integer} i        Current index in the 708 packet
36437     * @param  {Service} service  The service object to be affected
36438     * @return {Integer}          New index after parsing
36439     */
36440
36441    Cea708Stream.prototype.setCurrentWindow = function (i, service) {
36442      var packetData = this.current708Packet.data;
36443      var b = packetData[i];
36444      var windowNum = b & 0x07;
36445      service.setCurrentWindow(windowNum);
36446      return i;
36447    };
36448    /**
36449     * Parse and execute the DF# command.
36450     *
36451     * Define a window and set it as the current window.
36452     *
36453     * @param  {Integer} i        Current index in the 708 packet
36454     * @param  {Service} service  The service object to be affected
36455     * @return {Integer}          New index after parsing
36456     */
36457
36458    Cea708Stream.prototype.defineWindow = function (i, service) {
36459      var packetData = this.current708Packet.data;
36460      var b = packetData[i];
36461      var windowNum = b & 0x07;
36462      service.setCurrentWindow(windowNum);
36463      var win = service.currentWindow;
36464      b = packetData[++i];
36465      win.visible = (b & 0x20) >> 5; // v
36466
36467      win.rowLock = (b & 0x10) >> 4; // rl
36468
36469      win.columnLock = (b & 0x08) >> 3; // cl
36470
36471      win.priority = b & 0x07; // p
36472
36473      b = packetData[++i];
36474      win.relativePositioning = (b & 0x80) >> 7; // rp
36475
36476      win.anchorVertical = b & 0x7f; // av
36477
36478      b = packetData[++i];
36479      win.anchorHorizontal = b; // ah
36480
36481      b = packetData[++i];
36482      win.anchorPoint = (b & 0xf0) >> 4; // ap
36483
36484      win.rowCount = b & 0x0f; // rc
36485
36486      b = packetData[++i];
36487      win.columnCount = b & 0x3f; // cc
36488
36489      b = packetData[++i];
36490      win.windowStyle = (b & 0x38) >> 3; // ws
36491
36492      win.penStyle = b & 0x07; // ps
36493      // The spec says there are (rowCount+1) "virtual rows"
36494
36495      win.virtualRowCount = win.rowCount + 1;
36496      return i;
36497    };
36498    /**
36499     * Parse and execute the SWA command.
36500     *
36501     * Set attributes of the current window.
36502     *
36503     * @param  {Integer} i        Current index in the 708 packet
36504     * @param  {Service} service  The service object to be affected
36505     * @return {Integer}          New index after parsing
36506     */
36507
36508    Cea708Stream.prototype.setWindowAttributes = function (i, service) {
36509      var packetData = this.current708Packet.data;
36510      var b = packetData[i];
36511      var winAttr = service.currentWindow.winAttr;
36512      b = packetData[++i];
36513      winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
36514
36515      winAttr.fillRed = (b & 0x30) >> 4; // fr
36516
36517      winAttr.fillGreen = (b & 0x0c) >> 2; // fg
36518
36519      winAttr.fillBlue = b & 0x03; // fb
36520
36521      b = packetData[++i];
36522      winAttr.borderType = (b & 0xc0) >> 6; // bt
36523
36524      winAttr.borderRed = (b & 0x30) >> 4; // br
36525
36526      winAttr.borderGreen = (b & 0x0c) >> 2; // bg
36527
36528      winAttr.borderBlue = b & 0x03; // bb
36529
36530      b = packetData[++i];
36531      winAttr.borderType += (b & 0x80) >> 5; // bt
36532
36533      winAttr.wordWrap = (b & 0x40) >> 6; // ww
36534
36535      winAttr.printDirection = (b & 0x30) >> 4; // pd
36536
36537      winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
36538
36539      winAttr.justify = b & 0x03; // j
36540
36541      b = packetData[++i];
36542      winAttr.effectSpeed = (b & 0xf0) >> 4; // es
36543
36544      winAttr.effectDirection = (b & 0x0c) >> 2; // ed
36545
36546      winAttr.displayEffect = b & 0x03; // de
36547
36548      return i;
36549    };
36550    /**
36551     * Gather text from all displayed windows and push a caption to output.
36552     *
36553     * @param  {Integer} i        Current index in the 708 packet
36554     * @param  {Service} service  The service object to be affected
36555     */
36556
36557    Cea708Stream.prototype.flushDisplayed = function (pts, service) {
36558      var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
36559      // display text in the correct order, but sample files so far have not shown any issue.
36560
36561      for (var winId = 0; winId < 8; winId++) {
36562        if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
36563          displayedText.push(service.windows[winId].getText());
36564        }
36565      }
36566      service.endPts = pts;
36567      service.text = displayedText.join('\n\n');
36568      this.pushCaption(service);
36569      service.startPts = pts;
36570    };
36571    /**
36572     * Push a caption to output if the caption contains text.
36573     *
36574     * @param  {Service} service  The service object to be affected
36575     */
36576
36577    Cea708Stream.prototype.pushCaption = function (service) {
36578      if (service.text !== '') {
36579        this.trigger('data', {
36580          startPts: service.startPts,
36581          endPts: service.endPts,
36582          text: service.text,
36583          stream: 'cc708_' + service.serviceNum,
36584        });
36585        service.text = '';
36586        service.startPts = service.endPts;
36587      }
36588    };
36589    /**
36590     * Parse and execute the DSW command.
36591     *
36592     * Set visible property of windows based on the parsed bitmask.
36593     *
36594     * @param  {Integer} i        Current index in the 708 packet
36595     * @param  {Service} service  The service object to be affected
36596     * @return {Integer}          New index after parsing
36597     */
36598
36599    Cea708Stream.prototype.displayWindows = function (i, service) {
36600      var packetData = this.current708Packet.data;
36601      var b = packetData[++i];
36602      var pts = this.getPts(i);
36603      this.flushDisplayed(pts, service);
36604      for (var winId = 0; winId < 8; winId++) {
36605        if (b & (0x01 << winId)) {
36606          service.windows[winId].visible = 1;
36607        }
36608      }
36609      return i;
36610    };
36611    /**
36612     * Parse and execute the HDW command.
36613     *
36614     * Set visible property of windows based on the parsed bitmask.
36615     *
36616     * @param  {Integer} i        Current index in the 708 packet
36617     * @param  {Service} service  The service object to be affected
36618     * @return {Integer}          New index after parsing
36619     */
36620
36621    Cea708Stream.prototype.hideWindows = function (i, service) {
36622      var packetData = this.current708Packet.data;
36623      var b = packetData[++i];
36624      var pts = this.getPts(i);
36625      this.flushDisplayed(pts, service);
36626      for (var winId = 0; winId < 8; winId++) {
36627        if (b & (0x01 << winId)) {
36628          service.windows[winId].visible = 0;
36629        }
36630      }
36631      return i;
36632    };
36633    /**
36634     * Parse and execute the TGW command.
36635     *
36636     * Set visible property of windows based on the parsed bitmask.
36637     *
36638     * @param  {Integer} i        Current index in the 708 packet
36639     * @param  {Service} service  The service object to be affected
36640     * @return {Integer}          New index after parsing
36641     */
36642
36643    Cea708Stream.prototype.toggleWindows = function (i, service) {
36644      var packetData = this.current708Packet.data;
36645      var b = packetData[++i];
36646      var pts = this.getPts(i);
36647      this.flushDisplayed(pts, service);
36648      for (var winId = 0; winId < 8; winId++) {
36649        if (b & (0x01 << winId)) {
36650          service.windows[winId].visible ^= 1;
36651        }
36652      }
36653      return i;
36654    };
36655    /**
36656     * Parse and execute the CLW command.
36657     *
36658     * Clear text of windows based on the parsed bitmask.
36659     *
36660     * @param  {Integer} i        Current index in the 708 packet
36661     * @param  {Service} service  The service object to be affected
36662     * @return {Integer}          New index after parsing
36663     */
36664
36665    Cea708Stream.prototype.clearWindows = function (i, service) {
36666      var packetData = this.current708Packet.data;
36667      var b = packetData[++i];
36668      var pts = this.getPts(i);
36669      this.flushDisplayed(pts, service);
36670      for (var winId = 0; winId < 8; winId++) {
36671        if (b & (0x01 << winId)) {
36672          service.windows[winId].clearText();
36673        }
36674      }
36675      return i;
36676    };
36677    /**
36678     * Parse and execute the DLW command.
36679     *
36680     * Re-initialize windows based on the parsed bitmask.
36681     *
36682     * @param  {Integer} i        Current index in the 708 packet
36683     * @param  {Service} service  The service object to be affected
36684     * @return {Integer}          New index after parsing
36685     */
36686
36687    Cea708Stream.prototype.deleteWindows = function (i, service) {
36688      var packetData = this.current708Packet.data;
36689      var b = packetData[++i];
36690      var pts = this.getPts(i);
36691      this.flushDisplayed(pts, service);
36692      for (var winId = 0; winId < 8; winId++) {
36693        if (b & (0x01 << winId)) {
36694          service.windows[winId].reset();
36695        }
36696      }
36697      return i;
36698    };
36699    /**
36700     * Parse and execute the SPA command.
36701     *
36702     * Set pen attributes of the current window.
36703     *
36704     * @param  {Integer} i        Current index in the 708 packet
36705     * @param  {Service} service  The service object to be affected
36706     * @return {Integer}          New index after parsing
36707     */
36708
36709    Cea708Stream.prototype.setPenAttributes = function (i, service) {
36710      var packetData = this.current708Packet.data;
36711      var b = packetData[i];
36712      var penAttr = service.currentWindow.penAttr;
36713      b = packetData[++i];
36714      penAttr.textTag = (b & 0xf0) >> 4; // tt
36715
36716      penAttr.offset = (b & 0x0c) >> 2; // o
36717
36718      penAttr.penSize = b & 0x03; // s
36719
36720      b = packetData[++i];
36721      penAttr.italics = (b & 0x80) >> 7; // i
36722
36723      penAttr.underline = (b & 0x40) >> 6; // u
36724
36725      penAttr.edgeType = (b & 0x38) >> 3; // et
36726
36727      penAttr.fontStyle = b & 0x07; // fs
36728
36729      return i;
36730    };
36731    /**
36732     * Parse and execute the SPC command.
36733     *
36734     * Set pen color of the current window.
36735     *
36736     * @param  {Integer} i        Current index in the 708 packet
36737     * @param  {Service} service  The service object to be affected
36738     * @return {Integer}          New index after parsing
36739     */
36740
36741    Cea708Stream.prototype.setPenColor = function (i, service) {
36742      var packetData = this.current708Packet.data;
36743      var b = packetData[i];
36744      var penColor = service.currentWindow.penColor;
36745      b = packetData[++i];
36746      penColor.fgOpacity = (b & 0xc0) >> 6; // fo
36747
36748      penColor.fgRed = (b & 0x30) >> 4; // fr
36749
36750      penColor.fgGreen = (b & 0x0c) >> 2; // fg
36751
36752      penColor.fgBlue = b & 0x03; // fb
36753
36754      b = packetData[++i];
36755      penColor.bgOpacity = (b & 0xc0) >> 6; // bo
36756
36757      penColor.bgRed = (b & 0x30) >> 4; // br
36758
36759      penColor.bgGreen = (b & 0x0c) >> 2; // bg
36760
36761      penColor.bgBlue = b & 0x03; // bb
36762
36763      b = packetData[++i];
36764      penColor.edgeRed = (b & 0x30) >> 4; // er
36765
36766      penColor.edgeGreen = (b & 0x0c) >> 2; // eg
36767
36768      penColor.edgeBlue = b & 0x03; // eb
36769
36770      return i;
36771    };
36772    /**
36773     * Parse and execute the SPL command.
36774     *
36775     * Set pen location of the current window.
36776     *
36777     * @param  {Integer} i        Current index in the 708 packet
36778     * @param  {Service} service  The service object to be affected
36779     * @return {Integer}          New index after parsing
36780     */
36781
36782    Cea708Stream.prototype.setPenLocation = function (i, service) {
36783      var packetData = this.current708Packet.data;
36784      var b = packetData[i];
36785      var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
36786
36787      service.currentWindow.pendingNewLine = true;
36788      b = packetData[++i];
36789      penLoc.row = b & 0x0f; // r
36790
36791      b = packetData[++i];
36792      penLoc.column = b & 0x3f; // c
36793
36794      return i;
36795    };
36796    /**
36797     * Execute the RST command.
36798     *
36799     * Reset service to a clean slate. Re-initialize.
36800     *
36801     * @param  {Integer} i        Current index in the 708 packet
36802     * @param  {Service} service  The service object to be affected
36803     * @return {Service}          Re-initialized service
36804     */
36805
36806    Cea708Stream.prototype.reset = function (i, service) {
36807      var pts = this.getPts(i);
36808      this.flushDisplayed(pts, service);
36809      return this.initService(service.serviceNum, i);
36810    }; // This hash maps non-ASCII, special, and extended character codes to their
36811    // proper Unicode equivalent. The first keys that are only a single byte
36812    // are the non-standard ASCII characters, which simply map the CEA608 byte
36813    // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
36814    // character codes, but have their MSB bitmasked with 0x03 so that a lookup
36815    // can be performed regardless of the field and data channel on which the
36816    // character code was received.
36817
36818    var CHARACTER_TRANSLATION = {
36819      0x2a: 0xe1,
36820      // á
36821      0x5c: 0xe9,
36822      // é
36823      0x5e: 0xed,
36824      // í
36825      0x5f: 0xf3,
36826      // ó
36827      0x60: 0xfa,
36828      // ú
36829      0x7b: 0xe7,
36830      // ç
36831      0x7c: 0xf7,
36832      // ÷
36833      0x7d: 0xd1,
36834      // Ñ
36835      0x7e: 0xf1,
36836      // ñ
36837      0x7f: 0x2588,
36838      // █
36839      0x0130: 0xae,
36840      // ®
36841      0x0131: 0xb0,
36842      // °
36843      0x0132: 0xbd,
36844      // ½
36845      0x0133: 0xbf,
36846      // ¿
36847      0x0134: 0x2122,
36848      // ™
36849      0x0135: 0xa2,
36850      // ¢
36851      0x0136: 0xa3,
36852      // £
36853      0x0137: 0x266a,
36854      // ♪
36855      0x0138: 0xe0,
36856      // à
36857      0x0139: 0xa0,
36858      //
36859      0x013a: 0xe8,
36860      // è
36861      0x013b: 0xe2,
36862      // â
36863      0x013c: 0xea,
36864      // ê
36865      0x013d: 0xee,
36866      // î
36867      0x013e: 0xf4,
36868      // ô
36869      0x013f: 0xfb,
36870      // û
36871      0x0220: 0xc1,
36872      // Á
36873      0x0221: 0xc9,
36874      // É
36875      0x0222: 0xd3,
36876      // Ó
36877      0x0223: 0xda,
36878      // Ú
36879      0x0224: 0xdc,
36880      // Ü
36881      0x0225: 0xfc,
36882      // ü
36883      0x0226: 0x2018,
36884      // ‘
36885      0x0227: 0xa1,
36886      // ¡
36887      0x0228: 0x2a,
36888      // *
36889      0x0229: 0x27,
36890      // '
36891      0x022a: 0x2014,
36892      // —
36893      0x022b: 0xa9,
36894      // ©
36895      0x022c: 0x2120,
36896      // ℠
36897      0x022d: 0x2022,
36898      // •
36899      0x022e: 0x201c,
36900      // “
36901      0x022f: 0x201d,
36902      // ”
36903      0x0230: 0xc0,
36904      // À
36905      0x0231: 0xc2,
36906      // Â
36907      0x0232: 0xc7,
36908      // Ç
36909      0x0233: 0xc8,
36910      // È
36911      0x0234: 0xca,
36912      // Ê
36913      0x0235: 0xcb,
36914      // Ë
36915      0x0236: 0xeb,
36916      // ë
36917      0x0237: 0xce,
36918      // Î
36919      0x0238: 0xcf,
36920      // Ï
36921      0x0239: 0xef,
36922      // ï
36923      0x023a: 0xd4,
36924      // Ô
36925      0x023b: 0xd9,
36926      // Ù
36927      0x023c: 0xf9,
36928      // ù
36929      0x023d: 0xdb,
36930      // Û
36931      0x023e: 0xab,
36932      // «
36933      0x023f: 0xbb,
36934      // »
36935      0x0320: 0xc3,
36936      // Ã
36937      0x0321: 0xe3,
36938      // ã
36939      0x0322: 0xcd,
36940      // Í
36941      0x0323: 0xcc,
36942      // Ì
36943      0x0324: 0xec,
36944      // ì
36945      0x0325: 0xd2,
36946      // Ò
36947      0x0326: 0xf2,
36948      // ò
36949      0x0327: 0xd5,
36950      // Õ
36951      0x0328: 0xf5,
36952      // õ
36953      0x0329: 0x7b,
36954      // {
36955      0x032a: 0x7d,
36956      // }
36957      0x032b: 0x5c,
36958      // \
36959      0x032c: 0x5e,
36960      // ^
36961      0x032d: 0x5f,
36962      // _
36963      0x032e: 0x7c,
36964      // |
36965      0x032f: 0x7e,
36966      // ~
36967      0x0330: 0xc4,
36968      // Ä
36969      0x0331: 0xe4,
36970      // ä
36971      0x0332: 0xd6,
36972      // Ö
36973      0x0333: 0xf6,
36974      // ö
36975      0x0334: 0xdf,
36976      // ß
36977      0x0335: 0xa5,
36978      // ¥
36979      0x0336: 0xa4,
36980      // ¤
36981      0x0337: 0x2502,
36982      // │
36983      0x0338: 0xc5,
36984      // Å
36985      0x0339: 0xe5,
36986      // å
36987      0x033a: 0xd8,
36988      // Ø
36989      0x033b: 0xf8,
36990      // ø
36991      0x033c: 0x250c,
36992      // ┌
36993      0x033d: 0x2510,
36994      // ┐
36995      0x033e: 0x2514,
36996      // └
36997      0x033f: 0x2518, // ┘
36998    };
36999    var getCharFromCode = function (code) {
37000      if (code === null) {
37001        return '';
37002      }
37003      code = CHARACTER_TRANSLATION[code] || code;
37004      return String.fromCharCode(code);
37005    }; // the index of the last row in a CEA-608 display buffer
37006
37007    var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
37008    // getting it through bit logic.
37009
37010    var ROWS = [
37011      0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000,
37012      0x1300, 0x1320, 0x1400, 0x1420,
37013    ]; // CEA-608 captions are rendered onto a 34x15 matrix of character
37014    // cells. The "bottom" row is the last element in the outer array.
37015    // We keep track of positioning information as we go by storing the
37016    // number of indentations and the tab offset in this buffer.
37017
37018    var createDisplayBuffer = function () {
37019      var result = [],
37020        i = BOTTOM_ROW + 1;
37021      while (i--) {
37022        result.push({
37023          text: '',
37024          indent: 0,
37025          offset: 0,
37026        });
37027      }
37028      return result;
37029    };
37030    var Cea608Stream = function (field, dataChannel) {
37031      Cea608Stream.prototype.init.call(this);
37032      this.field_ = field || 0;
37033      this.dataChannel_ = dataChannel || 0;
37034      this.name_ = 'CC' + (((this.field_ << 1)
vendor: 8,791 bytes, lines 37034-37218
37034 | this.dataChannel_) + 1);
37035      this.setConstants();
37036      this.reset();
37037      this.push = function (packet) {
37038        var data, swap, char0, char1, text; // remove the parity bits
37039
37040        data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
37041
37042        if (data === this.lastControlCode_) {
37043          this.lastControlCode_ = null;
37044          return;
37045        } // Store control codes
37046
37047        if ((data & 0xf000) === 0x1000) {
37048          this.lastControlCode_ = data;
37049        } else if (data !== this.PADDING_) {
37050          this.lastControlCode_ = null;
37051        }
37052        char0 = data >>> 8;
37053        char1 = data & 0xff;
37054        if (data === this.PADDING_) {
37055          return;
37056        } else if (data === this.RESUME_CAPTION_LOADING_) {
37057          this.mode_ = 'popOn';
37058        } else if (data === this.END_OF_CAPTION_) {
37059          // If an EOC is received while in paint-on mode, the displayed caption
37060          // text should be swapped to non-displayed memory as if it was a pop-on
37061          // caption. Because of that, we should explicitly switch back to pop-on
37062          // mode
37063          this.mode_ = 'popOn';
37064          this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
37065
37066          this.flushDisplayed(packet.pts); // flip memory
37067
37068          swap = this.displayed_;
37069          this.displayed_ = this.nonDisplayed_;
37070          this.nonDisplayed_ = swap; // start measuring the time to display the caption
37071
37072          this.startPts_ = packet.pts;
37073        } else if (data === this.ROLL_UP_2_ROWS_) {
37074          this.rollUpRows_ = 2;
37075          this.setRollUp(packet.pts);
37076        } else if (data === this.ROLL_UP_3_ROWS_) {
37077          this.rollUpRows_ = 3;
37078          this.setRollUp(packet.pts);
37079        } else if (data === this.ROLL_UP_4_ROWS_) {
37080          this.rollUpRows_ = 4;
37081          this.setRollUp(packet.pts);
37082        } else if (data === this.CARRIAGE_RETURN_) {
37083          this.clearFormatting(packet.pts);
37084          this.flushDisplayed(packet.pts);
37085          this.shiftRowsUp_();
37086          this.startPts_ = packet.pts;
37087        } else if (data === this.BACKSPACE_) {
37088          if (this.mode_ === 'popOn') {
37089            this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
37090          } else {
37091            this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
37092          }
37093        } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
37094          this.flushDisplayed(packet.pts);
37095          this.displayed_ = createDisplayBuffer();
37096        } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
37097          this.nonDisplayed_ = createDisplayBuffer();
37098        } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
37099          if (this.mode_ !== 'paintOn') {
37100            // NOTE: This should be removed when proper caption positioning is
37101            // implemented
37102            this.flushDisplayed(packet.pts);
37103            this.displayed_ = createDisplayBuffer();
37104          }
37105          this.mode_ = 'paintOn';
37106          this.startPts_ = packet.pts; // Append special characters to caption text
37107        } else if (this.isSpecialCharacter(char0, char1)) {
37108          // Bitmask char0 so that we can apply character transformations
37109          // regardless of field and data channel.
37110          // Then byte-shift to the left and OR with char1 so we can pass the
37111          // entire character code to `getCharFromCode`.
37112          char0 = (char0 & 0x03) << 8;
37113          text = getCharFromCode(char0 | char1);
37114          this[this.mode_](packet.pts, text);
37115          this.column_++; // Append extended characters to caption text
37116        } else if (this.isExtCharacter(char0, char1)) {
37117          // Extended characters always follow their "non-extended" equivalents.
37118          // IE if a "è" is desired, you'll always receive "eè"; non-compliant
37119          // decoders are supposed to drop the "è", while compliant decoders
37120          // backspace the "e" and insert "è".
37121          // Delete the previous character
37122          if (this.mode_ === 'popOn') {
37123            this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
37124          } else {
37125            this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
37126          } // Bitmask char0 so that we can apply character transformations
37127          // regardless of field and data channel.
37128          // Then byte-shift to the left and OR with char1 so we can pass the
37129          // entire character code to `getCharFromCode`.
37130
37131          char0 = (char0 & 0x03) << 8;
37132          text = getCharFromCode(char0 | char1);
37133          this[this.mode_](packet.pts, text);
37134          this.column_++; // Process mid-row codes
37135        } else if (this.isMidRowCode(char0, char1)) {
37136          // Attributes are not additive, so clear all formatting
37137          this.clearFormatting(packet.pts); // According to the standard, mid-row codes
37138          // should be replaced with spaces, so add one now
37139
37140          this[this.mode_](packet.pts, ' ');
37141          this.column_++;
37142          if ((char1 & 0xe) === 0xe) {
37143            this.addFormatting(packet.pts, ['i']);
37144          }
37145          if ((char1 & 0x1) === 0x1) {
37146            this.addFormatting(packet.pts, ['u']);
37147          } // Detect offset control codes and adjust cursor
37148        } else if (this.isOffsetControlCode(char0, char1)) {
37149          // Cursor position is set by indent PAC (see below) in 4-column
37150          // increments, with an additional offset code of 1-3 to reach any
37151          // of the 32 columns specified by CEA-608. So all we need to do
37152          // here is increment the column cursor by the given offset.
37153          const offset = char1 & 0x03; // For an offest value 1-3, set the offset for that caption
37154          // in the non-displayed array.
37155
37156          this.nonDisplayed_[this.row_].offset = offset;
37157          this.column_ += offset; // Detect PACs (Preamble Address Codes)
37158        } else if (this.isPAC(char0, char1)) {
37159          // There's no logic for PAC -> row mapping, so we have to just
37160          // find the row code in an array and use its index :(
37161          var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
37162
37163          if (this.mode_ === 'rollUp') {
37164            // This implies that the base row is incorrectly set.
37165            // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
37166            // of roll-up rows set.
37167            if (row - this.rollUpRows_ + 1 < 0) {
37168              row = this.rollUpRows_ - 1;
37169            }
37170            this.setRollUp(packet.pts, row);
37171          } // Ensure the row is between 0 and 14, otherwise use the most
37172          // recent or default row.
37173
37174          if (row !== this.row_ && row >= 0 && row <= 14) {
37175            // formatting is only persistent for current row
37176            this.clearFormatting(packet.pts);
37177            this.row_ = row;
37178          } // All PACs can apply underline, so detect and apply
37179          // (All odd-numbered second bytes set underline)
37180
37181          if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
37182            this.addFormatting(packet.pts, ['u']);
37183          }
37184          if ((data & 0x10) === 0x10) {
37185            // We've got an indent level code. Each successive even number
37186            // increments the column cursor by 4, so we can get the desired
37187            // column position by bit-shifting to the right (to get n/2)
37188            // and multiplying by 4.
37189            const indentations = (data & 0xe) >> 1;
37190            this.column_ = indentations * 4; // add to the number of indentations for positioning
37191
37192            this.nonDisplayed_[this.row_].indent += indentations;
37193          }
37194          if (this.isColorPAC(char1)) {
37195            // it's a color code, though we only support white, which
37196            // can be either normal or italicized. white italics can be
37197            // either 0x4e or 0x6e depending on the row, so we just
37198            // bitwise-and with 0xe to see if italics should be turned on
37199            if ((char1 & 0xe) === 0xe) {
37200              this.addFormatting(packet.pts, ['i']);
37201            }
37202          } // We have a normal character in char0, and possibly one in char1
37203        } else if (this.isNormalChar(char0)) {
37204          if (char1 === 0x00) {
37205            char1 = null;
37206          }
37207          text = getCharFromCode(char0);
37208          text += getCharFromCode(char1);
37209          this[this.mode_](packet.pts, text);
37210          this.column_ += text.length;
37211        } // finish data processing
37212      };
37213    };
37214    Cea608Stream.prototype = new Stream$7(); // Trigger a cue point that captures the current state of the
37215    // display buffer
37216
37217    Cea608Stream.prototype.flushDisplayed = function (pts) {
37218      const logWarning = 
vendor: 31,367 bytes, lines 37218-38027
37218(index) => {
37219        this.trigger('log', {
37220          level: 'warn',
37221          message: 'Skipping a malformed 608 caption at index ' + index + '.',
37222        });
37223      };
37224      const content = [];
37225      this.displayed_.forEach((row, i) => {
37226        if (row && row.text && row.text.length) {
37227          try {
37228            // remove spaces from the start and end of the string
37229            row.text = row.text.trim();
37230          } catch (e) {
37231            // Ordinarily, this shouldn't happen. However, caption
37232            // parsing errors should not throw exceptions and
37233            // break playback.
37234            logWarning(i);
37235          } // See the below link for more details on the following fields:
37236          // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
37237
37238          if (row.text.length) {
37239            content.push({
37240              // The text to be displayed in the caption from this specific row, with whitespace removed.
37241              text: row.text,
37242              // Value between 1 and 15 representing the PAC row used to calculate line height.
37243              line: i + 1,
37244              // A number representing the indent position by percentage (CEA-608 PAC indent code).
37245              // The value will be a number between 10 and 80. Offset is used to add an aditional
37246              // value to the position if necessary.
37247              position: 10 + Math.min(70, row.indent * 10) + row.offset * 2.5,
37248            });
37249          }
37250        } else if (row === undefined || row === null) {
37251          logWarning(i);
37252        }
37253      });
37254      if (content.length) {
37255        this.trigger('data', {
37256          startPts: this.startPts_,
37257          endPts: pts,
37258          content,
37259          stream: this.name_,
37260        });
37261      }
37262    };
37263    /**
37264     * Zero out the data, used for startup and on seek
37265     */
37266
37267    Cea608Stream.prototype.reset = function () {
37268      this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
37269      // actually display captions. If a caption is shifted to a row
37270      // with a lower index than this, it is cleared from the display
37271      // buffer
37272
37273      this.topRow_ = 0;
37274      this.startPts_ = 0;
37275      this.displayed_ = createDisplayBuffer();
37276      this.nonDisplayed_ = createDisplayBuffer();
37277      this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
37278
37279      this.column_ = 0;
37280      this.row_ = BOTTOM_ROW;
37281      this.rollUpRows_ = 2; // This variable holds currently-applied formatting
37282
37283      this.formatting_ = [];
37284    };
37285    /**
37286     * Sets up control code and related constants for this instance
37287     */
37288
37289    Cea608Stream.prototype.setConstants = function () {
37290      // The following attributes have these uses:
37291      // ext_ :    char0 for mid-row codes, and the base for extended
37292      //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
37293      //           extended codes)
37294      // control_: char0 for control codes, except byte-shifted to the
37295      //           left so that we can do this.control_ | CONTROL_CODE
37296      // offset_:  char0 for tab offset codes
37297      //
37298      // It's also worth noting that control codes, and _only_ control codes,
37299      // differ between field 1 and field2. Field 2 control codes are always
37300      // their field 1 value plus 1. That's why there's the "| field" on the
37301      // control value.
37302      if (this.dataChannel_ === 0) {
37303        this.BASE_ = 0x10;
37304        this.EXT_ = 0x11;
37305        this.CONTROL_ = (0x14 | this.field_) << 8;
37306        this.OFFSET_ = 0x17;
37307      } else if (this.dataChannel_ === 1) {
37308        this.BASE_ = 0x18;
37309        this.EXT_ = 0x19;
37310        this.CONTROL_ = (0x1c | this.field_) << 8;
37311        this.OFFSET_ = 0x1f;
37312      } // Constants for the LSByte command codes recognized by Cea608Stream. This
37313      // list is not exhaustive. For a more comprehensive listing and semantics see
37314      // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
37315      // Padding
37316
37317      this.PADDING_ = 0x0000; // Pop-on Mode
37318
37319      this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
37320      this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
37321
37322      this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
37323      this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
37324      this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
37325      this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
37326
37327      this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
37328
37329      this.BACKSPACE_ = this.CONTROL_ | 0x21;
37330      this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
37331      this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
37332    };
37333    /**
37334     * Detects if the 2-byte packet data is a special character
37335     *
37336     * Special characters have a second byte in the range 0x30 to 0x3f,
37337     * with the first byte being 0x11 (for data channel 1) or 0x19 (for
37338     * data channel 2).
37339     *
37340     * @param  {Integer} char0 The first byte
37341     * @param  {Integer} char1 The second byte
37342     * @return {Boolean}       Whether the 2 bytes are an special character
37343     */
37344
37345    Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
37346      return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
37347    };
37348    /**
37349     * Detects if the 2-byte packet data is an extended character
37350     *
37351     * Extended characters have a second byte in the range 0x20 to 0x3f,
37352     * with the first byte being 0x12 or 0x13 (for data channel 1) or
37353     * 0x1a or 0x1b (for data channel 2).
37354     *
37355     * @param  {Integer} char0 The first byte
37356     * @param  {Integer} char1 The second byte
37357     * @return {Boolean}       Whether the 2 bytes are an extended character
37358     */
37359
37360    Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
37361      return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
37362    };
37363    /**
37364     * Detects if the 2-byte packet is a mid-row code
37365     *
37366     * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
37367     * the first byte being 0x11 (for data channel 1) or 0x19 (for data
37368     * channel 2).
37369     *
37370     * @param  {Integer} char0 The first byte
37371     * @param  {Integer} char1 The second byte
37372     * @return {Boolean}       Whether the 2 bytes are a mid-row code
37373     */
37374
37375    Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
37376      return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
37377    };
37378    /**
37379     * Detects if the 2-byte packet is an offset control code
37380     *
37381     * Offset control codes have a second byte in the range 0x21 to 0x23,
37382     * with the first byte being 0x17 (for data channel 1) or 0x1f (for
37383     * data channel 2).
37384     *
37385     * @param  {Integer} char0 The first byte
37386     * @param  {Integer} char1 The second byte
37387     * @return {Boolean}       Whether the 2 bytes are an offset control code
37388     */
37389
37390    Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
37391      return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
37392    };
37393    /**
37394     * Detects if the 2-byte packet is a Preamble Address Code
37395     *
37396     * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
37397     * or 0x18 to 0x1f (for data channel 2), with the second byte in the
37398     * range 0x40 to 0x7f.
37399     *
37400     * @param  {Integer} char0 The first byte
37401     * @param  {Integer} char1 The second byte
37402     * @return {Boolean}       Whether the 2 bytes are a PAC
37403     */
37404
37405    Cea608Stream.prototype.isPAC = function (char0, char1) {
37406      return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
37407    };
37408    /**
37409     * Detects if a packet's second byte is in the range of a PAC color code
37410     *
37411     * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
37412     * 0x60 to 0x6f.
37413     *
37414     * @param  {Integer} char1 The second byte
37415     * @return {Boolean}       Whether the byte is a color PAC
37416     */
37417
37418    Cea608Stream.prototype.isColorPAC = function (char1) {
37419      return (char1 >= 0x40 && char1 <= 0x4f) || (char1 >= 0x60 && char1 <= 0x7f);
37420    };
37421    /**
37422     * Detects if a single byte is in the range of a normal character
37423     *
37424     * Normal text bytes are in the range 0x20 to 0x7f.
37425     *
37426     * @param  {Integer} char  The byte
37427     * @return {Boolean}       Whether the byte is a normal character
37428     */
37429
37430    Cea608Stream.prototype.isNormalChar = function (char) {
37431      return char >= 0x20 && char <= 0x7f;
37432    };
37433    /**
37434     * Configures roll-up
37435     *
37436     * @param  {Integer} pts         Current PTS
37437     * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
37438     *                               a new position
37439     */
37440
37441    Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
37442      // Reset the base row to the bottom row when switching modes
37443      if (this.mode_ !== 'rollUp') {
37444        this.row_ = BOTTOM_ROW;
37445        this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
37446
37447        this.flushDisplayed(pts);
37448        this.nonDisplayed_ = createDisplayBuffer();
37449        this.displayed_ = createDisplayBuffer();
37450      }
37451      if (newBaseRow !== undefined && newBaseRow !== this.row_) {
37452        // move currently displayed captions (up or down) to the new base row
37453        for (var i = 0; i < this.rollUpRows_; i++) {
37454          this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
37455          this.displayed_[this.row_ - i] = {
37456            text: '',
37457            indent: 0,
37458            offset: 0,
37459          };
37460        }
37461      }
37462      if (newBaseRow === undefined) {
37463        newBaseRow = this.row_;
37464      }
37465      this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
37466    }; // Adds the opening HTML tag for the passed character to the caption text,
37467    // and keeps track of it for later closing
37468
37469    Cea608Stream.prototype.addFormatting = function (pts, format) {
37470      this.formatting_ = this.formatting_.concat(format);
37471      var text = format.reduce(function (text, format) {
37472        return text + '<' + format + '>';
37473      }, '');
37474      this[this.mode_](pts, text);
37475    }; // Adds HTML closing tags for current formatting to caption text and
37476    // clears remembered formatting
37477
37478    Cea608Stream.prototype.clearFormatting = function (pts) {
37479      if (!this.formatting_.length) {
37480        return;
37481      }
37482      var text = this.formatting_.reverse().reduce(function (text, format) {
37483        return text + '</' + format + '>';
37484      }, '');
37485      this.formatting_ = [];
37486      this[this.mode_](pts, text);
37487    }; // Mode Implementations
37488
37489    Cea608Stream.prototype.popOn = function (pts, text) {
37490      var baseRow = this.nonDisplayed_[this.row_].text; // buffer characters
37491
37492      baseRow += text;
37493      this.nonDisplayed_[this.row_].text = baseRow;
37494    };
37495    Cea608Stream.prototype.rollUp = function (pts, text) {
37496      var baseRow = this.displayed_[this.row_].text;
37497      baseRow += text;
37498      this.displayed_[this.row_].text = baseRow;
37499    };
37500    Cea608Stream.prototype.shiftRowsUp_ = function () {
37501      var i; // clear out inactive rows
37502
37503      for (i = 0; i < this.topRow_; i++) {
37504        this.displayed_[i] = {
37505          text: '',
37506          indent: 0,
37507          offset: 0,
37508        };
37509      }
37510      for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
37511        this.displayed_[i] = {
37512          text: '',
37513          indent: 0,
37514          offset: 0,
37515        };
37516      } // shift displayed rows up
37517
37518      for (i = this.topRow_; i < this.row_; i++) {
37519        this.displayed_[i] = this.displayed_[i + 1];
37520      } // clear out the bottom row
37521
37522      this.displayed_[this.row_] = {
37523        text: '',
37524        indent: 0,
37525        offset: 0,
37526      };
37527    };
37528    Cea608Stream.prototype.paintOn = function (pts, text) {
37529      var baseRow = this.displayed_[this.row_].text;
37530      baseRow += text;
37531      this.displayed_[this.row_].text = baseRow;
37532    }; // exports
37533
37534    var captionStream = {
37535      CaptionStream: CaptionStream$2,
37536      Cea608Stream: Cea608Stream,
37537      Cea708Stream: Cea708Stream,
37538    };
37539    /**
37540     * mux.js
37541     *
37542     * Copyright (c) Brightcove
37543     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
37544     */
37545
37546    var streamTypes = {
37547      H264_STREAM_TYPE: 0x1b,
37548      ADTS_STREAM_TYPE: 0x0f,
37549      METADATA_STREAM_TYPE: 0x15,
37550    };
37551    /**
37552     * mux.js
37553     *
37554     * Copyright (c) Brightcove
37555     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
37556     *
37557     * Accepts program elementary stream (PES) data events and corrects
37558     * decode and presentation time stamps to account for a rollover
37559     * of the 33 bit value.
37560     */
37561
37562    var Stream$6 = stream;
37563    var MAX_TS = 8589934592;
37564    var RO_THRESH = 4294967296;
37565    var TYPE_SHARED = 'shared';
37566    var handleRollover$1 = function (value, reference) {
37567      var direction = 1;
37568      if (value > reference) {
37569        // If the current timestamp value is greater than our reference timestamp and we detect a
37570        // timestamp rollover, this means the roll over is happening in the opposite direction.
37571        // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
37572        // point will be set to a small number, e.g. 1. The user then seeks backwards over the
37573        // rollover point. In loading this segment, the timestamp values will be very large,
37574        // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
37575        // the time stamp to be `value - 2^33`.
37576        direction = -1;
37577      } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
37578      // cause an incorrect adjustment.
37579
37580      while (Math.abs(reference - value) > RO_THRESH) {
37581        value += direction * MAX_TS;
37582      }
37583      return value;
37584    };
37585    var TimestampRolloverStream$1 = function (type) {
37586      var lastDTS, referenceDTS;
37587      TimestampRolloverStream$1.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
37588      // video and audio. We could use `undefined` here, but having a string
37589      // makes debugging a little clearer.
37590
37591      this.type_ = type || TYPE_SHARED;
37592      this.push = function (data) {
37593        /**
37594         * Rollover stream expects data from elementary stream.
37595         * Elementary stream can push forward 2 types of data
37596         * - Parsed Video/Audio/Timed-metadata PES (packetized elementary stream) packets
37597         * - Tracks metadata from PMT (Program Map Table)
37598         * Rollover stream expects pts/dts info to be available, since it stores lastDTS
37599         * We should ignore non-PES packets since they may override lastDTS to undefined.
37600         * lastDTS is important to signal the next segments
37601         * about rollover from the previous segments.
37602         */
37603        if (data.type === 'metadata') {
37604          this.trigger('data', data);
37605          return;
37606        } // Any "shared" rollover streams will accept _all_ data. Otherwise,
37607        // streams will only accept data that matches their type.
37608
37609        if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
37610          return;
37611        }
37612        if (referenceDTS === undefined) {
37613          referenceDTS = data.dts;
37614        }
37615        data.dts = handleRollover$1(data.dts, referenceDTS);
37616        data.pts = handleRollover$1(data.pts, referenceDTS);
37617        lastDTS = data.dts;
37618        this.trigger('data', data);
37619      };
37620      this.flush = function () {
37621        referenceDTS = lastDTS;
37622        this.trigger('done');
37623      };
37624      this.endTimeline = function () {
37625        this.flush();
37626        this.trigger('endedtimeline');
37627      };
37628      this.discontinuity = function () {
37629        referenceDTS = void 0;
37630        lastDTS = void 0;
37631      };
37632      this.reset = function () {
37633        this.discontinuity();
37634        this.trigger('reset');
37635      };
37636    };
37637    TimestampRolloverStream$1.prototype = new Stream$6();
37638    var timestampRolloverStream = {
37639      TimestampRolloverStream: TimestampRolloverStream$1,
37640      handleRollover: handleRollover$1,
37641    }; // Once IE11 support is dropped, this function should be removed.
37642
37643    var typedArrayIndexOf$1 = (typedArray, element, fromIndex) => {
37644      if (!typedArray) {
37645        return -1;
37646      }
37647      var currentIndex = fromIndex;
37648      for (; currentIndex < typedArray.length; currentIndex++) {
37649        if (typedArray[currentIndex] === element) {
37650          return currentIndex;
37651        }
37652      }
37653      return -1;
37654    };
37655    var typedArray = {
37656      typedArrayIndexOf: typedArrayIndexOf$1,
37657    };
37658    /**
37659     * mux.js
37660     *
37661     * Copyright (c) Brightcove
37662     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
37663     *
37664     * Tools for parsing ID3 frame data
37665     * @see http://id3.org/id3v2.3.0
37666     */
37667
37668    var typedArrayIndexOf = typedArray.typedArrayIndexOf,
37669      // Frames that allow different types of text encoding contain a text
37670      // encoding description byte [ID3v2.4.0 section 4.]
37671      textEncodingDescriptionByte = {
37672        Iso88591: 0x00,
37673        // ISO-8859-1, terminated with \0.
37674        Utf16: 0x01,
37675        // UTF-16 encoded Unicode BOM, terminated with \0\0
37676        Utf16be: 0x02,
37677        // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
37678        Utf8: 0x03, // UTF-8 encoded Unicode, terminated with \0
37679      },
37680      // return a percent-encoded representation of the specified byte range
37681      // @see http://en.wikipedia.org/wiki/Percent-encoding
37682      percentEncode$1 = function (bytes, start, end) {
37683        var i,
37684          result = '';
37685        for (i = start; i < end; i++) {
37686          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
37687        }
37688        return result;
37689      },
37690      // return the string representation of the specified byte range,
37691      // interpreted as UTf-8.
37692      parseUtf8 = function (bytes, start, end) {
37693        return decodeURIComponent(percentEncode$1(bytes, start, end));
37694      },
37695      // return the string representation of the specified byte range,
37696      // interpreted as ISO-8859-1.
37697      parseIso88591$1 = function (bytes, start, end) {
37698        return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
37699      },
37700      parseSyncSafeInteger$1 = function (data) {
37701        return (data[0] << 21) | (data[1] << 14) | (data[2] << 7) | data[3];
37702      },
37703      frameParsers = {
37704        APIC: function (frame) {
37705          var i = 1,
37706            mimeTypeEndIndex,
37707            descriptionEndIndex,
37708            LINK_MIME_TYPE = '-->';
37709          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
37710            // ignore frames with unrecognized character encodings
37711            return;
37712          } // parsing fields [ID3v2.4.0 section 4.14.]
37713
37714          mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
37715          if (mimeTypeEndIndex < 0) {
37716            // malformed frame
37717            return;
37718          } // parsing Mime type field (terminated with \0)
37719
37720          frame.mimeType = parseIso88591$1(frame.data, i, mimeTypeEndIndex);
37721          i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
37722
37723          frame.pictureType = frame.data[i];
37724          i++;
37725          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
37726          if (descriptionEndIndex < 0) {
37727            // malformed frame
37728            return;
37729          } // parsing Description field (terminated with \0)
37730
37731          frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
37732          i = descriptionEndIndex + 1;
37733          if (frame.mimeType === LINK_MIME_TYPE) {
37734            // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
37735            frame.url = parseIso88591$1(frame.data, i, frame.data.length);
37736          } else {
37737            // parsing Picture Data field as binary data
37738            frame.pictureData = frame.data.subarray(i, frame.data.length);
37739          }
37740        },
37741        'T*': function (frame) {
37742          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
37743            // ignore frames with unrecognized character encodings
37744            return;
37745          } // parse text field, do not include null terminator in the frame value
37746          // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
37747
37748          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.]
37749
37750          frame.values = frame.value.split('\0');
37751        },
37752        TXXX: function (frame) {
37753          var descriptionEndIndex;
37754          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
37755            // ignore frames with unrecognized character encodings
37756            return;
37757          }
37758          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
37759          if (descriptionEndIndex === -1) {
37760            return;
37761          } // parse the text fields
37762
37763          frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
37764          // frames that allow different types of encoding contain terminated text
37765          // [ID3v2.4.0 section 4.]
37766
37767          frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(
37768            /\0*$/,
37769            ''
37770          );
37771          frame.data = frame.value;
37772        },
37773        'W*': function (frame) {
37774          // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
37775          // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
37776          frame.url = parseIso88591$1(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
37777        },
37778        WXXX: function (frame) {
37779          var descriptionEndIndex;
37780          if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
37781            // ignore frames with unrecognized character encodings
37782            return;
37783          }
37784          descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
37785          if (descriptionEndIndex === -1) {
37786            return;
37787          } // parse the description and URL fields
37788
37789          frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
37790          // if the value is followed by a string termination all the following information
37791          // should be ignored [ID3v2.4.0 section 4.3]
37792
37793          frame.url = parseIso88591$1(
37794            frame.data,
37795            descriptionEndIndex + 1,
37796            frame.data.length
37797          ).replace(/\0.*$/, '');
37798        },
37799        PRIV: function (frame) {
37800          var i;
37801          for (i = 0; i < frame.data.length; i++) {
37802            if (frame.data[i] === 0) {
37803              // parse the description and URL fields
37804              frame.owner = parseIso88591$1(frame.data, 0, i);
37805              break;
37806            }
37807          }
37808          frame.privateData = frame.data.subarray(i + 1);
37809          frame.data = frame.privateData;
37810        },
37811      };
37812    var parseId3Frames$1 = function (data) {
37813      var frameSize,
37814        frameHeader,
37815        frameStart = 10,
37816        tagSize = 0,
37817        frames = []; // If we don't have enough data for a header, 10 bytes,
37818      // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
37819
37820      if (
37821        data.length < 10 ||
37822        data[0] !== 'I'.charCodeAt(0) ||
37823        data[1] !== 'D'.charCodeAt(0) ||
37824        data[2] !== '3'.charCodeAt(0)
37825      ) {
37826        return;
37827      } // the frame size is transmitted as a 28-bit integer in the
37828      // last four bytes of the ID3 header.
37829      // The most significant bit of each byte is dropped and the
37830      // results concatenated to recover the actual value.
37831
37832      tagSize = parseSyncSafeInteger$1(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
37833      // convenient for our comparisons to include it
37834
37835      tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
37836
37837      var hasExtendedHeader = data[5] & 0x40;
37838      if (hasExtendedHeader) {
37839        // advance the frame start past the extended header
37840        frameStart += 4; // header size field
37841
37842        frameStart += parseSyncSafeInteger$1(data.subarray(10, 14));
37843        tagSize -= parseSyncSafeInteger$1(data.subarray(16, 20)); // clip any padding off the end
37844      } // parse one or more ID3 frames
37845      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
37846
37847      do {
37848        // determine the number of bytes in this frame
37849        frameSize = parseSyncSafeInteger$1(data.subarray(frameStart + 4, frameStart + 8));
37850        if (frameSize < 1) {
37851          break;
37852        }
37853        frameHeader = String.fromCharCode(
37854          data[frameStart],
37855          data[frameStart + 1],
37856          data[frameStart + 2],
37857          data[frameStart + 3]
37858        );
37859        var frame = {
37860          id: frameHeader,
37861          data: data.subarray(frameStart + 10, frameStart + frameSize + 10),
37862        };
37863        frame.key = frame.id; // parse frame values
37864
37865        if (frameParsers[frame.id]) {
37866          // use frame specific parser
37867          frameParsers[frame.id](frame);
37868        } else if (frame.id[0] === 'T') {
37869          // use text frame generic parser
37870          frameParsers['T*'](frame);
37871        } else if (frame.id[0] === 'W') {
37872          // use URL link frame generic parser
37873          frameParsers['W*'](frame);
37874        }
37875        frames.push(frame);
37876        frameStart += 10; // advance past the frame header
37877
37878        frameStart += frameSize; // advance past the frame body
37879      } while (frameStart < tagSize);
37880      return frames;
37881    };
37882    var parseId3 = {
37883      parseId3Frames: parseId3Frames$1,
37884      parseSyncSafeInteger: parseSyncSafeInteger$1,
37885      frameParsers: frameParsers,
37886    };
37887    /**
37888     * mux.js
37889     *
37890     * Copyright (c) Brightcove
37891     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
37892     *
37893     * Accepts program elementary stream (PES) data events and parses out
37894     * ID3 metadata from them, if present.
37895     * @see http://id3.org/id3v2.3.0
37896     */
37897
37898    var Stream$5 = stream,
37899      StreamTypes$3 = streamTypes,
37900      id3 = parseId3,
37901      MetadataStream;
37902    MetadataStream = function (options) {
37903      var settings = {
37904          // the bytes of the program-level descriptor field in MP2T
37905          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
37906          // program element descriptors"
37907          descriptor: options && options.descriptor,
37908        },
37909        // the total size in bytes of the ID3 tag being parsed
37910        tagSize = 0,
37911        // tag data that is not complete enough to be parsed
37912        buffer = [],
37913        // the total number of bytes currently in the buffer
37914        bufferSize = 0,
37915        i;
37916      MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
37917      // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
37918
37919      this.dispatchType = StreamTypes$3.METADATA_STREAM_TYPE.toString(16);
37920      if (settings.descriptor) {
37921        for (i = 0; i < settings.descriptor.length; i++) {
37922          this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
37923        }
37924      }
37925      this.push = function (chunk) {
37926        var tag, frameStart, frameSize, frame, i, frameHeader;
37927        if (chunk.type !== 'timed-metadata') {
37928          return;
37929        } // if data_alignment_indicator is set in the PES header,
37930        // we must have the start of a new ID3 tag. Assume anything
37931        // remaining in the buffer was malformed and throw it out
37932
37933        if (chunk.dataAlignmentIndicator) {
37934          bufferSize = 0;
37935          buffer.length = 0;
37936        } // ignore events that don't look like ID3 data
37937
37938        if (
37939          buffer.length === 0 &&
37940          (chunk.data.length < 10 ||
37941            chunk.data[0] !== 'I'.charCodeAt(0) ||
37942            chunk.data[1] !== 'D'.charCodeAt(0) ||
37943            chunk.data[2] !== '3'.charCodeAt(0))
37944        ) {
37945          this.trigger('log', {
37946            level: 'warn',
37947            message: 'Skipping unrecognized metadata packet',
37948          });
37949          return;
37950        } // add this chunk to the data we've collected so far
37951
37952        buffer.push(chunk);
37953        bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
37954
37955        if (buffer.length === 1) {
37956          // the frame size is transmitted as a 28-bit integer in the
37957          // last four bytes of the ID3 header.
37958          // The most significant bit of each byte is dropped and the
37959          // results concatenated to recover the actual value.
37960          tagSize = id3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
37961          // convenient for our comparisons to include it
37962
37963          tagSize += 10;
37964        } // if the entire frame has not arrived, wait for more data
37965
37966        if (bufferSize < tagSize) {
37967          return;
37968        } // collect the entire frame so it can be parsed
37969
37970        tag = {
37971          data: new Uint8Array(tagSize),
37972          frames: [],
37973          pts: buffer[0].pts,
37974          dts: buffer[0].dts,
37975        };
37976        for (i = 0; i < tagSize; ) {
37977          tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
37978          i += buffer[0].data.byteLength;
37979          bufferSize -= buffer[0].data.byteLength;
37980          buffer.shift();
37981        } // find the start of the first frame and the end of the tag
37982
37983        frameStart = 10;
37984        if (tag.data[5] & 0x40) {
37985          // advance the frame start past the extended header
37986          frameStart += 4; // header size field
37987
37988          frameStart += id3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
37989
37990          tagSize -= id3.parseSyncSafeInteger(tag.data.subarray(16, 20));
37991        } // parse one or more ID3 frames
37992        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
37993
37994        do {
37995          // determine the number of bytes in this frame
37996          frameSize = id3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
37997          if (frameSize < 1) {
37998            this.trigger('log', {
37999              level: 'warn',
38000              message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.',
38001            }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
38002            // to be sent along.
38003
38004            break;
38005          }
38006          frameHeader = String.fromCharCode(
38007            tag.data[frameStart],
38008            tag.data[frameStart + 1],
38009            tag.data[frameStart + 2],
38010            tag.data[frameStart + 3]
38011          );
38012          frame = {
38013            id: frameHeader,
38014            data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10),
38015          };
38016          frame.key = frame.id; // parse frame values
38017
38018          if (id3.frameParsers[frame.id]) {
38019            // use frame specific parser
38020            id3.frameParsers[frame.id](frame);
38021          } else if (frame.id[0] === 'T') {
38022            // use text frame generic parser
38023            id3.frameParsers['T*'](frame);
38024          } else if (frame.id[0] === 'W') {
38025            // use URL link frame generic parser
38026            id3.frameParsers['W*'](frame);
38027          } // handle the special PRIV frame used to indicate the star
vendor: 10,602 bytes, lines 38027-38287
38027t
38028          // time for raw AAC data
38029
38030          if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
38031            var d = frame.data,
38032              size =
38033                ((d[3] & 0x01) << 30) | (d[4] << 22) | (d[5] << 14) | (d[6] << 6) | (d[7] >>> 2);
38034            size *= 4;
38035            size += d[7] & 0x03;
38036            frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
38037            // on the value of this frame
38038            // we couldn't have known the appropriate pts and dts before
38039            // parsing this ID3 tag so set those values now
38040
38041            if (tag.pts === undefined && tag.dts === undefined) {
38042              tag.pts = frame.timeStamp;
38043              tag.dts = frame.timeStamp;
38044            }
38045            this.trigger('timestamp', frame);
38046          }
38047          tag.frames.push(frame);
38048          frameStart += 10; // advance past the frame header
38049
38050          frameStart += frameSize; // advance past the frame body
38051        } while (frameStart < tagSize);
38052        this.trigger('data', tag);
38053      };
38054    };
38055    MetadataStream.prototype = new Stream$5();
38056    var metadataStream = MetadataStream;
38057    /**
38058     * mux.js
38059     *
38060     * Copyright (c) Brightcove
38061     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
38062     *
38063     * A stream-based mp2t to mp4 converter. This utility can be used to
38064     * deliver mp4s to a SourceBuffer on platforms that support native
38065     * Media Source Extensions.
38066     */
38067
38068    var Stream$4 = stream,
38069      CaptionStream$1 = captionStream,
38070      StreamTypes$2 = streamTypes,
38071      TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
38072
38073    var TransportPacketStream, TransportParseStream, ElementaryStream; // constants
38074
38075    var MP2T_PACKET_LENGTH$1 = 188,
38076      // bytes
38077      SYNC_BYTE$1 = 0x47;
38078    /**
38079     * Splits an incoming stream of binary data into MPEG-2 Transport
38080     * Stream packets.
38081     */
38082
38083    TransportPacketStream = function () {
38084      var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
38085        bytesInBuffer = 0;
38086      TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
38087
38088      /**
38089       * Split a stream of data into M2TS packets
38090       **/
38091
38092      this.push = function (bytes) {
38093        var startIndex = 0,
38094          endIndex = MP2T_PACKET_LENGTH$1,
38095          everything; // If there are bytes remaining from the last segment, prepend them to the
38096        // bytes that were pushed in
38097
38098        if (bytesInBuffer) {
38099          everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
38100          everything.set(buffer.subarray(0, bytesInBuffer));
38101          everything.set(bytes, bytesInBuffer);
38102          bytesInBuffer = 0;
38103        } else {
38104          everything = bytes;
38105        } // While we have enough data for a packet
38106
38107        while (endIndex < everything.byteLength) {
38108          // Look for a pair of start and end sync bytes in the data..
38109          if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
38110            // We found a packet so emit it and jump one whole packet forward in
38111            // the stream
38112            this.trigger('data', everything.subarray(startIndex, endIndex));
38113            startIndex += MP2T_PACKET_LENGTH$1;
38114            endIndex += MP2T_PACKET_LENGTH$1;
38115            continue;
38116          } // If we get here, we have somehow become de-synchronized and we need to step
38117          // forward one byte at a time until we find a pair of sync bytes that denote
38118          // a packet
38119
38120          startIndex++;
38121          endIndex++;
38122        } // If there was some data left over at the end of the segment that couldn't
38123        // possibly be a whole packet, keep it because it might be the start of a packet
38124        // that continues in the next segment
38125
38126        if (startIndex < everything.byteLength) {
38127          buffer.set(everything.subarray(startIndex), 0);
38128          bytesInBuffer = everything.byteLength - startIndex;
38129        }
38130      };
38131      /**
38132       * Passes identified M2TS packets to the TransportParseStream to be parsed
38133       **/
38134
38135      this.flush = function () {
38136        // If the buffer contains a whole packet when we are being flushed, emit it
38137        // and empty the buffer. Otherwise hold onto the data because it may be
38138        // important for decoding the next segment
38139        if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
38140          this.trigger('data', buffer);
38141          bytesInBuffer = 0;
38142        }
38143        this.trigger('done');
38144      };
38145      this.endTimeline = function () {
38146        this.flush();
38147        this.trigger('endedtimeline');
38148      };
38149      this.reset = function () {
38150        bytesInBuffer = 0;
38151        this.trigger('reset');
38152      };
38153    };
38154    TransportPacketStream.prototype = new Stream$4();
38155    /**
38156     * Accepts an MP2T TransportPacketStream and emits data events with parsed
38157     * forms of the individual transport stream packets.
38158     */
38159
38160    TransportParseStream = function () {
38161      var parsePsi, parsePat, parsePmt, self;
38162      TransportParseStream.prototype.init.call(this);
38163      self = this;
38164      this.packetsWaitingForPmt = [];
38165      this.programMapTable = undefined;
38166      parsePsi = function (payload, psi) {
38167        var offset = 0; // PSI packets may be split into multiple sections and those
38168        // sections may be split into multiple packets. If a PSI
38169        // section starts in this packet, the payload_unit_start_indicator
38170        // will be true and the first byte of the payload will indicate
38171        // the offset from the current position to the start of the
38172        // section.
38173
38174        if (psi.payloadUnitStartIndicator) {
38175          offset += payload[offset] + 1;
38176        }
38177        if (psi.type === 'pat') {
38178          parsePat(payload.subarray(offset), psi);
38179        } else {
38180          parsePmt(payload.subarray(offset), psi);
38181        }
38182      };
38183      parsePat = function (payload, pat) {
38184        pat.section_number = payload[7]; // eslint-disable-line camelcase
38185
38186        pat.last_section_number = payload[8]; // eslint-disable-line camelcase
38187        // skip the PSI header and parse the first PMT entry
38188
38189        self.pmtPid = ((payload[10] & 0x1f) << 8) | payload[11];
38190        pat.pmtPid = self.pmtPid;
38191      };
38192      /**
38193       * Parse out the relevant fields of a Program Map Table (PMT).
38194       * @param payload {Uint8Array} the PMT-specific portion of an MP2T
38195       * packet. The first byte in this array should be the table_id
38196       * field.
38197       * @param pmt {object} the object that should be decorated with
38198       * fields parsed from the PMT.
38199       */
38200
38201      parsePmt = function (payload, pmt) {
38202        var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
38203        // take effect. We don't believe this should ever be the case
38204        // for HLS but we'll ignore "forward" PMT declarations if we see
38205        // them. Future PMT declarations have the current_next_indicator
38206        // set to zero.
38207
38208        if (!(payload[5] & 0x01)) {
38209          return;
38210        } // overwrite any existing program map table
38211
38212        self.programMapTable = {
38213          video: null,
38214          audio: null,
38215          'timed-metadata': {},
38216        }; // the mapping table ends at the end of the current section
38217
38218        sectionLength = ((payload[1] & 0x0f) << 8) | payload[2];
38219        tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
38220        // long the program info descriptors are
38221
38222        programInfoLength = ((payload[10] & 0x0f) << 8) | payload[11]; // advance the offset to the first entry in the mapping table
38223
38224        offset = 12 + programInfoLength;
38225        while (offset < tableEnd) {
38226          var streamType = payload[offset];
38227          var pid = ((payload[offset + 1] & 0x1f) << 8) | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
38228          // TODO: should this be done for metadata too? for now maintain behavior of
38229          //       multiple metadata streams
38230
38231          if (
38232            streamType === StreamTypes$2.H264_STREAM_TYPE &&
38233            self.programMapTable.video === null
38234          ) {
38235            self.programMapTable.video = pid;
38236          } else if (
38237            streamType === StreamTypes$2.ADTS_STREAM_TYPE &&
38238            self.programMapTable.audio === null
38239          ) {
38240            self.programMapTable.audio = pid;
38241          } else if (streamType === StreamTypes$2.METADATA_STREAM_TYPE) {
38242            // map pid to stream type for metadata streams
38243            self.programMapTable['timed-metadata'][pid] = streamType;
38244          } // move to the next table entry
38245          // skip past the elementary stream descriptors, if present
38246
38247          offset += (((payload[offset + 3] & 0x0f) << 8) | payload[offset + 4]) + 5;
38248        } // record the map on the packet as well
38249
38250        pmt.programMapTable = self.programMapTable;
38251      };
38252      /**
38253       * Deliver a new MP2T packet to the next stream in the pipeline.
38254       */
38255
38256      this.push = function (packet) {
38257        var result = {},
38258          offset = 4;
38259        result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
38260
38261        result.pid = packet[1] & 0x1f;
38262        result.pid <<= 8;
38263        result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
38264        // fifth byte of the TS packet header. The adaptation field is
38265        // used to add stuffing to PES packets that don't fill a complete
38266        // TS packet, and to specify some forms of timing and control data
38267        // that we do not currently use.
38268
38269        if ((packet[3] & 0x30) >>> 4 > 0x01) {
38270          offset += packet[offset] + 1;
38271        } // parse the rest of the packet based on the type
38272
38273        if (result.pid === 0) {
38274          result.type = 'pat';
38275          parsePsi(packet.subarray(offset), result);
38276          this.trigger('data', result);
38277        } else if (result.pid === this.pmtPid) {
38278          result.type = 'pmt';
38279          parsePsi(packet.subarray(offset), result);
38280          this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
38281
38282          while (this.packetsWaitingForPmt.length) {
38283            this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
38284          }
38285        } else if (this.programMapTable === undefined) {
38286          // When we have not seen a PMT yet, defer further processing of
38287          // PES packets until one has been parsed
38288          this.packetsWaitingForPmt.push([packet, offset, result]);
38289        } else {
38290          this.processPes_(packet, offset, result);
38291        }
38292      };
38293      this.processPes_ = function (packet, offset, result) {
38294        // set the appropriate stream type
38295        if (result.pid === this.programMapTable.video) {
38296          result.streamType = StreamTypes$2.H264_STREAM_TYPE;
38297        } else if (result.pid === this.programMapTable.audio) {
38298          result.streamType = StreamTypes$2.ADTS_STREAM_TYPE;
38299        } else {
38300          // if not video or audio, it is timed-metadata or unknown
38301          // if unknown, streamType will be undefined
38302          result.streamType = this.programMapTable['timed-metadata'][result.pid];
38303        }
38304        result.type = 'pes';
38305        result.data = packet.subarray(offset);
38306        this.trigger('data', result);
38307      };
38308    };
38309    TransportParseStream.prototype = new Stream$4();
38310    TransportParseStream.STREAM_TYPES = {
38311      h264: 0x1b,
38312      adts: 0x0f,
38313    };
38314    /**
38315     * Reconsistutes program elementary stream (PES) packets from parsed
38316     * transport stream packets. That is, if you pipe an
38317     * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
38318     * events will be events which capture the bytes for individual PES
38319     * packets plus relevant metadata that has been extracted from the
38320     * container.
38321     */
38322
38323    ElementaryStream = function () {
38324      var self = this,
38325        segmentHadPmt = false,
38326        // PES packet fragments
38327        video = {
38328          data: [],
38329          size: 0,
38330        },
38331        audio = {
38332          data: [],
38333          size: 0,
38334        },
38335        timedMetadata = {
38336          data: [],
38337          size: 0,
38338        },
38339        programMapTable,
38340        parsePes = function (payload, pes) {
38341          var ptsDtsFlags;
38342          const startPrefix = (payload[0] << 16) | (payload[1] << 8) | payload[2]; // default to an empty array
38343
38344          pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
38345          // that are frame data that is continuing from the previous fragment. This
38346          // is to check that the pes data is the start of a new pes payload
38347
38348          if (startPrefix !== 1) {
38349            return;
38350          } // get the packet length, this will be 0 for video
38351
38352          pes.packetLength = 6 + ((payload[4] << 8) | payload[5]); // find out if this packets starts a new keyframe
38353
38354          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
38355          // and a DTS value. Determine what combination of values is
38356          // available to work with.
38357
38358          ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
38359          // performs all bitwise operations on 32-bit integers but javascript
38360          // supports a much greater range (52-bits) of integer using standard
38361          // mathematical operations.
38362          // We construct a 31-bit value using bitwise operators over the 31
38363          // most significant bits and then multiply by 4 (equal to a left-shift
38364          // of 2) before we add the final 2 least significant bits of the
38365          // timestamp (equal to an OR.)
38366
38367          if (ptsDtsFlags & 0xc0) {
38368            // the PTS and DTS are not written out directly. For information
38369            // on how they are encoded, see
38370            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
38371            pes.pts =
38372              ((payload[9] & 0x0e) << 27) |
38373              ((payload[10] & 0xff) << 20) |
38374              ((payload[11] & 0xfe) << 12) |
38375              ((payload[12] & 0xff) << 5) |
38376              ((payload[13] & 0xfe) >>> 3);
38377            pes.pts *= 4; // Left shift by 2
38378
38379            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
38380
38381            pes.dts = pes.pts;
38382            if (ptsDtsFlags & 0x40) {
38383              pes.dts =
38384                ((payload[14] & 0x0e) << 27) |
38385                ((payload[15] & 0xff) << 20) |
38386                ((payload[16] & 0xfe) << 12) |
38387                ((payload[17] & 0xff) << 5) |
38388                ((payload[18] & 0xfe) >>> 3);
38389              pes.dts *= 4; // Left shift by 2
38390
38391              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
38392            }
38393          } // the data section starts immediately after the PES header.
38394          // pes_header_data_length specifies the number of header bytes
vendor: 6,079 bytes, lines 38395-38568
38395          // that follow the last byte of the field.
38396
38397          pes.data = payload.subarray(9 + payload[8]);
38398        },
38399        /**
38400         * Pass completely parsed PES packets to the next stream in the pipeline
38401         **/
38402        flushStream = function (stream, type, forceFlush) {
38403          var packetData = new Uint8Array(stream.size),
38404            event = {
38405              type: type,
38406            },
38407            i = 0,
38408            offset = 0,
38409            packetFlushable = false,
38410            fragment; // do nothing if there is not enough buffered data for a complete
38411          // PES header
38412
38413          if (!stream.data.length || stream.size < 9) {
38414            return;
38415          }
38416          event.trackId = stream.data[0].pid; // reassemble the packet
38417
38418          for (i = 0; i < stream.data.length; i++) {
38419            fragment = stream.data[i];
38420            packetData.set(fragment.data, offset);
38421            offset += fragment.data.byteLength;
38422          } // parse assembled packet's PES header
38423
38424          parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
38425          // check that there is enough stream data to fill the packet
38426
38427          packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
38428
38429          if (forceFlush || packetFlushable) {
38430            stream.size = 0;
38431            stream.data.length = 0;
38432          } // only emit packets that are complete. this is to avoid assembling
38433          // incomplete PES packets due to poor segmentation
38434
38435          if (packetFlushable) {
38436            self.trigger('data', event);
38437          }
38438        };
38439      ElementaryStream.prototype.init.call(this);
38440      /**
38441       * Identifies M2TS packet types and parses PES packets using metadata
38442       * parsed from the PMT
38443       **/
38444
38445      this.push = function (data) {
38446        ({
38447          pat: function () {
38448            // we have to wait for the PMT to arrive as well before we
38449            // have any meaningful metadata
38450          },
38451          pes: function () {
38452            var stream, streamType;
38453            switch (data.streamType) {
38454              case StreamTypes$2.H264_STREAM_TYPE:
38455                stream = video;
38456                streamType = 'video';
38457                break;
38458              case StreamTypes$2.ADTS_STREAM_TYPE:
38459                stream = audio;
38460                streamType = 'audio';
38461                break;
38462              case StreamTypes$2.METADATA_STREAM_TYPE:
38463                stream = timedMetadata;
38464                streamType = 'timed-metadata';
38465                break;
38466              default:
38467                // ignore unknown stream types
38468                return;
38469            } // if a new packet is starting, we can flush the completed
38470            // packet
38471
38472            if (data.payloadUnitStartIndicator) {
38473              flushStream(stream, streamType, true);
38474            } // buffer this fragment until we are sure we've received the
38475            // complete payload
38476
38477            stream.data.push(data);
38478            stream.size += data.data.byteLength;
38479          },
38480          pmt: function () {
38481            var event = {
38482              type: 'metadata',
38483              tracks: [],
38484            };
38485            programMapTable = data.programMapTable; // translate audio and video streams to tracks
38486
38487            if (programMapTable.video !== null) {
38488              event.tracks.push({
38489                timelineStartInfo: {
38490                  baseMediaDecodeTime: 0,
38491                },
38492                id: +programMapTable.video,
38493                codec: 'avc',
38494                type: 'video',
38495              });
38496            }
38497            if (programMapTable.audio !== null) {
38498              event.tracks.push({
38499                timelineStartInfo: {
38500                  baseMediaDecodeTime: 0,
38501                },
38502                id: +programMapTable.audio,
38503                codec: 'adts',
38504                type: 'audio',
38505              });
38506            }
38507            segmentHadPmt = true;
38508            self.trigger('data', event);
38509          },
38510        })[data.type]();
38511      };
38512      this.reset = function () {
38513        video.size = 0;
38514        video.data.length = 0;
38515        audio.size = 0;
38516        audio.data.length = 0;
38517        this.trigger('reset');
38518      };
38519      /**
38520       * Flush any remaining input. Video PES packets may be of variable
38521       * length. Normally, the start of a new video packet can trigger the
38522       * finalization of the previous packet. That is not possible if no
38523       * more video is forthcoming, however. In that case, some other
38524       * mechanism (like the end of the file) has to be employed. When it is
38525       * clear that no additional data is forthcoming, calling this method
38526       * will flush the buffered packets.
38527       */
38528
38529      this.flushStreams_ = function () {
38530        // !!THIS ORDER IS IMPORTANT!!
38531        // video first then audio
38532        flushStream(video, 'video');
38533        flushStream(audio, 'audio');
38534        flushStream(timedMetadata, 'timed-metadata');
38535      };
38536      this.flush = function () {
38537        // if on flush we haven't had a pmt emitted
38538        // and we have a pmt to emit. emit the pmt
38539        // so that we trigger a trackinfo downstream.
38540        if (!segmentHadPmt && programMapTable) {
38541          var pmt = {
38542            type: 'metadata',
38543            tracks: [],
38544          }; // translate audio and video streams to tracks
38545
38546          if (programMapTable.video !== null) {
38547            pmt.tracks.push({
38548              timelineStartInfo: {
38549                baseMediaDecodeTime: 0,
38550              },
38551              id: +programMapTable.video,
38552              codec: 'avc',
38553              type: 'video',
38554            });
38555          }
38556          if (programMapTable.audio !== null) {
38557            pmt.tracks.push({
38558              timelineStartInfo: {
38559                baseMediaDecodeTime: 0,
38560              },
38561              id: +programMapTable.audio,
38562              codec: 'adts',
38563              type: 'audio',
38564            });
38565          }
38566          self.trigger('data', pmt);
38567        }
38568        segmentHadPmt = false;
38569        this.flushStreams_();
38570        this.trigger('done');
38571      };
38572    };
38573    ElementaryStream.prototype = new Stream$4();
38574    var m2ts$1 = {
38575      PAT_PID: 0x0000,
38576      MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
38577      TransportPacketStream: TransportPacketStream,
38578      TransportParseStream: TransportParseStream,
38579      ElementaryStream: ElementaryStream,
38580      TimestampRolloverStream: TimestampRolloverStream,
38581      CaptionStream: CaptionStream$1.CaptionStream,
38582      Cea608Stream: CaptionStream$1.Cea608Stream,
38583      Cea708Stream: CaptionStream$1.Cea708Stream,
38584      MetadataStream: metadataStream,
38585    };
38586    for (var type in StreamTypes$2) {
38587      if (StreamTypes$2.hasOwnProperty(type)) {
38588        m2ts$1[type] = StreamTypes$2[type];
38589      }
38590    }
38591    var m2ts_1 = m2ts$1;
38592    /**
38593     * mux.js
38594     *
38595     * Copyright (c) Brightcove
38596     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
38597     */
38598
38599    var Stream$3 = stream;
38600    var ONE_SECOND_IN_TS$2 = clock$2.ONE_SECOND_IN_TS;
38601    var AdtsStream$1;
38602    var ADTS_SAMPLING_FREQUENCIES$1 = [
38603      96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
38604    ];
38605    /*
38606     * Accepts a ElementaryStream and emits data events with parsed
38607     * AAC Audio Frames of the individual packets. Input audio in ADTS
38608     * format is unpacked and re-emitted as AAC frames.
38609     *
38610     * @see http://wiki.multimedia.cx/index.php?title=ADTS
38611     * @see http://wiki.multimedia.cx/?title=Understanding_AAC
38612     */
38613
38614    AdtsStream$1 = function (handlePartialSegments) {
38615      var buffer,
38616        frameNum = 0;
38617      AdtsStream$1.prototype.init.call(this);
38618      this.skipWarn_ = function (start, end) {
38619        this.trigger('log', {
38620          level: 'warn',
38621          message: `adts skiping bytes ${start} to ${end} in frame ${frameNum} outside syncword`,
38622        });
38623      };
38624      this.push = function (packet) {
38625        var i = 0,
38626          frameLength,
38627          protectionSkipBytes,
38628          oldBuffer,
38629          sampleCount,
38630          adtsFrameDuration;
38631        if (!handlePartialSegments) {
38632          frameNum = 0;
38633        }
38634        if (packet.type !== 'audio') {
38635          // ignore non-audio data
38636          return;
38637        } // Prepend any data in the buffer to the input data so that we can parse
38638        // aac frames the cross a PES packet boundary
38639
38640        if (buffer && buffer.length) {
38641          oldBuffer = buffer;
38642          buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
38643          buffer.set(oldBuffer);
38644          buffer.set(packet.data, oldBuffer.byteLength);
38645        } else {
38646          buffer = packet.data;
38647        } // unpack any ADTS frames which have been fully received
38648        // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
38649
38650        var skip; // We use i + 7 here because we want to be able to parse the entire header.
38651        // If we don't have enough bytes to do that, then we definitely won't have a full frame.
38652
38653        while (i + 7 < buffer.length) {
38654          // Look for the start of an ADTS header..
38655          if (buffer[i] !== 0xff || (buffer[i + 1] & 0xf6) !== 0xf0) {
38656            if (typeof skip !== 'number') {
38657              skip = i;
38658            } // If a valid header was not found,  jump one forward and attempt to
38659            // find a valid ADTS header starting at the next byte
38660
38661            i++;
38662            continue;
38663          }
38664          if (typeof skip === 'number') {
38665            this.skipWarn_(skip, i);
38666            skip = null;
38667          } // The protection skip bit tells us if we have 2 bytes of CRC data at the
38668          // end of the ADTS header
38669
38670          protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
38671          // end of the sync sequence
38672          // NOTE: frame length includes the size of the header
38673
38674          frameLength =
38675            ((buffer[i + 3] & 0x03) << 11) | (buffer[i + 4] << 3) | ((buffer[i + 5] & 0xe0) >> 5);
38676          sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
38677          adtsFrameDuration =
38678            (sampleCount * ONE_SECOND_IN_TS$2) /
38679            ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
38680          // then we have to wait for more data
38681
38682          if (buffer.byteLength - i < frameLength) {
38683            break;
38684          } // Otherwise, deliver the complete AAC frame
38685
38686          this.trigger('data', {
38687            pts: packet.pts + frameNum * adtsFrameDuration,
38688            dts: packet.dts + frameNum * adtsFrameDuration,
38689            sampleCount: sampleCount,
38690            audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1,
38691            channelcount: ((buffer[i + 2] & 1) << 2) | ((buffer[i + 3] & 0xc0) >>> 6),
38692            samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
38693            samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
38694            // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
38695            samplesize: 16,
38696            // data is the frame without it's header
38697            data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength),
38698          });
38699          frameNum++;
38700          i += frameLength;
38701        }
38702        if (typeof skip === 'number') {
38703          this.skipWarn_(skip, i);
38704          skip = null;
38705        } // remove processed bytes from the buffer.
38706
38707        buffer = buffer.subarray(i);
38708      };
38709      this.flush = function () {
38710        frameNum = 0;
38711        this.trigger('done');
38712      };
38713      this.reset = function () {
38714        buffer = void 0;
38715        this.trigger('reset');
38716      };
38717      this.endTimeline = function () {
38718        buffer = void 0;
38719        this.trigger('endedtimeline');
38720      };
38721    };
38722    AdtsStream$1.prototype = new Stream$3();
38723    var adts = AdtsStream$1;
38724    /**
38725     * mux.js
38726     *
38727     * Copyright (c) Brightcove
38728     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
38729     */
38730
38731    var ExpGolomb$1;
38732    /**
38733     * Parser for exponential Golomb codes, a variable-bitwidth number encoding
38734     * scheme used by h264.
38735     */
38736
38737    ExpGolomb$1 = function (workingData) {
38738      var // the number of bytes left to examine in workingData
38739        workingBytesAvailable = workingData.byteLength,
38740        // the current word being examined
38741        workingWord = 0,
38742        // :uint
38743        // the number of bits left to examine in the current word
38744        workingBitsAvailable = 0; // :uint;
38745      // ():uint
38746
38747      this.length = function () {
38748        return 8 * workingBytesAvailable;
38749      }; // ():uint
38750
38751      this.bitsAvailable = function () {
38752        return 8 * workingBytesAvailable + workingBitsAvailable;
38753      }; // ():void
38754
38755      this.loadWord = function () {
38756        var position = workingData.byteLength - workingBytesAvailable,
38757          workingBytes = new Uint8Array(4),
38758          availableBytes = Math.min(4, workingBytesAvailable);
38759        if (availableBytes === 0) {
38760          throw new Error('no bytes available');
38761        }
38762        workingBytes.set(workingData.subarray(position, position + availableBytes));
38763        workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
38764
38765        workingBitsAvailable = availableBytes * 8;
38766        workingBytesAvailable -= availableBytes;
38767      }; // (count:int):void
38768
38769      this.skipBits = function (count) {
38770        var skipBytes; // :int
38771
38772        if (workingBitsAvailable > count) {
38773          workingWord <<= count;
38774          workingBitsAvailable -= count;
38775        } else {
38776          count -= workingBitsAvailable;
38777          skipBytes = Math.floor(count / 8);
38778          count -= skipBytes * 8;
38779          workingBytesAvailable -= skipBytes;
38780          this.loadWord();
38781          workingWord <<= count;
38782          workingBitsAvailable -= count;
38783        }
38784      }; // (size:int):uint
38785
38786      this.readBits = function (size) {
38787        var bits = Math.min(workingBitsAvailable, size),
38788          // :uint
38789          valu = workingWord >>> (32 - bits); // :uint
38790        // if size > 31, handle error
38791
38792        workingBitsAvailable -= bits;
38793        if (workingBitsAvailable > 0) {
38794          workingWord <<= bits;
38795        } else if (workingBytesAvailable > 0) {
38796          this.loadWord();
38797        }
38798        bits = size - bits;
38799        if (bits > 0) {
38800          return (valu << bits) | this.readBits(bits);
38801        }
38802        return valu;
38803      }; // ():uint
38804
38805      this.skipLeadingZeros = function () {
38806        var leadingZeroCount; // :uint
38807
38808        for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable;
vendor: 5,870 bytes, lines 38808-38996
38808 ++leadingZeroCount) {
38809          if ((workingWord & (0x80000000 >>> leadingZeroCount)) !== 0) {
38810            // the first bit of working word is 1
38811            workingWord <<= leadingZeroCount;
38812            workingBitsAvailable -= leadingZeroCount;
38813            return leadingZeroCount;
38814          }
38815        } // we exhausted workingWord and still have not found a 1
38816
38817        this.loadWord();
38818        return leadingZeroCount + this.skipLeadingZeros();
38819      }; // ():void
38820
38821      this.skipUnsignedExpGolomb = function () {
38822        this.skipBits(1 + this.skipLeadingZeros());
38823      }; // ():void
38824
38825      this.skipExpGolomb = function () {
38826        this.skipBits(1 + this.skipLeadingZeros());
38827      }; // ():uint
38828
38829      this.readUnsignedExpGolomb = function () {
38830        var clz = this.skipLeadingZeros(); // :uint
38831
38832        return this.readBits(clz + 1) - 1;
38833      }; // ():int
38834
38835      this.readExpGolomb = function () {
38836        var valu = this.readUnsignedExpGolomb(); // :int
38837
38838        if (0x01 & valu) {
38839          // the number is odd if the low order bit is set
38840          return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
38841        }
38842        return -1 * (valu >>> 1); // divide by two then make it negative
38843      }; // Some convenience functions
38844      // :Boolean
38845
38846      this.readBoolean = function () {
38847        return this.readBits(1) === 1;
38848      }; // ():int
38849
38850      this.readUnsignedByte = function () {
38851        return this.readBits(8);
38852      };
38853      this.loadWord();
38854    };
38855    var expGolomb = ExpGolomb$1;
38856    /**
38857     * mux.js
38858     *
38859     * Copyright (c) Brightcove
38860     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
38861     */
38862
38863    var Stream$2 = stream;
38864    var ExpGolomb = expGolomb;
38865    var H264Stream$1, NalByteStream;
38866    var PROFILES_WITH_OPTIONAL_SPS_DATA;
38867    /**
38868     * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
38869     */
38870
38871    NalByteStream = function () {
38872      var syncPoint = 0,
38873        i,
38874        buffer;
38875      NalByteStream.prototype.init.call(this);
38876      /*
38877       * Scans a byte stream and triggers a data event with the NAL units found.
38878       * @param {Object} data Event received from H264Stream
38879       * @param {Uint8Array} data.data The h264 byte stream to be scanned
38880       *
38881       * @see H264Stream.push
38882       */
38883
38884      this.push = function (data) {
38885        var swapBuffer;
38886        if (!buffer) {
38887          buffer = data.data;
38888        } else {
38889          swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
38890          swapBuffer.set(buffer);
38891          swapBuffer.set(data.data, buffer.byteLength);
38892          buffer = swapBuffer;
38893        }
38894        var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
38895        // scan for NAL unit boundaries
38896        // a match looks like this:
38897        // 0 0 1 .. NAL .. 0 0 1
38898        // ^ sync point        ^ i
38899        // or this:
38900        // 0 0 1 .. NAL .. 0 0 0
38901        // ^ sync point        ^ i
38902        // advance the sync point to a NAL start, if necessary
38903
38904        for (; syncPoint < len - 3; syncPoint++) {
38905          if (buffer[syncPoint + 2] === 1) {
38906            // the sync point is properly aligned
38907            i = syncPoint + 5;
38908            break;
38909          }
38910        }
38911        while (i < len) {
38912          // look at the current byte to determine if we've hit the end of
38913          // a NAL unit boundary
38914          switch (buffer[i]) {
38915            case 0:
38916              // skip past non-sync sequences
38917              if (buffer[i - 1] !== 0) {
38918                i += 2;
38919                break;
38920              } else if (buffer[i - 2] !== 0) {
38921                i++;
38922                break;
38923              } // deliver the NAL unit if it isn't empty
38924
38925              if (syncPoint + 3 !== i - 2) {
38926                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
38927              } // drop trailing zeroes
38928
38929              do {
38930                i++;
38931              } while (buffer[i] !== 1 && i < len);
38932              syncPoint = i - 2;
38933              i += 3;
38934              break;
38935            case 1:
38936              // skip past non-sync sequences
38937              if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
38938                i += 3;
38939                break;
38940              } // deliver the NAL unit
38941
38942              this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
38943              syncPoint = i - 2;
38944              i += 3;
38945              break;
38946            default:
38947              // the current byte isn't a one or zero, so it cannot be part
38948              // of a sync sequence
38949              i += 3;
38950              break;
38951          }
38952        } // filter out the NAL units that were delivered
38953
38954        buffer = buffer.subarray(syncPoint);
38955        i -= syncPoint;
38956        syncPoint = 0;
38957      };
38958      this.reset = function () {
38959        buffer = null;
38960        syncPoint = 0;
38961        this.trigger('reset');
38962      };
38963      this.flush = function () {
38964        // deliver the last buffered NAL unit
38965        if (buffer && buffer.byteLength > 3) {
38966          this.trigger('data', buffer.subarray(syncPoint + 3));
38967        } // reset the stream state
38968
38969        buffer = null;
38970        syncPoint = 0;
38971        this.trigger('done');
38972      };
38973      this.endTimeline = function () {
38974        this.flush();
38975        this.trigger('endedtimeline');
38976      };
38977    };
38978    NalByteStream.prototype = new Stream$2(); // values of profile_idc that indicate additional fields are included in the SPS
38979    // see Recommendation ITU-T H.264 (4/2013),
38980    // 7.3.2.1.1 Sequence parameter set data syntax
38981
38982    PROFILES_WITH_OPTIONAL_SPS_DATA = {
38983      100: true,
38984      110: true,
38985      122: true,
38986      244: true,
38987      44: true,
38988      83: true,
38989      86: true,
38990      118: true,
38991      128: true,
38992      // TODO: the three profiles below don't
38993      // appear to have sps data in the specificiation anymore?
38994      138: true,
38995      139: true,
38996      134: true,
vendor: 1,799 bytes, lines 38996-39050
38996
38997    };
38998    /**
38999     * Accepts input from a ElementaryStream and produces H.264 NAL unit data
39000     * events.
39001     */
39002
39003    H264Stream$1 = function () {
39004      var nalByteStream = new NalByteStream(),
39005        self,
39006        trackId,
39007        currentPts,
39008        currentDts,
39009        discardEmulationPreventionBytes,
39010        readSequenceParameterSet,
39011        skipScalingList;
39012      H264Stream$1.prototype.init.call(this);
39013      self = this;
39014      /*
39015       * Pushes a packet from a stream onto the NalByteStream
39016       *
39017       * @param {Object} packet - A packet received from a stream
39018       * @param {Uint8Array} packet.data - The raw bytes of the packet
39019       * @param {Number} packet.dts - Decode timestamp of the packet
39020       * @param {Number} packet.pts - Presentation timestamp of the packet
39021       * @param {Number} packet.trackId - The id of the h264 track this packet came from
39022       * @param {('video'|'audio')} packet.type - The type of packet
39023       *
39024       */
39025
39026      this.push = function (packet) {
39027        if (packet.type !== 'video') {
39028          return;
39029        }
39030        trackId = packet.trackId;
39031        currentPts = packet.pts;
39032        currentDts = packet.dts;
39033        nalByteStream.push(packet);
39034      };
39035      /*
39036       * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
39037       * for the NALUs to the next stream component.
39038       * Also, preprocess caption and sequence parameter NALUs.
39039       *
39040       * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
39041       * @see NalByteStream.push
39042       */
39043
39044      nalByteStream.on('data', function (data) {
39045        var event = {
39046          trackId: trackId,
39047          pts: currentPts,
39048          dts: currentDts,
39049          data: data,
39050          nalUnitTypeCode: data[0] & 0x1f
vendor: 41,398 bytes, lines 39050-40169
39050,
39051        };
39052        switch (event.nalUnitTypeCode) {
39053          case 0x05:
39054            event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
39055            break;
39056          case 0x06:
39057            event.nalUnitType = 'sei_rbsp';
39058            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
39059            break;
39060          case 0x07:
39061            event.nalUnitType = 'seq_parameter_set_rbsp';
39062            event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
39063            event.config = readSequenceParameterSet(event.escapedRBSP);
39064            break;
39065          case 0x08:
39066            event.nalUnitType = 'pic_parameter_set_rbsp';
39067            break;
39068          case 0x09:
39069            event.nalUnitType = 'access_unit_delimiter_rbsp';
39070            break;
39071        } // This triggers data on the H264Stream
39072
39073        self.trigger('data', event);
39074      });
39075      nalByteStream.on('done', function () {
39076        self.trigger('done');
39077      });
39078      nalByteStream.on('partialdone', function () {
39079        self.trigger('partialdone');
39080      });
39081      nalByteStream.on('reset', function () {
39082        self.trigger('reset');
39083      });
39084      nalByteStream.on('endedtimeline', function () {
39085        self.trigger('endedtimeline');
39086      });
39087      this.flush = function () {
39088        nalByteStream.flush();
39089      };
39090      this.partialFlush = function () {
39091        nalByteStream.partialFlush();
39092      };
39093      this.reset = function () {
39094        nalByteStream.reset();
39095      };
39096      this.endTimeline = function () {
39097        nalByteStream.endTimeline();
39098      };
39099      /**
39100       * Advance the ExpGolomb decoder past a scaling list. The scaling
39101       * list is optionally transmitted as part of a sequence parameter
39102       * set and is not relevant to transmuxing.
39103       * @param count {number} the number of entries in this scaling list
39104       * @param expGolombDecoder {object} an ExpGolomb pointed to the
39105       * start of a scaling list
39106       * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
39107       */
39108
39109      skipScalingList = function (count, expGolombDecoder) {
39110        var lastScale = 8,
39111          nextScale = 8,
39112          j,
39113          deltaScale;
39114        for (j = 0; j < count; j++) {
39115          if (nextScale !== 0) {
39116            deltaScale = expGolombDecoder.readExpGolomb();
39117            nextScale = (lastScale + deltaScale + 256) % 256;
39118          }
39119          lastScale = nextScale === 0 ? lastScale : nextScale;
39120        }
39121      };
39122      /**
39123       * Expunge any "Emulation Prevention" bytes from a "Raw Byte
39124       * Sequence Payload"
39125       * @param data {Uint8Array} the bytes of a RBSP from a NAL
39126       * unit
39127       * @return {Uint8Array} the RBSP without any Emulation
39128       * Prevention Bytes
39129       */
39130
39131      discardEmulationPreventionBytes = function (data) {
39132        var length = data.byteLength,
39133          emulationPreventionBytesPositions = [],
39134          i = 1,
39135          newLength,
39136          newData; // Find all `Emulation Prevention Bytes`
39137
39138        while (i < length - 2) {
39139          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
39140            emulationPreventionBytesPositions.push(i + 2);
39141            i += 2;
39142          } else {
39143            i++;
39144          }
39145        } // If no Emulation Prevention Bytes were found just return the original
39146        // array
39147
39148        if (emulationPreventionBytesPositions.length === 0) {
39149          return data;
39150        } // Create a new array to hold the NAL unit data
39151
39152        newLength = length - emulationPreventionBytesPositions.length;
39153        newData = new Uint8Array(newLength);
39154        var sourceIndex = 0;
39155        for (i = 0; i < newLength; sourceIndex++, i++) {
39156          if (sourceIndex === emulationPreventionBytesPositions[0]) {
39157            // Skip this byte
39158            sourceIndex++; // Remove this position index
39159
39160            emulationPreventionBytesPositions.shift();
39161          }
39162          newData[i] = data[sourceIndex];
39163        }
39164        return newData;
39165      };
39166      /**
39167       * Read a sequence parameter set and return some interesting video
39168       * properties. A sequence parameter set is the H264 metadata that
39169       * describes the properties of upcoming video frames.
39170       * @param data {Uint8Array} the bytes of a sequence parameter set
39171       * @return {object} an object with configuration parsed from the
39172       * sequence parameter set, including the dimensions of the
39173       * associated video frames.
39174       */
39175
39176      readSequenceParameterSet = function (data) {
39177        var frameCropLeftOffset = 0,
39178          frameCropRightOffset = 0,
39179          frameCropTopOffset = 0,
39180          frameCropBottomOffset = 0,
39181          expGolombDecoder,
39182          profileIdc,
39183          levelIdc,
39184          profileCompatibility,
39185          chromaFormatIdc,
39186          picOrderCntType,
39187          numRefFramesInPicOrderCntCycle,
39188          picWidthInMbsMinus1,
39189          picHeightInMapUnitsMinus1,
39190          frameMbsOnlyFlag,
39191          scalingListCount,
39192          sarRatio = [1, 1],
39193          aspectRatioIdc,
39194          i;
39195        expGolombDecoder = new ExpGolomb(data);
39196        profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
39197
39198        profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
39199
39200        levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
39201
39202        expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
39203        // some profiles have more optional data we don't need
39204
39205        if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
39206          chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
39207          if (chromaFormatIdc === 3) {
39208            expGolombDecoder.skipBits(1); // separate_colour_plane_flag
39209          }
39210          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
39211
39212          expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
39213
39214          expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
39215
39216          if (expGolombDecoder.readBoolean()) {
39217            // seq_scaling_matrix_present_flag
39218            scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
39219            for (i = 0; i < scalingListCount; i++) {
39220              if (expGolombDecoder.readBoolean()) {
39221                // seq_scaling_list_present_flag[ i ]
39222                if (i < 6) {
39223                  skipScalingList(16, expGolombDecoder);
39224                } else {
39225                  skipScalingList(64, expGolombDecoder);
39226                }
39227              }
39228            }
39229          }
39230        }
39231        expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
39232
39233        picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
39234        if (picOrderCntType === 0) {
39235          expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
39236        } else if (picOrderCntType === 1) {
39237          expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
39238
39239          expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
39240
39241          expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
39242
39243          numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
39244          for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
39245            expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
39246          }
39247        }
39248        expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
39249
39250        expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
39251
39252        picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
39253        picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
39254        frameMbsOnlyFlag = expGolombDecoder.readBits(1);
39255        if (frameMbsOnlyFlag === 0) {
39256          expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
39257        }
39258        expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
39259
39260        if (expGolombDecoder.readBoolean()) {
39261          // frame_cropping_flag
39262          frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
39263          frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
39264          frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
39265          frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
39266        }
39267        if (expGolombDecoder.readBoolean()) {
39268          // vui_parameters_present_flag
39269          if (expGolombDecoder.readBoolean()) {
39270            // aspect_ratio_info_present_flag
39271            aspectRatioIdc = expGolombDecoder.readUnsignedByte();
39272            switch (aspectRatioIdc) {
39273              case 1:
39274                sarRatio = [1, 1];
39275                break;
39276              case 2:
39277                sarRatio = [12, 11];
39278                break;
39279              case 3:
39280                sarRatio = [10, 11];
39281                break;
39282              case 4:
39283                sarRatio = [16, 11];
39284                break;
39285              case 5:
39286                sarRatio = [40, 33];
39287                break;
39288              case 6:
39289                sarRatio = [24, 11];
39290                break;
39291              case 7:
39292                sarRatio = [20, 11];
39293                break;
39294              case 8:
39295                sarRatio = [32, 11];
39296                break;
39297              case 9:
39298                sarRatio = [80, 33];
39299                break;
39300              case 10:
39301                sarRatio = [18, 11];
39302                break;
39303              case 11:
39304                sarRatio = [15, 11];
39305                break;
39306              case 12:
39307                sarRatio = [64, 33];
39308                break;
39309              case 13:
39310                sarRatio = [160, 99];
39311                break;
39312              case 14:
39313                sarRatio = [4, 3];
39314                break;
39315              case 15:
39316                sarRatio = [3, 2];
39317                break;
39318              case 16:
39319                sarRatio = [2, 1];
39320                break;
39321              case 255: {
39322                sarRatio = [
39323                  (expGolombDecoder.readUnsignedByte() << 8) | expGolombDecoder.readUnsignedByte(),
39324                  (expGolombDecoder.readUnsignedByte() << 8) | expGolombDecoder.readUnsignedByte(),
39325                ];
39326                break;
39327              }
39328            }
39329            if (sarRatio) {
39330              sarRatio[0] / sarRatio[1];
39331            }
39332          }
39333        }
39334        return {
39335          profileIdc: profileIdc,
39336          levelIdc: levelIdc,
39337          profileCompatibility: profileCompatibility,
39338          width:
39339            (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
39340          height:
39341            (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 -
39342            frameCropTopOffset * 2 -
39343            frameCropBottomOffset * 2,
39344          // sar is sample aspect ratio
39345          sarRatio: sarRatio,
39346        };
39347      };
39348    };
39349    H264Stream$1.prototype = new Stream$2();
39350    var h264 = {
39351      H264Stream: H264Stream$1,
39352      NalByteStream: NalByteStream,
39353    };
39354    /**
39355     * mux.js
39356     *
39357     * Copyright (c) Brightcove
39358     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39359     *
39360     * Utilities to detect basic properties and metadata about Aac data.
39361     */
39362
39363    var ADTS_SAMPLING_FREQUENCIES = [
39364      96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
39365    ];
39366    var parseId3TagSize = function (header, byteIndex) {
39367      var returnSize =
39368          (header[byteIndex + 6] << 21) |
39369          (header[byteIndex + 7] << 14) |
39370          (header[byteIndex + 8] << 7) |
39371          header[byteIndex + 9],
39372        flags = header[byteIndex + 5],
39373        footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
39374
39375      returnSize = returnSize >= 0 ? returnSize : 0;
39376      if (footerPresent) {
39377        return returnSize + 20;
39378      }
39379      return returnSize + 10;
39380    };
39381    var getId3Offset = function (data, offset) {
39382      if (
39383        data.length - offset < 10 ||
39384        data[offset] !== 'I'.charCodeAt(0) ||
39385        data[offset + 1] !== 'D'.charCodeAt(0) ||
39386        data[offset + 2] !== '3'.charCodeAt(0)
39387      ) {
39388        return offset;
39389      }
39390      offset += parseId3TagSize(data, offset);
39391      return getId3Offset(data, offset);
39392    }; // TODO: use vhs-utils
39393
39394    var isLikelyAacData$1 = function (data) {
39395      var offset = getId3Offset(data, 0);
39396      return (
39397        data.length >= offset + 2 &&
39398        (data[offset] & 0xff) === 0xff &&
39399        (data[offset + 1] & 0xf0) === 0xf0 &&
39400        // verify that the 2 layer bits are 0, aka this
39401        // is not mp3 data but aac data.
39402        (data[offset + 1] & 0x16) === 0x10
39403      );
39404    };
39405    var parseSyncSafeInteger = function (data) {
39406      return (data[0] << 21) | (data[1] << 14) | (data[2] << 7) | data[3];
39407    }; // return a percent-encoded representation of the specified byte range
39408    // @see http://en.wikipedia.org/wiki/Percent-encoding
39409
39410    var percentEncode = function (bytes, start, end) {
39411      var i,
39412        result = '';
39413      for (i = start; i < end; i++) {
39414        result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
39415      }
39416      return result;
39417    }; // return the string representation of the specified byte range,
39418    // interpreted as ISO-8859-1.
39419
39420    var parseIso88591 = function (bytes, start, end) {
39421      return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
39422    };
39423    var parseAdtsSize = function (header, byteIndex) {
39424      var lowThree = (header[byteIndex + 5] & 0xe0) >> 5,
39425        middle = header[byteIndex + 4] << 3,
39426        highTwo = header[byteIndex + 3] & (0x3 << 11);
39427      return highTwo | middle | lowThree;
39428    };
39429    var parseType$5 = function (header, byteIndex) {
39430      if (
39431        header[byteIndex] === 'I'.charCodeAt(0) &&
39432        header[byteIndex + 1] === 'D'.charCodeAt(0) &&
39433        header[byteIndex + 2] === '3'.charCodeAt(0)
39434      ) {
39435        return 'timed-metadata';
39436      } else if (header[byteIndex] & (0xff === 0xff) && (header[byteIndex + 1] & 0xf0) === 0xf0) {
39437        return 'audio';
39438      }
39439      return null;
39440    };
39441    var parseSampleRate = function (packet) {
39442      var i = 0;
39443      while (i + 5 < packet.length) {
39444        if (packet[i] !== 0xff || (packet[i + 1] & 0xf6) !== 0xf0) {
39445          // If a valid header was not found,  jump one forward and attempt to
39446          // find a valid ADTS header starting at the next byte
39447          i++;
39448          continue;
39449        }
39450        return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
39451      }
39452      return null;
39453    };
39454    var parseAacTimestamp = function (packet) {
39455      var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
39456
39457      frameStart = 10;
39458      if (packet[5] & 0x40) {
39459        // advance the frame start past the extended header
39460        frameStart += 4; // header size field
39461
39462        frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
39463      } // parse one or more ID3 frames
39464      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
39465
39466      do {
39467        // determine the number of bytes in this frame
39468        frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
39469        if (frameSize < 1) {
39470          return null;
39471        }
39472        frameHeader = String.fromCharCode(
39473          packet[frameStart],
39474          packet[frameStart + 1],
39475          packet[frameStart + 2],
39476          packet[frameStart + 3]
39477        );
39478        if (frameHeader === 'PRIV') {
39479          frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
39480          for (var i = 0; i < frame.byteLength; i++) {
39481            if (frame[i] === 0) {
39482              var owner = parseIso88591(frame, 0, i);
39483              if (owner === 'com.apple.streaming.transportStreamTimestamp') {
39484                var d = frame.subarray(i + 1);
39485                var size =
39486                  ((d[3] & 0x01) << 30) | (d[4] << 22) | (d[5] << 14) | (d[6] << 6) | (d[7] >>> 2);
39487                size *= 4;
39488                size += d[7] & 0x03;
39489                return size;
39490              }
39491              break;
39492            }
39493          }
39494        }
39495        frameStart += 10; // advance past the frame header
39496
39497        frameStart += frameSize; // advance past the frame body
39498      } while (frameStart < packet.byteLength);
39499      return null;
39500    };
39501    var utils = {
39502      isLikelyAacData: isLikelyAacData$1,
39503      parseId3TagSize: parseId3TagSize,
39504      parseAdtsSize: parseAdtsSize,
39505      parseType: parseType$5,
39506      parseSampleRate: parseSampleRate,
39507      parseAacTimestamp: parseAacTimestamp,
39508    };
39509    /**
39510     * mux.js
39511     *
39512     * Copyright (c) Brightcove
39513     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39514     *
39515     * A stream-based aac to mp4 converter. This utility can be used to
39516     * deliver mp4s to a SourceBuffer on platforms that support native
39517     * Media Source Extensions.
39518     */
39519
39520    var Stream$1 = stream;
39521    var aacUtils = utils; // Constants
39522
39523    var AacStream$1;
39524    /**
39525     * Splits an incoming stream of binary data into ADTS and ID3 Frames.
39526     */
39527
39528    AacStream$1 = function () {
39529      var everything = new Uint8Array(),
39530        timeStamp = 0;
39531      AacStream$1.prototype.init.call(this);
39532      this.setTimestamp = function (timestamp) {
39533        timeStamp = timestamp;
39534      };
39535      this.push = function (bytes) {
39536        var frameSize = 0,
39537          byteIndex = 0,
39538          bytesLeft,
39539          chunk,
39540          packet,
39541          tempLength; // If there are bytes remaining from the last segment, prepend them to the
39542        // bytes that were pushed in
39543
39544        if (everything.length) {
39545          tempLength = everything.length;
39546          everything = new Uint8Array(bytes.byteLength + tempLength);
39547          everything.set(everything.subarray(0, tempLength));
39548          everything.set(bytes, tempLength);
39549        } else {
39550          everything = bytes;
39551        }
39552        while (everything.length - byteIndex >= 3) {
39553          if (
39554            everything[byteIndex] === 'I'.charCodeAt(0) &&
39555            everything[byteIndex + 1] === 'D'.charCodeAt(0) &&
39556            everything[byteIndex + 2] === '3'.charCodeAt(0)
39557          ) {
39558            // Exit early because we don't have enough to parse
39559            // the ID3 tag header
39560            if (everything.length - byteIndex < 10) {
39561              break;
39562            } // check framesize
39563
39564            frameSize = aacUtils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
39565            // to emit a full packet
39566            // Add to byteIndex to support multiple ID3 tags in sequence
39567
39568            if (byteIndex + frameSize > everything.length) {
39569              break;
39570            }
39571            chunk = {
39572              type: 'timed-metadata',
39573              data: everything.subarray(byteIndex, byteIndex + frameSize),
39574            };
39575            this.trigger('data', chunk);
39576            byteIndex += frameSize;
39577            continue;
39578          } else if (
39579            (everything[byteIndex] & 0xff) === 0xff &&
39580            (everything[byteIndex + 1] & 0xf0) === 0xf0
39581          ) {
39582            // Exit early because we don't have enough to parse
39583            // the ADTS frame header
39584            if (everything.length - byteIndex < 7) {
39585              break;
39586            }
39587            frameSize = aacUtils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
39588            // to emit a full packet
39589
39590            if (byteIndex + frameSize > everything.length) {
39591              break;
39592            }
39593            packet = {
39594              type: 'audio',
39595              data: everything.subarray(byteIndex, byteIndex + frameSize),
39596              pts: timeStamp,
39597              dts: timeStamp,
39598            };
39599            this.trigger('data', packet);
39600            byteIndex += frameSize;
39601            continue;
39602          }
39603          byteIndex++;
39604        }
39605        bytesLeft = everything.length - byteIndex;
39606        if (bytesLeft > 0) {
39607          everything = everything.subarray(byteIndex);
39608        } else {
39609          everything = new Uint8Array();
39610        }
39611      };
39612      this.reset = function () {
39613        everything = new Uint8Array();
39614        this.trigger('reset');
39615      };
39616      this.endTimeline = function () {
39617        everything = new Uint8Array();
39618        this.trigger('endedtimeline');
39619      };
39620    };
39621    AacStream$1.prototype = new Stream$1();
39622    var aac = AacStream$1;
39623    var AUDIO_PROPERTIES$1 = [
39624      'audioobjecttype',
39625      'channelcount',
39626      'samplerate',
39627      'samplingfrequencyindex',
39628      'samplesize',
39629    ];
39630    var audioProperties = AUDIO_PROPERTIES$1;
39631    var VIDEO_PROPERTIES$1 = [
39632      'width',
39633      'height',
39634      'profileIdc',
39635      'levelIdc',
39636      'profileCompatibility',
39637      'sarRatio',
39638    ];
39639    var videoProperties = VIDEO_PROPERTIES$1;
39640    /**
39641     * mux.js
39642     *
39643     * Copyright (c) Brightcove
39644     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39645     *
39646     * A stream-based mp2t to mp4 converter. This utility can be used to
39647     * deliver mp4s to a SourceBuffer on platforms that support native
39648     * Media Source Extensions.
39649     */
39650
39651    var Stream = stream;
39652    var mp4 = mp4Generator;
39653    var frameUtils = frameUtils$1;
39654    var audioFrameUtils = audioFrameUtils$1;
39655    var trackDecodeInfo = trackDecodeInfo$1;
39656    var m2ts = m2ts_1;
39657    var clock = clock$2;
39658    var AdtsStream = adts;
39659    var H264Stream = h264.H264Stream;
39660    var AacStream = aac;
39661    var isLikelyAacData = utils.isLikelyAacData;
39662    var ONE_SECOND_IN_TS$1 = clock$2.ONE_SECOND_IN_TS;
39663    var AUDIO_PROPERTIES = audioProperties;
39664    var VIDEO_PROPERTIES = videoProperties; // object types
39665
39666    var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
39667    var retriggerForStream = function (key, event) {
39668      event.stream = key;
39669      this.trigger('log', event);
39670    };
39671    var addPipelineLogRetriggers = function (transmuxer, pipeline) {
39672      var keys = Object.keys(pipeline);
39673      for (var i = 0; i < keys.length; i++) {
39674        var key = keys[i]; // skip non-stream keys and headOfPipeline
39675        // which is just a duplicate
39676
39677        if (key === 'headOfPipeline' || !pipeline[key].on) {
39678          continue;
39679        }
39680        pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
39681      }
39682    };
39683    /**
39684     * Compare two arrays (even typed) for same-ness
39685     */
39686
39687    var arrayEquals = function (a, b) {
39688      var i;
39689      if (a.length !== b.length) {
39690        return false;
39691      } // compare the value of each element in the array
39692
39693      for (i = 0; i < a.length; i++) {
39694        if (a[i] !== b[i]) {
39695          return false;
39696        }
39697      }
39698      return true;
39699    };
39700    var generateSegmentTimingInfo = function (
39701      baseMediaDecodeTime,
39702      startDts,
39703      startPts,
39704      endDts,
39705      endPts,
39706      prependedContentDuration
39707    ) {
39708      var ptsOffsetFromDts = startPts - startDts,
39709        decodeDuration = endDts - startDts,
39710        presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
39711      // however, the player time values will reflect a start from the baseMediaDecodeTime.
39712      // In order to provide relevant values for the player times, base timing info on the
39713      // baseMediaDecodeTime and the DTS and PTS durations of the segment.
39714
39715      return {
39716        start: {
39717          dts: baseMediaDecodeTime,
39718          pts: baseMediaDecodeTime + ptsOffsetFromDts,
39719        },
39720        end: {
39721          dts: baseMediaDecodeTime + decodeDuration,
39722          pts: baseMediaDecodeTime + presentationDuration,
39723        },
39724        prependedContentDuration: prependedContentDuration,
39725        baseMediaDecodeTime: baseMediaDecodeTime,
39726      };
39727    };
39728    /**
39729     * Constructs a single-track, ISO BMFF media segment from AAC data
39730     * events. The output of this stream can be fed to a SourceBuffer
39731     * configured with a suitable initialization segment.
39732     * @param track {object} track metadata configuration
39733     * @param options {object} transmuxer options object
39734     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
39735     *        in the source; false to adjust the first segment to start at 0.
39736     */
39737
39738    AudioSegmentStream = function (track, options) {
39739      var adtsFrames = [],
39740        sequenceNumber,
39741        earliestAllowedDts = 0,
39742        audioAppendStartTs = 0,
39743        videoBaseMediaDecodeTime = Infinity;
39744      options = options || {};
39745      sequenceNumber = options.firstSequenceNumber || 0;
39746      AudioSegmentStream.prototype.init.call(this);
39747      this.push = function (data) {
39748        trackDecodeInfo.collectDtsInfo(track, data);
39749        if (track) {
39750          AUDIO_PROPERTIES.forEach(function (prop) {
39751            track[prop] = data[prop];
39752          });
39753        } // buffer audio data until end() is called
39754
39755        adtsFrames.push(data);
39756      };
39757      this.setEarliestDts = function (earliestDts) {
39758        earliestAllowedDts = earliestDts;
39759      };
39760      this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
39761        videoBaseMediaDecodeTime = baseMediaDecodeTime;
39762      };
39763      this.setAudioAppendStart = function (timestamp) {
39764        audioAppendStartTs = timestamp;
39765      };
39766      this.flush = function () {
39767        var frames,
39768          moof,
39769          mdat,
39770          boxes,
39771          frameDuration,
39772          segmentDuration,
39773          videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
39774
39775        if (adtsFrames.length === 0) {
39776          this.trigger('done', 'AudioSegmentStream');
39777          return;
39778        }
39779        frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
39780        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
39781          track,
39782          options.keepOriginalTimestamps
39783        ); // amount of audio filled but the value is in video clock rather than audio clock
39784
39785        videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(
39786          track,
39787          frames,
39788          audioAppendStartTs,
39789          videoBaseMediaDecodeTime
39790        ); // we have to build the index from byte locations to
39791        // samples (that is, adts frames) in the audio data
39792
39793        track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
39794
39795        mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
39796        adtsFrames = [];
39797        moof = mp4.moof(sequenceNumber, [track]);
39798        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
39799
39800        sequenceNumber++;
39801        boxes.set(moof);
39802        boxes.set(mdat, moof.byteLength);
39803        trackDecodeInfo.clearDtsInfo(track);
39804        frameDuration = Math.ceil((ONE_SECOND_IN_TS$1 * 1024) / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
39805        // tests) on adding the timingInfo event. However, it seems unlikely that there's a
39806        // valid use-case where an init segment/data should be triggered without associated
39807        // frames. Leaving for now, but should be looked into.
39808
39809        if (frames.length) {
39810          segmentDuration = frames.length * frameDuration;
39811          this.trigger(
39812            'segmentTimingInfo',
39813            generateSegmentTimingInfo(
39814              // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
39815              // frame info is in video clock cycles. Convert to match expectation of
39816              // listeners (that all timestamps will be based on video clock cycles).
39817              clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
39818              // frame times are already in video clock, as is segment duration
39819              frames[0].dts,
39820              frames[0].pts,
39821              frames[0].dts + segmentDuration,
39822              frames[0].pts + segmentDuration,
39823              videoClockCyclesOfSilencePrefixed || 0
39824            )
39825          );
39826          this.trigger('timingInfo', {
39827            start: frames[0].pts,
39828            end: frames[0].pts + segmentDuration,
39829          });
39830        }
39831        this.trigger('data', {
39832          track: track,
39833          boxes: boxes,
39834        });
39835        this.trigger('done', 'AudioSegmentStream');
39836      };
39837      this.reset = function () {
39838        trackDecodeInfo.clearDtsInfo(track);
39839        adtsFrames = [];
39840        this.trigger('reset');
39841      };
39842    };
39843    AudioSegmentStream.prototype = new Stream();
39844    /**
39845     * Constructs a single-track, ISO BMFF media segment from H264 data
39846     * events. The output of this stream can be fed to a SourceBuffer
39847     * configured with a suitable initialization segment.
39848     * @param track {object} track metadata configuration
39849     * @param options {object} transmuxer options object
39850     * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
39851     *        gopsToAlignWith list when attempting to align gop pts
39852     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
39853     *        in the source; false to adjust the first segment to start at 0.
39854     */
39855
39856    VideoSegmentStream = function (track, options) {
39857      var sequenceNumber,
39858        nalUnits = [],
39859        gopsToAlignWith = [],
39860        config,
39861        pps;
39862      options = options || {};
39863      sequenceNumber = options.firstSequenceNumber || 0;
39864      VideoSegmentStream.prototype.init.call(this);
39865      delete track.minPTS;
39866      this.gopCache_ = [];
39867      /**
39868       * Constructs a ISO BMFF segment given H264 nalUnits
39869       * @param {Object} nalUnit A data event representing a nalUnit
39870       * @param {String} nalUnit.nalUnitType
39871       * @param {Object} nalUnit.config Properties for a mp4 track
39872       * @param {Uint8Array} nalUnit.data The nalUnit bytes
39873       * @see lib/codecs/h264.js
39874       **/
39875
39876      this.push = function (nalUnit) {
39877        trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
39878
39879        if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
39880          config = nalUnit.config;
39881          track.sps = [nalUnit.data];
39882          VIDEO_PROPERTIES.forEach(function (prop) {
39883            track[prop] = config[prop];
39884          }, this);
39885        }
39886        if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
39887          pps = nalUnit.data;
39888          track.pps = [nalUnit.data];
39889        } // buffer video until flush() is called
39890
39891        nalUnits.push(nalUnit);
39892      };
39893      /**
39894       * Pass constructed ISO BMFF track and boxes on to the
39895       * next stream in the pipeline
39896       **/
39897
39898      this.flush = function () {
39899        var frames,
39900          gopForFusion,
39901          gops,
39902          moof,
39903          mdat,
39904          boxes,
39905          prependedContentDuration = 0,
39906          firstGop,
39907          lastGop; // Throw away nalUnits at the start of the byte stream until
39908        // we find the first AUD
39909
39910        while (nalUnits.length) {
39911          if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
39912            break;
39913          }
39914          nalUnits.shift();
39915        } // Return early if no video data has been observed
39916
39917        if (nalUnits.length === 0) {
39918          this.resetStream_();
39919          this.trigger('done', 'VideoSegmentStream');
39920          return;
39921        } // Organize the raw nal-units into arrays that represent
39922        // higher-level constructs such as frames and gops
39923        // (group-of-pictures)
39924
39925        frames = frameUtils.groupNalsIntoFrames(nalUnits);
39926        gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
39927        // a problem since MSE (on Chrome) requires a leading keyframe.
39928        //
39929        // We have two approaches to repairing this situation:
39930        // 1) GOP-FUSION:
39931        //    This is where we keep track of the GOPS (group-of-pictures)
39932        //    from previous fragments and attempt to find one that we can
39933        //    prepend to the current fragment in order to create a valid
39934        //    fragment.
39935        // 2) KEYFRAME-PULLING:
39936        //    Here we search for the first keyframe in the fragment and
39937        //    throw away all the frames between the start of the fragment
39938        //    and that keyframe. We then extend the duration and pull the
39939        //    PTS of the keyframe forward so that it covers the time range
39940        //    of the frames that were disposed of.
39941        //
39942        // #1 is far prefereable over #2 which can cause "stuttering" but
39943        // requires more things to be just right.
39944
39945        if (!gops[0][0].keyFrame) {
39946          // Search for a gop for fusion from our gopCache
39947          gopForFusion = this.getGopForFusion_(nalUnits[0], track);
39948          if (gopForFusion) {
39949            // in order to provide more accurate timing information about the segment, save
39950            // the number of seconds prepended to the original segment due to GOP fusion
39951            prependedContentDuration = gopForFusion.duration;
39952            gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
39953            // new gop at the beginning
39954
39955            gops.byteLength += gopForFusion.byteLength;
39956            gops.nalCount += gopForFusion.nalCount;
39957            gops.pts = gopForFusion.pts;
39958            gops.dts = gopForFusion.dts;
39959            gops.duration += gopForFusion.duration;
39960          } else {
39961            // If we didn't find a candidate gop fall back to keyframe-pulling
39962            gops = frameUtils.extendFirstKeyFrame(gops);
39963          }
39964        } // Trim gops to align with gopsToAlignWith
39965
39966        if (gopsToAlignWith.length) {
39967          var alignedGops;
39968          if (options.alignGopsAtEnd) {
39969            alignedGops = this.alignGopsAtEnd_(gops);
39970          } else {
39971            alignedGops = this.alignGopsAtStart_(gops);
39972          }
39973          if (!alignedGops) {
39974            // save all the nals in the last GOP into the gop cache
39975            this.gopCache_.unshift({
39976              gop: gops.pop(),
39977              pps: track.pps,
39978              sps: track.sps,
39979            }); // Keep a maximum of 6 GOPs in the cache
39980
39981            this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
39982
39983            nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
39984
39985            this.resetStream_();
39986            this.trigger('done', 'VideoSegmentStream');
39987            return;
39988          } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
39989          // when recalculated before sending off to CoalesceStream
39990
39991          trackDecodeInfo.clearDtsInfo(track);
39992          gops = alignedGops;
39993        }
39994        trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
39995        // samples (that is, frames) in the video data
39996
39997        track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
39998
39999        mdat = mp4.mdat(frameUtils.concatenateNalData(gops));
40000        track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
40001          track,
40002          options.keepOriginalTimestamps
40003        );
40004        this.trigger(
40005          'processedGopsInfo',
40006          gops.map(function (gop) {
40007            return {
40008              pts: gop.pts,
40009              dts: gop.dts,
40010              byteLength: gop.byteLength,
40011            };
40012          })
40013        );
40014        firstGop = gops[0];
40015        lastGop = gops[gops.length - 1];
40016        this.trigger(
40017          'segmentTimingInfo',
40018          generateSegmentTimingInfo(
40019            track.baseMediaDecodeTime,
40020            firstGop.dts,
40021            firstGop.pts,
40022            lastGop.dts + lastGop.duration,
40023            lastGop.pts + lastGop.duration,
40024            prependedContentDuration
40025          )
40026        );
40027        this.trigger('timingInfo', {
40028          start: gops[0].pts,
40029          end: gops[gops.length - 1].pts + gops[gops.length - 1].duration,
40030        }); // save all the nals in the last GOP into the gop cache
40031
40032        this.gopCache_.unshift({
40033          gop: gops.pop(),
40034          pps: track.pps,
40035          sps: track.sps,
40036        }); // Keep a maximum of 6 GOPs in the cache
40037
40038        this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
40039
40040        nalUnits = [];
40041        this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
40042        this.trigger('timelineStartInfo', track.timelineStartInfo);
40043        moof = mp4.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
40044        // throwing away hundreds of media segment fragments
40045
40046        boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
40047
40048        sequenceNumber++;
40049        boxes.set(moof);
40050        boxes.set(mdat, moof.byteLength);
40051        this.trigger('data', {
40052          track: track,
40053          boxes: boxes,
40054        });
40055        this.resetStream_(); // Continue with the flush process now
40056
40057        this.trigger('done', 'VideoSegmentStream');
40058      };
40059      this.reset = function () {
40060        this.resetStream_();
40061        nalUnits = [];
40062        this.gopCache_.length = 0;
40063        gopsToAlignWith.length = 0;
40064        this.trigger('reset');
40065      };
40066      this.resetStream_ = function () {
40067        trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
40068        // for instance, when we are rendition switching
40069
40070        config = undefined;
40071        pps = undefined;
40072      }; // Search for a candidate Gop for gop-fusion from the gop cache and
40073      // return it or return null if no good candidate was found
40074
40075      this.getGopForFusion_ = function (nalUnit) {
40076        var halfSecond = 45000,
40077          // Half-a-second in a 90khz clock
40078          allowableOverlap = 10000,
40079          // About 3 frames @ 30fps
40080          nearestDistance = Infinity,
40081          dtsDistance,
40082          nearestGopObj,
40083          currentGop,
40084          currentGopObj,
40085          i; // Search for the GOP nearest to the beginning of this nal unit
40086
40087        for (i = 0; i < this.gopCache_.length; i++) {
40088          currentGopObj = this.gopCache_[i];
40089          currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
40090
40091          if (
40092            !(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) ||
40093            !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))
40094          ) {
40095            continue;
40096          } // Reject Gops that would require a negative baseMediaDecodeTime
40097
40098          if (currentGop.dts < track.timelineStartInfo.dts) {
40099            continue;
40100          } // The distance between the end of the gop and the start of the nalUnit
40101
40102          dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
40103          // a half-second of the nal unit
40104
40105          if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
40106            // Always use the closest GOP we found if there is more than
40107            // one candidate
40108            if (!nearestGopObj || nearestDistance > dtsDistance) {
40109              nearestGopObj = currentGopObj;
40110              nearestDistance = dtsDistance;
40111            }
40112          }
40113        }
40114        if (nearestGopObj) {
40115          return nearestGopObj.gop;
40116        }
40117        return null;
40118      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
40119      // of gopsToAlignWith starting from the START of the list
40120
40121      this.alignGopsAtStart_ = function (gops) {
40122        var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
40123        byteLength = gops.byteLength;
40124        nalCount = gops.nalCount;
40125        duration = gops.duration;
40126        alignIndex = gopIndex = 0;
40127        while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
40128          align = gopsToAlignWith[alignIndex];
40129          gop = gops[gopIndex];
40130          if (align.pts === gop.pts) {
40131            break;
40132          }
40133          if (gop.pts > align.pts) {
40134            // this current gop starts after the current gop we want to align on, so increment
40135            // align index
40136            alignIndex++;
40137            continue;
40138          } // current gop starts before the current gop we want to align on. so increment gop
40139          // index
40140
40141          gopIndex++;
40142          byteLength -= gop.byteLength;
40143          nalCount -= gop.nalCount;
40144          duration -= gop.duration;
40145        }
40146        if (gopIndex === 0) {
40147          // no gops to trim
40148          return gops;
40149        }
40150        if (gopIndex === gops.length) {
40151          // all gops trimmed, skip appending all gops
40152          return null;
40153        }
40154        alignedGops = gops.slice(gopIndex);
40155        alignedGops.byteLength = byteLength;
40156        alignedGops.duration = duration;
40157        alignedGops.nalCount = nalCount;
40158        alignedGops.pts = alignedGops[0].pts;
40159        alignedGops.dts = alignedGops[0].dts;
40160        return alignedGops;
40161      }; // trim gop list to the first gop found that has a matching pts with a gop in the list
40162      // of gopsToAlignWith starting from the END of the list
40163
40164      this.alignGopsAtEnd_ = function (gops) {
40165        var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
40166        alignIndex = gopsToAlignWith.length - 1;
40167        gopIndex = gops.length - 1;
40168        alignEndIndex = null;
40169        matchFound = false;
vendor: 4,634 bytes, lines 40170-40297
40170        while (alignIndex >= 0 && gopIndex >= 0) {
40171          align = gopsToAlignWith[alignIndex];
40172          gop = gops[gopIndex];
40173          if (align.pts === gop.pts) {
40174            matchFound = true;
40175            break;
40176          }
40177          if (align.pts > gop.pts) {
40178            alignIndex--;
40179            continue;
40180          }
40181          if (alignIndex === gopsToAlignWith.length - 1) {
40182            // gop.pts is greater than the last alignment candidate. If no match is found
40183            // by the end of this loop, we still want to append gops that come after this
40184            // point
40185            alignEndIndex = gopIndex;
40186          }
40187          gopIndex--;
40188        }
40189        if (!matchFound && alignEndIndex === null) {
40190          return null;
40191        }
40192        var trimIndex;
40193        if (matchFound) {
40194          trimIndex = gopIndex;
40195        } else {
40196          trimIndex = alignEndIndex;
40197        }
40198        if (trimIndex === 0) {
40199          return gops;
40200        }
40201        var alignedGops = gops.slice(trimIndex);
40202        var metadata = alignedGops.reduce(
40203          function (total, gop) {
40204            total.byteLength += gop.byteLength;
40205            total.duration += gop.duration;
40206            total.nalCount += gop.nalCount;
40207            return total;
40208          },
40209          {
40210            byteLength: 0,
40211            duration: 0,
40212            nalCount: 0,
40213          }
40214        );
40215        alignedGops.byteLength = metadata.byteLength;
40216        alignedGops.duration = metadata.duration;
40217        alignedGops.nalCount = metadata.nalCount;
40218        alignedGops.pts = alignedGops[0].pts;
40219        alignedGops.dts = alignedGops[0].dts;
40220        return alignedGops;
40221      };
40222      this.alignGopsWith = function (newGopsToAlignWith) {
40223        gopsToAlignWith = newGopsToAlignWith;
40224      };
40225    };
40226    VideoSegmentStream.prototype = new Stream();
40227    /**
40228     * A Stream that can combine multiple streams (ie. audio & video)
40229     * into a single output segment for MSE. Also supports audio-only
40230     * and video-only streams.
40231     * @param options {object} transmuxer options object
40232     * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
40233     *        in the source; false to adjust the first segment to start at media timeline start.
40234     */
40235
40236    CoalesceStream = function (options, metadataStream) {
40237      // Number of Tracks per output segment
40238      // If greater than 1, we combine multiple
40239      // tracks into a single segment
40240      this.numberOfTracks = 0;
40241      this.metadataStream = metadataStream;
40242      options = options || {};
40243      if (typeof options.remux !== 'undefined') {
40244        this.remuxTracks = !!options.remux;
40245      } else {
40246        this.remuxTracks = true;
40247      }
40248      if (typeof options.keepOriginalTimestamps === 'boolean') {
40249        this.keepOriginalTimestamps = options.keepOriginalTimestamps;
40250      } else {
40251        this.keepOriginalTimestamps = false;
40252      }
40253      this.pendingTracks = [];
40254      this.videoTrack = null;
40255      this.pendingBoxes = [];
40256      this.pendingCaptions = [];
40257      this.pendingMetadata = [];
40258      this.pendingBytes = 0;
40259      this.emittedTracks = 0;
40260      CoalesceStream.prototype.init.call(this); // Take output from multiple
40261
40262      this.push = function (output) {
40263        // buffer incoming captions until the associated video segment
40264        // finishes
40265        if (output.content || output.text) {
40266          return this.pendingCaptions.push(output);
40267        } // buffer incoming id3 tags until the final flush
40268
40269        if (output.frames) {
40270          return this.pendingMetadata.push(output);
40271        } // Add this track to the list of pending tracks and store
40272        // important information required for the construction of
40273        // the final segment
40274
40275        this.pendingTracks.push(output.track);
40276        this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
40277        // We unshift audio and push video because
40278        // as of Chrome 75 when switching from
40279        // one init segment to another if the video
40280        // mdat does not appear after the audio mdat
40281        // only audio will play for the duration of our transmux.
40282
40283        if (output.track.type === 'video') {
40284          this.videoTrack = output.track;
40285          this.pendingBoxes.push(output.boxes);
40286        }
40287        if (output.track.type === 'audio') {
40288          this.audioTrack = output.track;
40289          this.pendingBoxes.unshift(output.boxes);
40290        }
40291      };
40292    };
40293    CoalesceStream.prototype = new Stream();
40294    CoalesceStream.prototype.flush = function (flushSource) {
40295      var offset = 0,
40296        event = {
40297          captions: [],
vendor: 25,981 bytes, lines 40298-40934
40298          captionStreams: {},
40299          metadata: [],
40300          info: {},
40301        },
40302        caption,
40303        id3,
40304        initSegment,
40305        timelineStartPts = 0,
40306        i;
40307      if (this.pendingTracks.length < this.numberOfTracks) {
40308        if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
40309          // Return because we haven't received a flush from a data-generating
40310          // portion of the segment (meaning that we have only recieved meta-data
40311          // or captions.)
40312          return;
40313        } else if (this.remuxTracks) {
40314          // Return until we have enough tracks from the pipeline to remux (if we
40315          // are remuxing audio and video into a single MP4)
40316          return;
40317        } else if (this.pendingTracks.length === 0) {
40318          // In the case where we receive a flush without any data having been
40319          // received we consider it an emitted track for the purposes of coalescing
40320          // `done` events.
40321          // We do this for the case where there is an audio and video track in the
40322          // segment but no audio data. (seen in several playlists with alternate
40323          // audio tracks and no audio present in the main TS segments.)
40324          this.emittedTracks++;
40325          if (this.emittedTracks >= this.numberOfTracks) {
40326            this.trigger('done');
40327            this.emittedTracks = 0;
40328          }
40329          return;
40330        }
40331      }
40332      if (this.videoTrack) {
40333        timelineStartPts = this.videoTrack.timelineStartInfo.pts;
40334        VIDEO_PROPERTIES.forEach(function (prop) {
40335          event.info[prop] = this.videoTrack[prop];
40336        }, this);
40337      } else if (this.audioTrack) {
40338        timelineStartPts = this.audioTrack.timelineStartInfo.pts;
40339        AUDIO_PROPERTIES.forEach(function (prop) {
40340          event.info[prop] = this.audioTrack[prop];
40341        }, this);
40342      }
40343      if (this.videoTrack || this.audioTrack) {
40344        if (this.pendingTracks.length === 1) {
40345          event.type = this.pendingTracks[0].type;
40346        } else {
40347          event.type = 'combined';
40348        }
40349        this.emittedTracks += this.pendingTracks.length;
40350        initSegment = mp4.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
40351
40352        event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
40353        // and track definitions
40354
40355        event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
40356
40357        event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
40358
40359        for (i = 0; i < this.pendingBoxes.length; i++) {
40360          event.data.set(this.pendingBoxes[i], offset);
40361          offset += this.pendingBoxes[i].byteLength;
40362        } // Translate caption PTS times into second offsets to match the
40363        // video timeline for the segment, and add track info
40364
40365        for (i = 0; i < this.pendingCaptions.length; i++) {
40366          caption = this.pendingCaptions[i];
40367          caption.startTime = clock.metadataTsToSeconds(
40368            caption.startPts,
40369            timelineStartPts,
40370            this.keepOriginalTimestamps
40371          );
40372          caption.endTime = clock.metadataTsToSeconds(
40373            caption.endPts,
40374            timelineStartPts,
40375            this.keepOriginalTimestamps
40376          );
40377          event.captionStreams[caption.stream] = true;
40378          event.captions.push(caption);
40379        } // Translate ID3 frame PTS times into second offsets to match the
40380        // video timeline for the segment
40381
40382        for (i = 0; i < this.pendingMetadata.length; i++) {
40383          id3 = this.pendingMetadata[i];
40384          id3.cueTime = clock.metadataTsToSeconds(
40385            id3.pts,
40386            timelineStartPts,
40387            this.keepOriginalTimestamps
40388          );
40389          event.metadata.push(id3);
40390        } // We add this to every single emitted segment even though we only need
40391        // it for the first
40392
40393        event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
40394
40395        this.pendingTracks.length = 0;
40396        this.videoTrack = null;
40397        this.pendingBoxes.length = 0;
40398        this.pendingCaptions.length = 0;
40399        this.pendingBytes = 0;
40400        this.pendingMetadata.length = 0; // Emit the built segment
40401        // We include captions and ID3 tags for backwards compatibility,
40402        // ideally we should send only video and audio in the data event
40403
40404        this.trigger('data', event); // Emit each caption to the outside world
40405        // Ideally, this would happen immediately on parsing captions,
40406        // but we need to ensure that video data is sent back first
40407        // so that caption timing can be adjusted to match video timing
40408
40409        for (i = 0; i < event.captions.length; i++) {
40410          caption = event.captions[i];
40411          this.trigger('caption', caption);
40412        } // Emit each id3 tag to the outside world
40413        // Ideally, this would happen immediately on parsing the tag,
40414        // but we need to ensure that video data is sent back first
40415        // so that ID3 frame timing can be adjusted to match video timing
40416
40417        for (i = 0; i < event.metadata.length; i++) {
40418          id3 = event.metadata[i];
40419          this.trigger('id3Frame', id3);
40420        }
40421      } // Only emit `done` if all tracks have been flushed and emitted
40422
40423      if (this.emittedTracks >= this.numberOfTracks) {
40424        this.trigger('done');
40425        this.emittedTracks = 0;
40426      }
40427    };
40428    CoalesceStream.prototype.setRemux = function (val) {
40429      this.remuxTracks = val;
40430    };
40431    /**
40432     * A Stream that expects MP2T binary data as input and produces
40433     * corresponding media segments, suitable for use with Media Source
40434     * Extension (MSE) implementations that support the ISO BMFF byte
40435     * stream format, like Chrome.
40436     */
40437
40438    Transmuxer = function (options) {
40439      var self = this,
40440        hasFlushed = true,
40441        videoTrack,
40442        audioTrack;
40443      Transmuxer.prototype.init.call(this);
40444      options = options || {};
40445      this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
40446      this.transmuxPipeline_ = {};
40447      this.setupAacPipeline = function () {
40448        var pipeline = {};
40449        this.transmuxPipeline_ = pipeline;
40450        pipeline.type = 'aac';
40451        pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
40452
40453        pipeline.aacStream = new AacStream();
40454        pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
40455        pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream(
40456          'timed-metadata'
40457        );
40458        pipeline.adtsStream = new AdtsStream();
40459        pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
40460        pipeline.headOfPipeline = pipeline.aacStream;
40461        pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
40462        pipeline.aacStream
40463          .pipe(pipeline.timedMetadataTimestampRolloverStream)
40464          .pipe(pipeline.metadataStream)
40465          .pipe(pipeline.coalesceStream);
40466        pipeline.metadataStream.on('timestamp', function (frame) {
40467          pipeline.aacStream.setTimestamp(frame.timeStamp);
40468        });
40469        pipeline.aacStream.on('data', function (data) {
40470          if (
40471            (data.type !== 'timed-metadata' && data.type !== 'audio') ||
40472            pipeline.audioSegmentStream
40473          ) {
40474            return;
40475          }
40476          audioTrack = audioTrack || {
40477            timelineStartInfo: {
40478              baseMediaDecodeTime: self.baseMediaDecodeTime,
40479            },
40480            codec: 'adts',
40481            type: 'audio',
40482          }; // hook up the audio segment stream to the first track with aac data
40483
40484          pipeline.coalesceStream.numberOfTracks++;
40485          pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
40486          pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
40487          pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
40488
40489          pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
40490
40491          self.trigger('trackinfo', {
40492            hasAudio: !!audioTrack,
40493            hasVideo: !!videoTrack,
40494          });
40495        }); // Re-emit any data coming from the coalesce stream to the outside world
40496
40497        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
40498
40499        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
40500        addPipelineLogRetriggers(this, pipeline);
40501      };
40502      this.setupTsPipeline = function () {
40503        var pipeline = {};
40504        this.transmuxPipeline_ = pipeline;
40505        pipeline.type = 'ts';
40506        pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
40507
40508        pipeline.packetStream = new m2ts.TransportPacketStream();
40509        pipeline.parseStream = new m2ts.TransportParseStream();
40510        pipeline.elementaryStream = new m2ts.ElementaryStream();
40511        pipeline.timestampRolloverStream = new m2ts.TimestampRolloverStream();
40512        pipeline.adtsStream = new AdtsStream();
40513        pipeline.h264Stream = new H264Stream();
40514        pipeline.captionStream = new m2ts.CaptionStream(options);
40515        pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
40516        pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
40517
40518        pipeline.packetStream
40519          .pipe(pipeline.parseStream)
40520          .pipe(pipeline.elementaryStream)
40521          .pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
40522        // demux the streams
40523
40524        pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
40525        pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
40526        pipeline.timestampRolloverStream
40527          .pipe(pipeline.metadataStream)
40528          .pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
40529
40530        pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
40531        pipeline.elementaryStream.on('data', function (data) {
40532          var i;
40533          if (data.type === 'metadata') {
40534            i = data.tracks.length; // scan the tracks listed in the metadata
40535
40536            while (i--) {
40537              if (!videoTrack && data.tracks[i].type === 'video') {
40538                videoTrack = data.tracks[i];
40539                videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
40540              } else if (!audioTrack && data.tracks[i].type === 'audio') {
40541                audioTrack = data.tracks[i];
40542                audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
40543              }
40544            } // hook up the video segment stream to the first track with h264 data
40545
40546            if (videoTrack && !pipeline.videoSegmentStream) {
40547              pipeline.coalesceStream.numberOfTracks++;
40548              pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
40549              pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
40550              pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
40551                // When video emits timelineStartInfo data after a flush, we forward that
40552                // info to the AudioSegmentStream, if it exists, because video timeline
40553                // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
40554                // because this is a particularly subtle form of timestamp alteration.
40555                if (audioTrack && !options.keepOriginalTimestamps) {
40556                  audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
40557                  // very earliest DTS we have seen in video because Chrome will
40558                  // interpret any video track with a baseMediaDecodeTime that is
40559                  // non-zero as a gap.
40560
40561                  pipeline.audioSegmentStream.setEarliestDts(
40562                    timelineStartInfo.dts - self.baseMediaDecodeTime
40563                  );
40564                }
40565              });
40566              pipeline.videoSegmentStream.on(
40567                'processedGopsInfo',
40568                self.trigger.bind(self, 'gopInfo')
40569              );
40570              pipeline.videoSegmentStream.on(
40571                'segmentTimingInfo',
40572                self.trigger.bind(self, 'videoSegmentTimingInfo')
40573              );
40574              pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
40575                if (audioTrack) {
40576                  pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
40577                }
40578              });
40579              pipeline.videoSegmentStream.on(
40580                'timingInfo',
40581                self.trigger.bind(self, 'videoTimingInfo')
40582              ); // Set up the final part of the video pipeline
40583
40584              pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
40585            }
40586            if (audioTrack && !pipeline.audioSegmentStream) {
40587              // hook up the audio segment stream to the first track with aac data
40588              pipeline.coalesceStream.numberOfTracks++;
40589              pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
40590              pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
40591              pipeline.audioSegmentStream.on(
40592                'timingInfo',
40593                self.trigger.bind(self, 'audioTimingInfo')
40594              );
40595              pipeline.audioSegmentStream.on(
40596                'segmentTimingInfo',
40597                self.trigger.bind(self, 'audioSegmentTimingInfo')
40598              ); // Set up the final part of the audio pipeline
40599
40600              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
40601            } // emit pmt info
40602
40603            self.trigger('trackinfo', {
40604              hasAudio: !!audioTrack,
40605              hasVideo: !!videoTrack,
40606            });
40607          }
40608        }); // Re-emit any data coming from the coalesce stream to the outside world
40609
40610        pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
40611        pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
40612          id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
40613          self.trigger('id3Frame', id3Frame);
40614        });
40615        pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
40616
40617        pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
40618        addPipelineLogRetriggers(this, pipeline);
40619      }; // hook up the segment streams once track metadata is delivered
40620
40621      this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
40622        var pipeline = this.transmuxPipeline_;
40623        if (!options.keepOriginalTimestamps) {
40624          this.baseMediaDecodeTime = baseMediaDecodeTime;
40625        }
40626        if (audioTrack) {
40627          audioTrack.timelineStartInfo.dts = undefined;
40628          audioTrack.timelineStartInfo.pts = undefined;
40629          trackDecodeInfo.clearDtsInfo(audioTrack);
40630          if (pipeline.audioTimestampRolloverStream) {
40631            pipeline.audioTimestampRolloverStream.discontinuity();
40632          }
40633        }
40634        if (videoTrack) {
40635          if (pipeline.videoSegmentStream) {
40636            pipeline.videoSegmentStream.gopCache_ = [];
40637          }
40638          videoTrack.timelineStartInfo.dts = undefined;
40639          videoTrack.timelineStartInfo.pts = undefined;
40640          trackDecodeInfo.clearDtsInfo(videoTrack);
40641          pipeline.captionStream.reset();
40642        }
40643        if (pipeline.timestampRolloverStream) {
40644          pipeline.timestampRolloverStream.discontinuity();
40645        }
40646      };
40647      this.setAudioAppendStart = function (timestamp) {
40648        if (audioTrack) {
40649          this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
40650        }
40651      };
40652      this.setRemux = function (val) {
40653        var pipeline = this.transmuxPipeline_;
40654        options.remux = val;
40655        if (pipeline && pipeline.coalesceStream) {
40656          pipeline.coalesceStream.setRemux(val);
40657        }
40658      };
40659      this.alignGopsWith = function (gopsToAlignWith) {
40660        if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
40661          this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
40662        }
40663      };
40664      this.getLogTrigger_ = function (key) {
40665        var self = this;
40666        return function (event) {
40667          event.stream = key;
40668          self.trigger('log', event);
40669        };
40670      }; // feed incoming data to the front of the parsing pipeline
40671
40672      this.push = function (data) {
40673        if (hasFlushed) {
40674          var isAac = isLikelyAacData(data);
40675          if (isAac && this.transmuxPipeline_.type !== 'aac') {
40676            this.setupAacPipeline();
40677          } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
40678            this.setupTsPipeline();
40679          }
40680          hasFlushed = false;
40681        }
40682        this.transmuxPipeline_.headOfPipeline.push(data);
40683      }; // flush any buffered data
40684
40685      this.flush = function () {
40686        hasFlushed = true; // Start at the top of the pipeline and flush all pending work
40687
40688        this.transmuxPipeline_.headOfPipeline.flush();
40689      };
40690      this.endTimeline = function () {
40691        this.transmuxPipeline_.headOfPipeline.endTimeline();
40692      };
40693      this.reset = function () {
40694        if (this.transmuxPipeline_.headOfPipeline) {
40695          this.transmuxPipeline_.headOfPipeline.reset();
40696        }
40697      }; // Caption data has to be reset when seeking outside buffered range
40698
40699      this.resetCaptions = function () {
40700        if (this.transmuxPipeline_.captionStream) {
40701          this.transmuxPipeline_.captionStream.reset();
40702        }
40703      };
40704    };
40705    Transmuxer.prototype = new Stream();
40706    var transmuxer = {
40707      Transmuxer: Transmuxer,
40708      VideoSegmentStream: VideoSegmentStream,
40709      AudioSegmentStream: AudioSegmentStream,
40710      AUDIO_PROPERTIES: AUDIO_PROPERTIES,
40711      VIDEO_PROPERTIES: VIDEO_PROPERTIES,
40712      // exported for testing
40713      generateSegmentTimingInfo: generateSegmentTimingInfo,
40714    };
40715    /**
40716     * mux.js
40717     *
40718     * Copyright (c) Brightcove
40719     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40720     */
40721
40722    var toUnsigned$3 = function (value) {
40723      return value >>> 0;
40724    };
40725    var toHexString$1 = function (value) {
40726      return ('00' + value.toString(16)).slice(-2);
40727    };
40728    var bin = {
40729      toUnsigned: toUnsigned$3,
40730      toHexString: toHexString$1,
40731    };
40732    var parseType$4 = function (buffer) {
40733      var result = '';
40734      result += String.fromCharCode(buffer[0]);
40735      result += String.fromCharCode(buffer[1]);
40736      result += String.fromCharCode(buffer[2]);
40737      result += String.fromCharCode(buffer[3]);
40738      return result;
40739    };
40740    var parseType_1 = parseType$4;
40741    var toUnsigned$2 = bin.toUnsigned;
40742    var parseType$3 = parseType_1;
40743    var findBox$5 = function (data, path) {
40744      var results = [],
40745        i,
40746        size,
40747        type,
40748        end,
40749        subresults;
40750      if (!path.length) {
40751        // short-circuit the search for empty paths
40752        return null;
40753      }
40754      for (i = 0; i < data.byteLength; ) {
40755        size = toUnsigned$2(
40756          (data[i] << 24) | (data[i + 1] << 16) | (data[i + 2] << 8) | data[i + 3]
40757        );
40758        type = parseType$3(data.subarray(i + 4, i + 8));
40759        end = size > 1 ? i + size : data.byteLength;
40760        if (type === path[0]) {
40761          if (path.length === 1) {
40762            // this is the end of the path and we've found the box we were
40763            // looking for
40764            results.push(data.subarray(i + 8, end));
40765          } else {
40766            // recursively search for the next box along the path
40767            subresults = findBox$5(data.subarray(i + 8, end), path.slice(1));
40768            if (subresults.length) {
40769              results = results.concat(subresults);
40770            }
40771          }
40772        }
40773        i = end;
40774      } // we've finished searching all of data
40775
40776      return results;
40777    };
40778    var findBox_1 = findBox$5;
40779    var toUnsigned$1 = bin.toUnsigned;
40780    var getUint64$4 = numbers.getUint64;
40781    var tfdt = function (data) {
40782      var result = {
40783        version: data[0],
40784        flags: new Uint8Array(data.subarray(1, 4)),
40785      };
40786      if (result.version === 1) {
40787        result.baseMediaDecodeTime = getUint64$4(data.subarray(4));
40788      } else {
40789        result.baseMediaDecodeTime = toUnsigned$1(
40790          (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]
40791        );
40792      }
40793      return result;
40794    };
40795    var parseTfdt$3 = tfdt;
40796    var tfhd = function (data) {
40797      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
40798        result = {
40799          version: data[0],
40800          flags: new Uint8Array(data.subarray(1, 4)),
40801          trackId: view.getUint32(4),
40802        },
40803        baseDataOffsetPresent = result.flags[2] & 0x01,
40804        sampleDescriptionIndexPresent = result.flags[2] & 0x02,
40805        defaultSampleDurationPresent = result.flags[2] & 0x08,
40806        defaultSampleSizePresent = result.flags[2] & 0x10,
40807        defaultSampleFlagsPresent = result.flags[2] & 0x20,
40808        durationIsEmpty = result.flags[0] & 0x010000,
40809        defaultBaseIsMoof = result.flags[0] & 0x020000,
40810        i;
40811      i = 8;
40812      if (baseDataOffsetPresent) {
40813        i += 4; // truncate top 4 bytes
40814        // FIXME: should we read the full 64 bits?
40815
40816        result.baseDataOffset = view.getUint32(12);
40817        i += 4;
40818      }
40819      if (sampleDescriptionIndexPresent) {
40820        result.sampleDescriptionIndex = view.getUint32(i);
40821        i += 4;
40822      }
40823      if (defaultSampleDurationPresent) {
40824        result.defaultSampleDuration = view.getUint32(i);
40825        i += 4;
40826      }
40827      if (defaultSampleSizePresent) {
40828        result.defaultSampleSize = view.getUint32(i);
40829        i += 4;
40830      }
40831      if (defaultSampleFlagsPresent) {
40832        result.defaultSampleFlags = view.getUint32(i);
40833      }
40834      if (durationIsEmpty) {
40835        result.durationIsEmpty = true;
40836      }
40837      if (!baseDataOffsetPresent && defaultBaseIsMoof) {
40838        result.baseDataOffsetIsMoof = true;
40839      }
40840      return result;
40841    };
40842    var parseTfhd$2 = tfhd;
40843    var getUint64$3 = numbers.getUint64;
40844    var parseSidx = function (data) {
40845      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
40846        result = {
40847          version: data[0],
40848          flags: new Uint8Array(data.subarray(1, 4)),
40849          references: [],
40850          referenceId: view.getUint32(4),
40851          timescale: view.getUint32(8),
40852        },
40853        i = 12;
40854      if (result.version === 0) {
40855        result.earliestPresentationTime = view.getUint32(i);
40856        result.firstOffset = view.getUint32(i + 4);
40857        i += 8;
40858      } else {
40859        // read 64 bits
40860        result.earliestPresentationTime = getUint64$3(data.subarray(i));
40861        result.firstOffset = getUint64$3(data.subarray(i + 8));
40862        i += 16;
40863      }
40864      i += 2; // reserved
40865
40866      var referenceCount = view.getUint16(i);
40867      i += 2; // start of references
40868
40869      for (; referenceCount > 0; i += 12, referenceCount--) {
40870        result.references.push({
40871          referenceType: (data[i] & 0x80) >>> 7,
40872          referencedSize: view.getUint32(i) & 0x7fffffff,
40873          subsegmentDuration: view.getUint32(i + 4),
40874          startsWithSap: !!(data[i + 8] & 0x80),
40875          sapType: (data[i + 8] & 0x70) >>> 4,
40876          sapDeltaTime: view.getUint32(i + 8) & 0x0fffffff,
40877        });
40878      }
40879      return result;
40880    };
40881    var parseSidx_1 = parseSidx;
40882    var parseSampleFlags$1 = function (flags) {
40883      return {
40884        isLeading: (flags[0] & 0x0c) >>> 2,
40885        dependsOn: flags[0] & 0x03,
40886        isDependedOn: (flags[1] & 0xc0) >>> 6,
40887        hasRedundancy: (flags[1] & 0x30) >>> 4,
40888        paddingValue: (flags[1] & 0x0e) >>> 1,
40889        isNonSyncSample: flags[1] & 0x01,
40890        degradationPriority: (flags[2] << 8) | flags[3],
40891      };
40892    };
40893    var parseSampleFlags_1 = parseSampleFlags$1;
40894    var parseSampleFlags = parseSampleFlags_1;
40895    var trun = function (data) {
40896      var result = {
40897          version: data[0],
40898          flags: new Uint8Array(data.subarray(1, 4)),
40899          samples: [],
40900        },
40901        view = new DataView(data.buffer, data.byteOffset, data.byteLength),
40902        // Flag interpretation
40903        dataOffsetPresent = result.flags[2] & 0x01,
40904        // compare with 2nd byte of 0x1
40905        firstSampleFlagsPresent = result.flags[2] & 0x04,
40906        // compare with 2nd byte of 0x4
40907        sampleDurationPresent = result.flags[1] & 0x01,
40908        // compare with 2nd byte of 0x100
40909        sampleSizePresent = result.flags[1] & 0x02,
40910        // compare with 2nd byte of 0x200
40911        sampleFlagsPresent = result.flags[1] & 0x04,
40912        // compare with 2nd byte of 0x400
40913        sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
40914        // compare with 2nd byte of 0x800
40915        sampleCount = view.getUint32(4),
40916        offset = 8,
40917        sample;
40918      if (dataOffsetPresent) {
40919        // 32 bit signed integer
40920        result.dataOffset = view.getInt32(offset);
40921        offset += 4;
40922      } // Overrides the flags for the first sample only. The order of
40923      // optional values will be: duration, size, compositionTimeOffset
40924
40925      if (firstSampleFlagsPresent && sampleCount) {
40926        sample = {
40927          flags: parseSampleFlags(data.subarray(offset, offset + 4)),
40928        };
40929        offset += 4;
40930        if (sampleDurationPresent) {
40931          sample.duration = view.getUint32(offset);
40932          offset += 4;
40933        }
40934        if (sampleSizePresent) {
40935          sample.size = view.getUint32(offset);
40936          offset += 4;
40937        }
40938        if (sampleCompositionTimeOffsetPresent) {
40939          if (result.version === 1) {
40940            sample.compositionTimeOffset = view.getInt32(offset);
40941          } else {
40942            sample.compositionTimeOffset = view.getUint32(offset);
40943          }
40944          offset += 4;
40945        }
40946        result.samples.push(sample);
40947        sampleCount--;
40948      }
40949      while (sampleCount--) {
40950        sample = {};
40951        if (sampleDurationPresent) {
40952          sample.duration = view.getUint32(offset);
40953          offset += 4;
40954        }
40955        if (sampleSizePresent) {
40956          sample.size = view.getUint32(offset);
40957          offset += 4;
40958        }
40959        if (sampleFlagsPresent) {
40960          sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
40961          offset += 4;
40962        }
40963        if (sampleCompositionTimeOffsetPresent) {
40964          if (result.version === 1) {
40965            sample.compositionTimeOffset = view.getInt32(offset);
40966          } else {
40967            sample.compositionTimeOffset = view.getUint32(offset);
40968          }
40969          offset += 4;
40970        }
40971        result.samples.push(sample);
40972      }
40973      return result;
40974    };
40975    var parseTrun$2 = trun;
40976    /**
40977     * mux.js
40978     *
40979     * Copyright (c) Brightcove
40980     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40981     *
40982     * Parse the internal MP4 structure into an equivalent javascript
40983     * object.
40984     */
40985
40986    var numberHelpers = numbers;
40987    var getUint64$2 = numberHelpers.getUint64;
40988    var inspectMp4,
40989      textifyMp4,
40990      parseMp4Date = function (seconds) {
40991        return new Date(seconds * 1000 - 2082844800000);
40992      },
40993      parseType$2 = parseType_1,
40994      findBox$4 = findBox_1,
40995      nalParse = function (avcStream) {
40996        var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
40997          result = [],
40998          i,
40999          length;
41000        for (i = 0; i + 4 < avcStream.length; i += length) {
41001          length = avcView.getUint32(i);
41002          i += 4; // bail if this doesn't appear to be an H264 stream
41003
41004          if (length <= 0) {
41005            result.push("<span style='color:red;'>MALFORMED DATA</span>");
41006            continue;
41007          }
41008          switch (avcStream[i] & 0x1f) {
41009            case 0x01:
41010              result.push('slice_layer_without_partitioning_rbsp');
41011              break;
41012            case 0x05:
41013              result.push('slice_layer_without_partitioning_rbsp_idr');
41014              break;
41015            case 0x06:
41016              result.push('sei_rbsp');
41017              break;
41018            case 0x07:
41019              result.push('seq_parameter_set_rbsp');
41020              break;
41021            case 0x08:
41022              result.push('pic_parameter_set_rbsp');
41023              break;
41024            case 0x09:
41025              result.push('access_unit_delimiter_rbsp');
41026              break;
41027            default:
41028              result.push(('UNKNOWN NAL - ' + avcStream[i]) & 0x1f);
41029              break;
41030          }
41031        }
41032        return result;
41033      },
41034      // registry of handlers for individual mp4 box types
41035      parse = {
41036        // codingname, not a first-class box type. stsd entries share the
41037        // same format as real boxes so the parsing infrastructure can be
41038        // shared
41039        avc1: function (data) {
41040          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
41041          return {
41042            dataReferenceIndex: view.getUint16(6),
41043            width: view.getUint16(24),
41044            height: view.getUint16(26),
41045            horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
41046            vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
41047            frameCount: view.getUint16(40),
41048            depth: view.getUint16(74),
41049            config: inspectMp4(data.subarray(78, data.byteLength)),
41050          };
41051        },
41052        avcC: function (data) {
41053          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41054            result = {
41055              configurationVersion: data[0],
41056              avcProfileIndication: data[1],
41057              profileCompatibility: data[2],
41058              avcLevelIndication: data[3],
41059              lengthSizeMinusOne: data[4] & 0x03,
41060              sps: [],
41061              pps: [],
41062            },
41063            numOfSequenceParameterSets = data[5] & 0x1f,
41064            numOfPictureParameterSets,
41065            nalSize,
41066            offset,
41067            i; // iterate past any SPSs
41068
41069          offset = 6;
41070          for (i = 0; i < numOfSequenceParameterSets; i++) {
41071            nalSize = view.getUint16(offset);
41072            offset += 2;
41073            result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
41074            offset += nalSize;
41075          } // iterate past any PPSs
41076
41077          numOfPictureParameterSets = data[offset];
41078          offset++;
41079          for (i = 0; i < numOfPictureParameterSets; i++) {
41080            nalSize = view.getUint16(offset);
41081            offset += 2;
41082            result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
41083            offset += nalSize;
41084          }
41085          return result;
41086        },
41087        btrt: function (data) {
41088          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
41089          return {
41090            bufferSizeDB: view.getUint32(0),
41091            maxBitrate: view.getUint32(4),
41092            avgBitrate: view.getUint32(8),
41093          };
41094        },
41095        edts: function edts(data) {
41096          return {
41097            boxes: inspectMp4(data),
41098          };
41099        },
41100        elst: function elst(data) {
41101          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41102            result = {
41103              version: view.getUint8(0),
41104              flags: new Uint8Array(data.subarray(1, 4)),
41105              edits: [],
41106            },
41107            entryCount = view.getUint32(4),
41108            i;
41109          for (i = 8; entryCount; entryCount--) {
41110            if (result.version === 0) {
41111              result.edits.push({
41112                segmentDuration: view.getUint32(i),
41113                mediaTime: view.getInt32(i + 4),
41114                mediaRate: view.getUint16(i + 8) + view.getUint16(i + 10) / (256 * 256),
41115              });
41116              i += 12;
41117            } else {
41118              result.edits.push({
41119                segmentDuration: getUint64$2(data.subarray(i)),
41120                mediaTime: getUint64$2(data.subarray(i + 8)),
41121                mediaRate: view.getUint16(i + 16) + view.getUint16(i + 18) / (256 * 256),
41122              });
41123              i += 20;
41124            }
41125          }
41126          return result;
41127        },
41128        esds: function (data) {
41129          return {
41130            version: data[0],
41131            flags: new Uint8Array(data.subarray(1, 4)),
41132            esId: (data[6] << 8) | data[7],
41133            streamPriority: data[8] & 0x1f,
41134            decoderConfig: {
41135              objectProfileIndication: data[11],
41136              streamType: (data[12] >>> 2) & 0x3f,
41137              bufferSize: (data[13] << 16) | (data[14] << 8) | data[15],
41138              maxBitrate: (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19],
41139              avgBitrate: (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23],
41140              decoderConfigDescriptor: {
41141                tag: data[24],
41142                length: data[25],
41143                audioObjectType: (data[26] >>> 3) & 0x1f,
41144                samplingFrequencyIndex: ((data[26] & 0x07) << 1) | ((data[27] >>> 7) & 0x01),
41145                channelConfiguration: (data[27] >>> 3) & 0x0f,
41146              },
41147            },
41148          };
41149        },
41150        ftyp: function (data) {
41151          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41152            result = {
41153              majorBrand: parseType$2(data.subarray(0, 4)),
41154              minorVersion: view.getUint32(4),
41155              compatibleBrands: [],
41156            },
41157            i = 8;
41158          while (i < data.byteLength) {
41159            result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
41160            i += 4;
41161          }
41162          return result;
41163        },
41164        dinf: function (data) {
41165          return {
41166            boxes: inspectMp4(data),
41167          };
41168        },
41169        dref: function (data) {
41170          return {
41171            version: data[0],
41172            flags: new Uint8Array(data.subarray(1, 4)),
41173            dataReferences: inspectMp4(data.subarray(8)),
41174          };
41175        },
41176        hdlr: function (data) {
41177          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41178            result = {
41179              version: view.getUint8(0),
41180              flags: new Uint8Array(data.subarray(1, 4)),
41181              handlerType: parseType$2(data.subarray(8, 12)),
41182              name: '',
41183            },
41184            i = 8; // parse out the name field
41185
41186          for (i = 24; i < data.byteLength; i++) {
41187            if (data[i] === 0x00) {
41188              // the name field is null-terminated
41189              i++;
41190              break;
41191            }
41192            result.name += String.fromCharCode(data[i]);
41193          } // decode UTF-8 to javascript's internal representation
41194          // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
41195
41196          result.name = decodeURIComponent(escape(result.name));
41197          return result;
41198        },
41199        mdat: function (data) {
41200          return {
41201            byteLength: data.byteLength,
41202            nals: nalParse(data),
41203          };
41204        },
41205        mdhd: function (data) {
41206          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41207            i = 4,
41208            language,
41209            result = {
41210              version: view.getUint8(0),
41211              flags: new Uint8Array(data.subarray(1, 4)),
41212              language: '',
41213            };
41214          if (result.version === 1) {
41215            i += 4;
41216            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41217
41218            i += 8;
41219            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41220
41221            i += 4;
41222            result.timescale = view.getUint32(i);
41223            i += 8;
41224            result.duration = view.getUint32(i); // truncating top 4 bytes
41225          } else {
41226            result.creationTime = parseMp4Date(view.getUint32(i));
41227            i += 4;
41228            result.modificationTime = parseMp4Date(view.getUint32(i));
41229            i += 4;
41230            result.timescale = view.getUint32(i);
41231            i += 4;
41232            result.duration = view.getUint32(i);
41233          }
41234          i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
41235          // each field is the packed difference between its ASCII value and 0x60
41236
41237          language = view.getUint16(i);
41238          result.language += String.fromCharCode((language >> 10) + 0x60);
41239          result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
41240          result.language += String.fromCharCode((language & 0x1f) + 0x60);
41241          return result;
41242        },
41243        mdia: function (data) {
41244          return {
41245            boxes: inspectMp4(data),
41246          };
41247        },
41248        mfhd: function (data) {
41249          return {
41250            version: data[0],
41251            flags: new Uint8Array(data.subarray(1, 4)),
41252            sequenceNumber: (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7],
41253          };
41254        },
41255        minf: function (data) {
41256          return {
41257            boxes: inspectMp4(data),
41258          };
41259        },
41260        // codingname, not a first-class box type. stsd entries share the
vendor: 21,065 bytes, lines 41261-41874
41261        // same format as real boxes so the parsing infrastructure can be
41262        // shared
41263        mp4a: function (data) {
41264          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41265            result = {
41266              // 6 bytes reserved
41267              dataReferenceIndex: view.getUint16(6),
41268              // 4 + 4 bytes reserved
41269              channelcount: view.getUint16(16),
41270              samplesize: view.getUint16(18),
41271              // 2 bytes pre_defined
41272              // 2 bytes reserved
41273              samplerate: view.getUint16(24) + view.getUint16(26) / 65536,
41274            }; // if there are more bytes to process, assume this is an ISO/IEC
41275          // 14496-14 MP4AudioSampleEntry and parse the ESDBox
41276
41277          if (data.byteLength > 28) {
41278            result.streamDescriptor = inspectMp4(data.subarray(28))[0];
41279          }
41280          return result;
41281        },
41282        moof: function (data) {
41283          return {
41284            boxes: inspectMp4(data),
41285          };
41286        },
41287        moov: function (data) {
41288          return {
41289            boxes: inspectMp4(data),
41290          };
41291        },
41292        mvex: function (data) {
41293          return {
41294            boxes: inspectMp4(data),
41295          };
41296        },
41297        mvhd: function (data) {
41298          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41299            i = 4,
41300            result = {
41301              version: view.getUint8(0),
41302              flags: new Uint8Array(data.subarray(1, 4)),
41303            };
41304          if (result.version === 1) {
41305            i += 4;
41306            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41307
41308            i += 8;
41309            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41310
41311            i += 4;
41312            result.timescale = view.getUint32(i);
41313            i += 8;
41314            result.duration = view.getUint32(i); // truncating top 4 bytes
41315          } else {
41316            result.creationTime = parseMp4Date(view.getUint32(i));
41317            i += 4;
41318            result.modificationTime = parseMp4Date(view.getUint32(i));
41319            i += 4;
41320            result.timescale = view.getUint32(i);
41321            i += 4;
41322            result.duration = view.getUint32(i);
41323          }
41324          i += 4; // convert fixed-point, base 16 back to a number
41325
41326          result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
41327          i += 4;
41328          result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
41329          i += 2;
41330          i += 2;
41331          i += 2 * 4;
41332          result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
41333          i += 9 * 4;
41334          i += 6 * 4;
41335          result.nextTrackId = view.getUint32(i);
41336          return result;
41337        },
41338        pdin: function (data) {
41339          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
41340          return {
41341            version: view.getUint8(0),
41342            flags: new Uint8Array(data.subarray(1, 4)),
41343            rate: view.getUint32(4),
41344            initialDelay: view.getUint32(8),
41345          };
41346        },
41347        sdtp: function (data) {
41348          var result = {
41349              version: data[0],
41350              flags: new Uint8Array(data.subarray(1, 4)),
41351              samples: [],
41352            },
41353            i;
41354          for (i = 4; i < data.byteLength; i++) {
41355            result.samples.push({
41356              dependsOn: (data[i] & 0x30) >> 4,
41357              isDependedOn: (data[i] & 0x0c) >> 2,
41358              hasRedundancy: data[i] & 0x03,
41359            });
41360          }
41361          return result;
41362        },
41363        sidx: parseSidx_1,
41364        smhd: function (data) {
41365          return {
41366            version: data[0],
41367            flags: new Uint8Array(data.subarray(1, 4)),
41368            balance: data[4] + data[5] / 256,
41369          };
41370        },
41371        stbl: function (data) {
41372          return {
41373            boxes: inspectMp4(data),
41374          };
41375        },
41376        ctts: function (data) {
41377          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41378            result = {
41379              version: view.getUint8(0),
41380              flags: new Uint8Array(data.subarray(1, 4)),
41381              compositionOffsets: [],
41382            },
41383            entryCount = view.getUint32(4),
41384            i;
41385          for (i = 8; entryCount; i += 8, entryCount--) {
41386            result.compositionOffsets.push({
41387              sampleCount: view.getUint32(i),
41388              sampleOffset: view[result.version === 0 ? 'getUint32' : 'getInt32'](i + 4),
41389            });
41390          }
41391          return result;
41392        },
41393        stss: function (data) {
41394          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41395            result = {
41396              version: view.getUint8(0),
41397              flags: new Uint8Array(data.subarray(1, 4)),
41398              syncSamples: [],
41399            },
41400            entryCount = view.getUint32(4),
41401            i;
41402          for (i = 8; entryCount; i += 4, entryCount--) {
41403            result.syncSamples.push(view.getUint32(i));
41404          }
41405          return result;
41406        },
41407        stco: function (data) {
41408          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41409            result = {
41410              version: data[0],
41411              flags: new Uint8Array(data.subarray(1, 4)),
41412              chunkOffsets: [],
41413            },
41414            entryCount = view.getUint32(4),
41415            i;
41416          for (i = 8; entryCount; i += 4, entryCount--) {
41417            result.chunkOffsets.push(view.getUint32(i));
41418          }
41419          return result;
41420        },
41421        stsc: function (data) {
41422          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41423            entryCount = view.getUint32(4),
41424            result = {
41425              version: data[0],
41426              flags: new Uint8Array(data.subarray(1, 4)),
41427              sampleToChunks: [],
41428            },
41429            i;
41430          for (i = 8; entryCount; i += 12, entryCount--) {
41431            result.sampleToChunks.push({
41432              firstChunk: view.getUint32(i),
41433              samplesPerChunk: view.getUint32(i + 4),
41434              sampleDescriptionIndex: view.getUint32(i + 8),
41435            });
41436          }
41437          return result;
41438        },
41439        stsd: function (data) {
41440          return {
41441            version: data[0],
41442            flags: new Uint8Array(data.subarray(1, 4)),
41443            sampleDescriptions: inspectMp4(data.subarray(8)),
41444          };
41445        },
41446        stsz: function (data) {
41447          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41448            result = {
41449              version: data[0],
41450              flags: new Uint8Array(data.subarray(1, 4)),
41451              sampleSize: view.getUint32(4),
41452              entries: [],
41453            },
41454            i;
41455          for (i = 12; i < data.byteLength; i += 4) {
41456            result.entries.push(view.getUint32(i));
41457          }
41458          return result;
41459        },
41460        stts: function (data) {
41461          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41462            result = {
41463              version: data[0],
41464              flags: new Uint8Array(data.subarray(1, 4)),
41465              timeToSamples: [],
41466            },
41467            entryCount = view.getUint32(4),
41468            i;
41469          for (i = 8; entryCount; i += 8, entryCount--) {
41470            result.timeToSamples.push({
41471              sampleCount: view.getUint32(i),
41472              sampleDelta: view.getUint32(i + 4),
41473            });
41474          }
41475          return result;
41476        },
41477        styp: function (data) {
41478          return parse.ftyp(data);
41479        },
41480        tfdt: parseTfdt$3,
41481        tfhd: parseTfhd$2,
41482        tkhd: function (data) {
41483          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
41484            i = 4,
41485            result = {
41486              version: view.getUint8(0),
41487              flags: new Uint8Array(data.subarray(1, 4)),
41488            };
41489          if (result.version === 1) {
41490            i += 4;
41491            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41492
41493            i += 8;
41494            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
41495
41496            i += 4;
41497            result.trackId = view.getUint32(i);
41498            i += 4;
41499            i += 8;
41500            result.duration = view.getUint32(i); // truncating top 4 bytes
41501          } else {
41502            result.creationTime = parseMp4Date(view.getUint32(i));
41503            i += 4;
41504            result.modificationTime = parseMp4Date(view.getUint32(i));
41505            i += 4;
41506            result.trackId = view.getUint32(i);
41507            i += 4;
41508            i += 4;
41509            result.duration = view.getUint32(i);
41510          }
41511          i += 4;
41512          i += 2 * 4;
41513          result.layer = view.getUint16(i);
41514          i += 2;
41515          result.alternateGroup = view.getUint16(i);
41516          i += 2; // convert fixed-point, base 16 back to a number
41517
41518          result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
41519          i += 2;
41520          i += 2;
41521          result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
41522          i += 9 * 4;
41523          result.width = view.getUint16(i) + view.getUint16(i + 2) / 65536;
41524          i += 4;
41525          result.height = view.getUint16(i) + view.getUint16(i + 2) / 65536;
41526          return result;
41527        },
41528        traf: function (data) {
41529          return {
41530            boxes: inspectMp4(data),
41531          };
41532        },
41533        trak: function (data) {
41534          return {
41535            boxes: inspectMp4(data),
41536          };
41537        },
41538        trex: function (data) {
41539          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
41540          return {
41541            version: data[0],
41542            flags: new Uint8Array(data.subarray(1, 4)),
41543            trackId: view.getUint32(4),
41544            defaultSampleDescriptionIndex: view.getUint32(8),
41545            defaultSampleDuration: view.getUint32(12),
41546            defaultSampleSize: view.getUint32(16),
41547            sampleDependsOn: data[20] & 0x03,
41548            sampleIsDependedOn: (data[21] & 0xc0) >> 6,
41549            sampleHasRedundancy: (data[21] & 0x30) >> 4,
41550            samplePaddingValue: (data[21] & 0x0e) >> 1,
41551            sampleIsDifferenceSample: !!(data[21] & 0x01),
41552            sampleDegradationPriority: view.getUint16(22),
41553          };
41554        },
41555        trun: parseTrun$2,
41556        'url ': function (data) {
41557          return {
41558            version: data[0],
41559            flags: new Uint8Array(data.subarray(1, 4)),
41560          };
41561        },
41562        vmhd: function (data) {
41563          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
41564          return {
41565            version: data[0],
41566            flags: new Uint8Array(data.subarray(1, 4)),
41567            graphicsmode: view.getUint16(4),
41568            opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]),
41569          };
41570        },
41571      };
41572    /**
41573     * Return a javascript array of box objects parsed from an ISO base
41574     * media file.
41575     * @param data {Uint8Array} the binary data of the media to be inspected
41576     * @return {array} a javascript array of potentially nested box objects
41577     */
41578
41579    inspectMp4 = function (data) {
41580      var i = 0,
41581        result = [],
41582        view,
41583        size,
41584        type,
41585        end,
41586        box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
41587
41588      var ab = new ArrayBuffer(data.length);
41589      var v = new Uint8Array(ab);
41590      for (var z = 0; z < data.length; ++z) {
41591        v[z] = data[z];
41592      }
41593      view = new DataView(ab);
41594      while (i < data.byteLength) {
41595        // parse box data
41596        size = view.getUint32(i);
41597        type = parseType$2(data.subarray(i + 4, i + 8));
41598        end = size > 1 ? i + size : data.byteLength; // parse type-specific data
41599
41600        box = (
41601          parse[type] ||
41602          function (data) {
41603            return {
41604              data: data,
41605            };
41606          }
41607        )(data.subarray(i + 8, end));
41608        box.size = size;
41609        box.type = type; // store this box and move to the next
41610
41611        result.push(box);
41612        i = end;
41613      }
41614      return result;
41615    };
41616    /**
41617     * Returns a textual representation of the javascript represtentation
41618     * of an MP4 file. You can use it as an alternative to
41619     * JSON.stringify() to compare inspected MP4s.
41620     * @param inspectedMp4 {array} the parsed array of boxes in an MP4
41621     * file
41622     * @param depth {number} (optional) the number of ancestor boxes of
41623     * the elements of inspectedMp4. Assumed to be zero if unspecified.
41624     * @return {string} a text representation of the parsed MP4
41625     */
41626
41627    textifyMp4 = function (inspectedMp4, depth) {
41628      var indent;
41629      depth = depth || 0;
41630      indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
41631
41632      return inspectedMp4
41633        .map(function (box, index) {
41634          // list the box type first at the current indentation level
41635          return (
41636            indent +
41637            box.type +
41638            '\n' +
41639            // the type is already included and handle child boxes separately
41640            Object.keys(box)
41641              .filter(function (key) {
41642                return key !== 'type' && key !== 'boxes'; // output all the box properties
41643              })
41644              .map(function (key) {
41645                var prefix = indent + '  ' + key + ': ',
41646                  value = box[key]; // print out raw bytes as hexademical
41647
41648                if (value instanceof Uint8Array || value instanceof Uint32Array) {
41649                  var bytes = Array.prototype.slice
41650                    .call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
41651                    .map(function (byte) {
41652                      return ' ' + ('00' + byte.toString(16)).slice(-2);
41653                    })
41654                    .join('')
41655                    .match(/.{1,24}/g);
41656                  if (!bytes) {
41657                    return prefix + '<>';
41658                  }
41659                  if (bytes.length === 1) {
41660                    return prefix + '<' + bytes.join('').slice(1) + '>';
41661                  }
41662                  return (
41663                    prefix +
41664                    '<\n' +
41665                    bytes
41666                      .map(function (line) {
41667                        return indent + '  ' + line;
41668                      })
41669                      .join('\n') +
41670                    '\n' +
41671                    indent +
41672                    '  >'
41673                  );
41674                } // stringify generic objects
41675
41676                return (
41677                  prefix +
41678                  JSON.stringify(value, null, 2)
41679                    .split('\n')
41680                    .map(function (line, index) {
41681                      if (index === 0) {
41682                        return line;
41683                      }
41684                      return indent + '  ' + line;
41685                    })
41686                    .join('\n')
41687                );
41688              })
41689              .join('\n') +
41690            // recursively textify the child boxes
41691            (box.boxes ? '\n' + textifyMp4(box.boxes, depth + 1) : '')
41692          );
41693        })
41694        .join('\n');
41695    };
41696    var mp4Inspector = {
41697      inspect: inspectMp4,
41698      textify: textifyMp4,
41699      parseType: parseType$2,
41700      findBox: findBox$4,
41701      parseTraf: parse.traf,
41702      parseTfdt: parse.tfdt,
41703      parseHdlr: parse.hdlr,
41704      parseTfhd: parse.tfhd,
41705      parseTrun: parse.trun,
41706      parseSidx: parse.sidx,
41707    };
41708    /**
41709     * Returns the first string in the data array ending with a null char '\0'
41710     * @param {UInt8} data
41711     * @returns the string with the null char
41712     */
41713
41714    var uint8ToCString$1 = function (data) {
41715      var index = 0;
41716      var curChar = String.fromCharCode(data[index]);
41717      var retString = '';
41718      while (curChar !== '\0') {
41719        retString += curChar;
41720        index++;
41721        curChar = String.fromCharCode(data[index]);
41722      } // Add nullChar
41723
41724      retString += curChar;
41725      return retString;
41726    };
41727    var string = {
41728      uint8ToCString: uint8ToCString$1,
41729    };
41730    var uint8ToCString = string.uint8ToCString;
41731    var getUint64$1 = numbers.getUint64;
41732    /**
41733     * Based on: ISO/IEC 23009 Section: 5.10.3.3
41734     * References:
41735     * https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
41736     * https://aomediacodec.github.io/id3-emsg/
41737     *
41738     * Takes emsg box data as a uint8 array and returns a emsg box object
41739     * @param {UInt8Array} boxData data from emsg box
41740     * @returns A parsed emsg box object
41741     */
41742
41743    var parseEmsgBox = function (boxData) {
41744      // version + flags
41745      var offset = 4;
41746      var version = boxData[0];
41747      var scheme_id_uri,
41748        value,
41749        timescale,
41750        presentation_time,
41751        presentation_time_delta,
41752        event_duration,
41753        id,
41754        message_data;
41755      if (version === 0) {
41756        scheme_id_uri = uint8ToCString(boxData.subarray(offset));
41757        offset += scheme_id_uri.length;
41758        value = uint8ToCString(boxData.subarray(offset));
41759        offset += value.length;
41760        var dv = new DataView(boxData.buffer);
41761        timescale = dv.getUint32(offset);
41762        offset += 4;
41763        presentation_time_delta = dv.getUint32(offset);
41764        offset += 4;
41765        event_duration = dv.getUint32(offset);
41766        offset += 4;
41767        id = dv.getUint32(offset);
41768        offset += 4;
41769      } else if (version === 1) {
41770        var dv = new DataView(boxData.buffer);
41771        timescale = dv.getUint32(offset);
41772        offset += 4;
41773        presentation_time = getUint64$1(boxData.subarray(offset));
41774        offset += 8;
41775        event_duration = dv.getUint32(offset);
41776        offset += 4;
41777        id = dv.getUint32(offset);
41778        offset += 4;
41779        scheme_id_uri = uint8ToCString(boxData.subarray(offset));
41780        offset += scheme_id_uri.length;
41781        value = uint8ToCString(boxData.subarray(offset));
41782        offset += value.length;
41783      }
41784      message_data = new Uint8Array(boxData.subarray(offset, boxData.byteLength));
41785      var emsgBox = {
41786        scheme_id_uri,
41787        value,
41788        // if timescale is undefined or 0 set to 1
41789        timescale: timescale ? timescale : 1,
41790        presentation_time,
41791        presentation_time_delta,
41792        event_duration,
41793        id,
41794        message_data,
41795      };
41796      return isValidEmsgBox(version, emsgBox) ? emsgBox : undefined;
41797    };
41798    /**
41799     * Scales a presentation time or time delta with an offset with a provided timescale
41800     * @param {number} presentationTime
41801     * @param {number} timescale
41802     * @param {number} timeDelta
41803     * @param {number} offset
41804     * @returns the scaled time as a number
41805     */
41806
41807    var scaleTime = function (presentationTime, timescale, timeDelta, offset) {
41808      return presentationTime || presentationTime === 0
41809        ? presentationTime / timescale
41810        : offset + timeDelta / timescale;
41811    };
41812    /**
41813     * Checks the emsg box data for validity based on the version
41814     * @param {number} version of the emsg box to validate
41815     * @param {Object} emsg the emsg data to validate
41816     * @returns if the box is valid as a boolean
41817     */
41818
41819    var isValidEmsgBox = function (version, emsg) {
41820      var hasScheme = emsg.scheme_id_uri !== '\0';
41821      var isValidV0Box = version === 0 && isDefined(emsg.presentation_time_delta) && hasScheme;
41822      var isValidV1Box = version === 1 && isDefined(emsg.presentation_time) && hasScheme; // Only valid versions of emsg are 0 and 1
41823
41824      return (!(version > 1) && isValidV0Box) || isValidV1Box;
41825    }; // Utility function to check if an object is defined
41826
41827    var isDefined = function (data) {
41828      return data !== undefined || data !== null;
41829    };
41830    var emsg$1 = {
41831      parseEmsgBox: parseEmsgBox,
41832      scaleTime: scaleTime,
41833    };
41834    var win;
41835    if (typeof window !== 'undefined') {
41836      win = window;
41837    } else if (typeof commonjsGlobal !== 'undefined') {
41838      win = commonjsGlobal;
41839    } else if (typeof self !== 'undefined') {
41840      win = self;
41841    } else {
41842      win = {};
41843    }
41844    var window_1 = win;
41845    /**
41846     * mux.js
41847     *
41848     * Copyright (c) Brightcove
41849     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
41850     *
41851     * Utilities to detect basic properties and metadata about MP4s.
41852     */
41853
41854    var toUnsigned = bin.toUnsigned;
41855    var toHexString = bin.toHexString;
41856    var findBox$3 = findBox_1;
41857    var parseType$1 = parseType_1;
41858    var emsg = emsg$1;
41859    var parseTfhd$1 = parseTfhd$2;
41860    var parseTrun$1 = parseTrun$2;
41861    var parseTfdt$2 = parseTfdt$3;
41862    var getUint64 = numbers.getUint64;
41863    var timescale,
41864      startTime,
41865      compositionStartTime,
41866      getVideoTrackIds,
41867      getTracks,
41868      getTimescaleFromMediaHeader$1,
41869      getEmsgID3;
41870    var window$2 = window_1;
41871    var parseId3Frames = parseId3.parseId3Frames;
41872    /**
41873     * Parses an MP4 initialization segment and extracts the timescale
41874     * values for any declared tracks. Timescale values indicate the
41875     * number of clock ticks per second to assume for time-based values
41876     * elsewhere in the MP4.
41877     *
41878     * To determine the start time of an MP4, you need two pieces of
41879     * information: the timescale unit and the earliest base media decode
41880     * time. Multiple timescales can be specified within an MP4 but the
41881     * base media decode time is always expressed in the timescale from
41882     * the media header box for the track:
41883     * ```
41884     * moov > trak > mdia > mdhd.timescale
41885     * ```
41886     * @param init {Uint8Array} the bytes of the init segment
41887     * @return {object} a hash of track ids to timescale values or null if
41888     * the init segment is malformed.
41889     */
41890
41891    timescale = function (init) {
41892      var result = {},
41893        traks = findBox$3(init, ['moov', 'trak']); // mdhd timescale
41894
41895      return traks.reduce(function (result, trak) {
41896        var tkhd, version, index, id, mdhd;
41897        tkhd = findBox$3(trak, ['tkhd'])[0];
41898        if (!tkhd) {
41899          return null;
41900        }
41901        version = tkhd[0];
41902        index = version === 0 ? 12 : 20;
41903        id = toUnsigned(
41904          (tkhd[index] << 24) | (tkhd[index + 1] << 16) | (tkhd[index + 2] << 8) | tkhd[index + 3]
41905        );
41906        mdhd = findBox$3(trak, ['mdia', 'mdhd'])[0];
41907        if (!mdhd) {
41908          return null;
41909        }
41910        version = mdhd[0];
41911        index = version === 0 ? 12 : 20;
41912        result[id] = toUnsigned(
41913          (mdhd[index] << 24) | (mdhd[index + 1] << 16) | (mdhd[index + 2] << 8) | mdhd[index + 3]
41914        );
41915        return result;
41916      }, result);
41917    };
41918    /**
41919     * Determine the base media decode start time, in seconds, for an MP4
41920     * fragment. If multiple fragments are specified, the earliest time is
41921     * returned.
41922     *
41923     * The base media decode time can be parsed from track fragment
41924     * metadata:
41925     * ```
41926     * moof > traf > tfdt.baseMediaDecodeTime
41927     * ```
41928     * It requires the timescale value from the mdhd to interpret.
41929     *
41930     * @param timescale {object} a hash of track ids to timescale values.
41931     * @return {number} the earliest base media decode start time for the
41932     * fragment, in seconds
41933     */
41934
41935    startTime = function (timescale, fragment) {
41936      var trafs; // we need info from two childrend of each track fragment box
41937
41938      trafs = findBox$3(fragment, ['moof', 'traf']); // determine the start times for each track
41939
41940      var lowestTime = trafs.reduce(function (acc, traf) {
41941        var tfhd = findBox$3(traf, ['tfhd'])[0]; // get the track id from the tfhd
41942
41943        var id = toUnsigned((tfhd[4] << 24) | (tfhd[5] << 16) | (tfhd[6] << 8) | tfhd[7]); // assume a 90kHz clock if no timescale was specified
41944
41945        var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
41946
41947        var tfdt = findBox$3(traf, ['tfdt'])[0];
41948        var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
41949        var baseTime; // version 1 is 64 bit
41950
41951        if (tfdt[0] === 1) {
41952          baseTime = getUint64(tfdt.subarray(4, 12));
41953        } else {
41954          baseTime = dv.getUint32(4);
41955        }
vendor: 14,631 bytes, lines 41955-42324
41955 // convert base time to seconds if it is a valid number.
41956
41957        let seconds;
41958        if (typeof baseTime === 'bigint') {
41959          seconds = baseTime / window$2.BigInt(scale);
41960        } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
41961          seconds = baseTime / scale;
41962        }
41963        if (seconds < Number.MAX_SAFE_INTEGER) {
41964          seconds = Number(seconds);
41965        }
41966        if (seconds < acc) {
41967          acc = seconds;
41968        }
41969        return acc;
41970      }, Infinity);
41971      return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
41972    };
41973    /**
41974     * Determine the composition start, in seconds, for an MP4
41975     * fragment.
41976     *
41977     * The composition start time of a fragment can be calculated using the base
41978     * media decode time, composition time offset, and timescale, as follows:
41979     *
41980     * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
41981     *
41982     * All of the aforementioned information is contained within a media fragment's
41983     * `traf` box, except for timescale info, which comes from the initialization
41984     * segment, so a track id (also contained within a `traf`) is also necessary to
41985     * associate it with a timescale
41986     *
41987     *
41988     * @param timescales {object} - a hash of track ids to timescale values.
41989     * @param fragment {Unit8Array} - the bytes of a media segment
41990     * @return {number} the composition start time for the fragment, in seconds
41991     **/
41992
41993    compositionStartTime = function (timescales, fragment) {
41994      var trafBoxes = findBox$3(fragment, ['moof', 'traf']);
41995      var baseMediaDecodeTime = 0;
41996      var compositionTimeOffset = 0;
41997      var trackId;
41998      if (trafBoxes && trafBoxes.length) {
41999        // The spec states that track run samples contained within a `traf` box are contiguous, but
42000        // it does not explicitly state whether the `traf` boxes themselves are contiguous.
42001        // We will assume that they are, so we only need the first to calculate start time.
42002        var tfhd = findBox$3(trafBoxes[0], ['tfhd'])[0];
42003        var trun = findBox$3(trafBoxes[0], ['trun'])[0];
42004        var tfdt = findBox$3(trafBoxes[0], ['tfdt'])[0];
42005        if (tfhd) {
42006          var parsedTfhd = parseTfhd$1(tfhd);
42007          trackId = parsedTfhd.trackId;
42008        }
42009        if (tfdt) {
42010          var parsedTfdt = parseTfdt$2(tfdt);
42011          baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
42012        }
42013        if (trun) {
42014          var parsedTrun = parseTrun$1(trun);
42015          if (parsedTrun.samples && parsedTrun.samples.length) {
42016            compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
42017          }
42018        }
42019      } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
42020      // specified.
42021
42022      var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
42023
42024      if (typeof baseMediaDecodeTime === 'bigint') {
42025        compositionTimeOffset = window$2.BigInt(compositionTimeOffset);
42026        timescale = window$2.BigInt(timescale);
42027      }
42028      var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
42029      if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
42030        result = Number(result);
42031      }
42032      return result;
42033    };
42034    /**
42035     * Find the trackIds of the video tracks in this source.
42036     * Found by parsing the Handler Reference and Track Header Boxes:
42037     *   moov > trak > mdia > hdlr
42038     *   moov > trak > tkhd
42039     *
42040     * @param {Uint8Array} init - The bytes of the init segment for this source
42041     * @return {Number[]} A list of trackIds
42042     *
42043     * @see ISO-BMFF-12/2015, Section 8.4.3
42044     **/
42045
42046    getVideoTrackIds = function (init) {
42047      var traks = findBox$3(init, ['moov', 'trak']);
42048      var videoTrackIds = [];
42049      traks.forEach(function (trak) {
42050        var hdlrs = findBox$3(trak, ['mdia', 'hdlr']);
42051        var tkhds = findBox$3(trak, ['tkhd']);
42052        hdlrs.forEach(function (hdlr, index) {
42053          var handlerType = parseType$1(hdlr.subarray(8, 12));
42054          var tkhd = tkhds[index];
42055          var view;
42056          var version;
42057          var trackId;
42058          if (handlerType === 'vide') {
42059            view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
42060            version = view.getUint8(0);
42061            trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
42062            videoTrackIds.push(trackId);
42063          }
42064        });
42065      });
42066      return videoTrackIds;
42067    };
42068    getTimescaleFromMediaHeader$1 = function (mdhd) {
42069      // mdhd is a FullBox, meaning it will have its own version as the first byte
42070      var version = mdhd[0];
42071      var index = version === 0 ? 12 : 20;
42072      return toUnsigned(
42073        (mdhd[index] << 24) | (mdhd[index + 1] << 16) | (mdhd[index + 2] << 8) | mdhd[index + 3]
42074      );
42075    };
42076    /**
42077     * Get all the video, audio, and hint tracks from a non fragmented
42078     * mp4 segment
42079     */
42080
42081    getTracks = function (init) {
42082      var traks = findBox$3(init, ['moov', 'trak']);
42083      var tracks = [];
42084      traks.forEach(function (trak) {
42085        var track = {};
42086        var tkhd = findBox$3(trak, ['tkhd'])[0];
42087        var view, tkhdVersion; // id
42088
42089        if (tkhd) {
42090          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
42091          tkhdVersion = view.getUint8(0);
42092          track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
42093        }
42094        var hdlr = findBox$3(trak, ['mdia', 'hdlr'])[0]; // type
42095
42096        if (hdlr) {
42097          var type = parseType$1(hdlr.subarray(8, 12));
42098          if (type === 'vide') {
42099            track.type = 'video';
42100          } else if (type === 'soun') {
42101            track.type = 'audio';
42102          } else {
42103            track.type = type;
42104          }
42105        } // codec
42106
42107        var stsd = findBox$3(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
42108        if (stsd) {
42109          var sampleDescriptions = stsd.subarray(8); // gives the codec type string
42110
42111          track.codec = parseType$1(sampleDescriptions.subarray(4, 8));
42112          var codecBox = findBox$3(sampleDescriptions, [track.codec])[0];
42113          var codecConfig, codecConfigType;
42114          if (codecBox) {
42115            // https://tools.ietf.org/html/rfc6381#section-3.3
42116            if (/^[asm]vc[1-9]$/i.test(track.codec)) {
42117              // we don't need anything but the "config" parameter of the
42118              // avc1 codecBox
42119              codecConfig = codecBox.subarray(78);
42120              codecConfigType = parseType$1(codecConfig.subarray(4, 8));
42121              if (codecConfigType === 'avcC' && codecConfig.length > 11) {
42122                track.codec += '.'; // left padded with zeroes for single digit hex
42123                // profile idc
42124
42125                track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
42126
42127                track.codec += toHexString(codecConfig[10]); // level idc
42128
42129                track.codec += toHexString(codecConfig[11]);
42130              } else {
42131                // TODO: show a warning that we couldn't parse the codec
42132                // and are using the default
42133                track.codec = 'avc1.4d400d';
42134              }
42135            } else if (/^mp4[a,v]$/i.test(track.codec)) {
42136              // we do not need anything but the streamDescriptor of the mp4a codecBox
42137              codecConfig = codecBox.subarray(28);
42138              codecConfigType = parseType$1(codecConfig.subarray(4, 8));
42139              if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
42140                track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
42141
42142                track.codec += '.' + toHexString((codecConfig[20] >>> 2) & 0x3f).replace(/^0/, '');
42143              } else {
42144                // TODO: show a warning that we couldn't parse the codec
42145                // and are using the default
42146                track.codec = 'mp4a.40.2';
42147              }
42148            } else {
42149              // flac, opus, etc
42150              track.codec = track.codec.toLowerCase();
42151            }
42152          }
42153        }
42154        var mdhd = findBox$3(trak, ['mdia', 'mdhd'])[0];
42155        if (mdhd) {
42156          track.timescale = getTimescaleFromMediaHeader$1(mdhd);
42157        }
42158        tracks.push(track);
42159      });
42160      return tracks;
42161    };
42162    /**
42163     * Returns an array of emsg ID3 data from the provided segmentData.
42164     * An offset can also be provided as the Latest Arrival Time to calculate
42165     * the Event Start Time of v0 EMSG boxes.
42166     * See: https://dashif-documents.azurewebsites.net/Events/master/event.html#Inband-event-timing
42167     *
42168     * @param {Uint8Array} segmentData the segment byte array.
42169     * @param {number} offset the segment start time or Latest Arrival Time,
42170     * @return {Object[]} an array of ID3 parsed from EMSG boxes
42171     */
42172
42173    getEmsgID3 = function (segmentData, offset = 0) {
42174      var emsgBoxes = findBox$3(segmentData, ['emsg']);
42175      return emsgBoxes.map((data) => {
42176        var parsedBox = emsg.parseEmsgBox(new Uint8Array(data));
42177        var parsedId3Frames = parseId3Frames(parsedBox.message_data);
42178        return {
42179          cueTime: emsg.scaleTime(
42180            parsedBox.presentation_time,
42181            parsedBox.timescale,
42182            parsedBox.presentation_time_delta,
42183            offset
42184          ),
42185          duration: emsg.scaleTime(parsedBox.event_duration, parsedBox.timescale),
42186          frames: parsedId3Frames,
42187        };
42188      });
42189    };
42190    var probe$2 = {
42191      // export mp4 inspector's findBox and parseType for backwards compatibility
42192      findBox: findBox$3,
42193      parseType: parseType$1,
42194      timescale: timescale,
42195      startTime: startTime,
42196      compositionStartTime: compositionStartTime,
42197      videoTrackIds: getVideoTrackIds,
42198      tracks: getTracks,
42199      getTimescaleFromMediaHeader: getTimescaleFromMediaHeader$1,
42200      getEmsgID3: getEmsgID3,
42201    };
42202    const { parseTrun } = mp4Inspector;
42203    const { findBox: findBox$2 } = probe$2;
42204    var window$1 = window_1;
42205    /**
42206     * Utility function for parsing data from mdat boxes.
42207     * @param {Array<Uint8Array>} segment the segment data to create mdat/traf pairs from.
42208     * @returns mdat and traf boxes paired up for easier parsing.
42209     */
42210
42211    var getMdatTrafPairs$2 = function (segment) {
42212      var trafs = findBox$2(segment, ['moof', 'traf']);
42213      var mdats = findBox$2(segment, ['mdat']);
42214      var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
42215
42216      mdats.forEach(function (mdat, index) {
42217        var matchingTraf = trafs[index];
42218        mdatTrafPairs.push({
42219          mdat: mdat,
42220          traf: matchingTraf,
42221        });
42222      });
42223      return mdatTrafPairs;
42224    };
42225    /**
42226    * Parses sample information out of Track Run Boxes and calculates
42227    * the absolute presentation and decode timestamps of each sample.
42228    *
42229    * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
42230    * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
42231        @see ISO-BMFF-12/2015, Section 8.8.12
42232    * @param {Object} tfhd - The parsed Track Fragment Header
42233    *   @see inspect.parseTfhd
42234    * @return {Object[]} the parsed samples
42235    *
42236    * @see ISO-BMFF-12/2015, Section 8.8.8
42237   **/
42238
42239    var parseSamples$2 = function (truns, baseMediaDecodeTime, tfhd) {
42240      var currentDts = baseMediaDecodeTime;
42241      var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
42242      var defaultSampleSize = tfhd.defaultSampleSize || 0;
42243      var trackId = tfhd.trackId;
42244      var allSamples = [];
42245      truns.forEach(function (trun) {
42246        // Note: We currently do not parse the sample table as well
42247        // as the trun. It's possible some sources will require this.
42248        // moov > trak > mdia > minf > stbl
42249        var trackRun = parseTrun(trun);
42250        var samples = trackRun.samples;
42251        samples.forEach(function (sample) {
42252          if (sample.duration === undefined) {
42253            sample.duration = defaultSampleDuration;
42254          }
42255          if (sample.size === undefined) {
42256            sample.size = defaultSampleSize;
42257          }
42258          sample.trackId = trackId;
42259          sample.dts = currentDts;
42260          if (sample.compositionTimeOffset === undefined) {
42261            sample.compositionTimeOffset = 0;
42262          }
42263          if (typeof currentDts === 'bigint') {
42264            sample.pts = currentDts + window$1.BigInt(sample.compositionTimeOffset);
42265            currentDts += window$1.BigInt(sample.duration);
42266          } else {
42267            sample.pts = currentDts + sample.compositionTimeOffset;
42268            currentDts += sample.duration;
42269          }
42270        });
42271        allSamples = allSamples.concat(samples);
42272      });
42273      return allSamples;
42274    };
42275    var samples = {
42276      getMdatTrafPairs: getMdatTrafPairs$2,
42277      parseSamples: parseSamples$2,
42278    };
42279    /**
42280     * mux.js
42281     *
42282     * Copyright (c) Brightcove
42283     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
42284     *
42285     * Reads in-band CEA-708 captions out of FMP4 segments.
42286     * @see https://en.wikipedia.org/wiki/CEA-708
42287     */
42288
42289    var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
42290    var CaptionStream = captionStream.CaptionStream;
42291    var findBox$1 = findBox_1;
42292    var parseTfdt$1 = parseTfdt$3;
42293    var parseTfhd = parseTfhd$2;
42294    var { getMdatTrafPairs: getMdatTrafPairs$1, parseSamples: parseSamples$1 } = samples;
42295    /**
42296     * Maps an offset in the mdat to a sample based on the the size of the samples.
42297     * Assumes that `parseSamples` has been called first.
42298     *
42299     * @param {Number} offset - The offset into the mdat
42300     * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
42301     * @return {?Object} The matching sample, or null if no match was found.
42302     *
42303     * @see ISO-BMFF-12/2015, Section 8.8.8
42304     **/
42305
42306    var mapToSample = function (offset, samples) {
42307      var approximateOffset = offset;
42308      for (var i = 0; i < samples.length; i++) {
42309        var sample = samples[i];
42310        if (approximateOffset < sample.size) {
42311          return sample;
42312        }
42313        approximateOffset -= sample.size;
42314      }
42315      return null;
42316    };
42317    /**
42318     * Finds SEI nal units contained in a Media Data Box.
42319     * Assumes that `parseSamples` has been called first.
42320     *
42321     * @param {Uint8Array} avcStream - The bytes of the mdat
42322     * @param {Object[]} samples - The samples parsed out by `parseSamples`
42323     * @param {Number} trackId - The trackId of this video track
42324     * @return {Object[]}
42324 seiNals - the parsed SEI NALUs found.
42325     *   The contents of the seiNal should match what is expected by
42326     *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
42327     *
42328     * @see ISO-BMFF-12/2015, Section 8.1.1
42329     * @see Rec. ITU-T H.264, 7.3.2.3.1
42330     **/
42331
42332    var findSeiNals = function (avcStream, samples, trackId) {
42333      var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
42334        result = {
42335          logs: [],
42336          seiNals: [],
42337        },
42338        seiNal,
42339        i,
42340        length,
42341        lastMatchedSample;
42342      for (i = 0; i + 4 < avcStream.length; i += length) {
42343        length = avcView.getUint32(i);
42344        i += 4; // Bail if this doesn't appear to be an H264 stream
42345
42346        if (length <= 0) {
42347          continue;
42348        }
42349        switch (avcStream[i] & 0x1f) {
42350          case 0x06:
42351            var data = avcStream.subarray(i + 1, i + 1 + length);
42352            var matchingSample = mapToSample(i, samples);
42353            seiNal = {
42354              nalUnitType: 'sei_rbsp',
42355              size: length,
42356              data: data,
42357              escapedRBSP: discardEmulationPreventionBytes(data),
42358              trackId: trackId,
42359            };
42360            if (matchingSample) {
42361              seiNal.pts = matchingSample.pts;
42362              seiNal.dts = matchingSample.dts;
42363              lastMatchedSample = matchingSample;
42364            } else if (lastMatchedSample) {
42365              // If a matching sample cannot be found, use the last
42366              // sample's values as they should be as close as possible
42367              seiNal.pts = lastMatchedSample.pts;
42368              seiNal.dts = lastMatchedSample.dts;
42369            } else {
42370              result.logs.push({
42371                level: 'warn',
42372                message:
42373                  "We've encountered a nal unit without data at " +
42374                  i +
42375                  ' for trackId ' +
42376                  trackId +
42377                  '. See mux.js#223.',
42378              });
42379              break;
42380            }
42381            result.seiNals.push(seiNal);
42382            break;
42383        }
42384      }
42385      return result;
42386    };
42387    /**
42388     * Parses out caption nals from an FMP4 segment's video tracks.
42389     *
42390     * @param {Uint8Array} segment - The bytes of a single segment
42391     * @param {Number} videoTrackId - The trackId of a video track in the segment
42392     * @return {Object.<Number, Object[]>} A mapping of video trackId to
42393     *   a list of seiNals found in that track
42394     **/
42395
42396    var parseCaptionNals = function (segment, videoTrackId) {
42397      var captionNals = {};
42398      var mdatTrafPairs = getMdatTrafPairs$1(segment);
42399      mdatTrafPairs.forEach(function (pair) {
42400        var mdat = pair.mdat;
42401        var traf = pair.traf;
42402        var tfhd = findBox$1(traf, ['tfhd']); // Exactly 1 tfhd per traf
42403
42404        var headerInfo = parseTfhd(tfhd[0]);
42405        var trackId = headerInfo.trackId;
42406        var tfdt = findBox$1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
42407
42408        var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt$1(tfdt[0]).baseMediaDecodeTime : 0;
42409        var truns = findBox$1(traf, ['trun']);
42410        var samples;
42411        var result; // Only parse video data for the chosen video track
42412
42413        if (videoTrackId === trackId && truns.length > 0) {
42414          samples = parseSamples$1(truns, baseMediaDecodeTime, headerInfo);
42415          result = findSeiNals(mdat, samples, trackId);
42416          if (!captionNals[trackId]) {
42417            captionNals[trackId] = {
42418              seiNals: [],
42419              logs: [],
42420            };
42421          }
42422          captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
42423          captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
42424        }
42425      });
42426      return captionNals;
42427    };
42428    /**
42429     * Parses out inband captions from an MP4 container and returns
42430     * caption objects that can be used by WebVTT and the TextTrack API.
42431     * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
42432     * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
42433     * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
42434     *
42435     * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
42436     * @param {Number} trackId - The id of the video track to parse
42437     * @param {Number} timescale - The timescale for the video track from the init segment
42438     *
42439     * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
42440     * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
42441     * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
42442     * @return {Object[]} parsedCaptions[].content - A list of individual caption segments
42443     * @return {String} parsedCaptions[].content.text - The visible content of the caption segment
42444     * @return {Number} parsedCaptions[].content.line - The line height from 1-15 for positioning of the caption segment
42445     * @return {Number} parsedCaptions[].content.position - The column indent percentage for cue positioning from 10-80
42446     **/
42447
42448    var parseEmbeddedCaptions = function (segment, trackId, timescale) {
42449      var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
42450
42451      if (trackId === null) {
42452        return null;
42453      }
42454      captionNals = parseCaptionNals(segment, trackId);
42455      var trackNals = captionNals[trackId] || {};
42456      return {
42457        seiNals: trackNals.seiNals,
42458        logs: trackNals.logs,
42459        timescale: timescale,
42460      };
42461    };
42462    /**
42463     * Converts SEI NALUs into captions that can be used by video.js
42464     **/
42465
42466    var CaptionParser = function () {
42467      var isInitialized = false;
vendor: 34,761 bytes, lines 42468-43420
42468      var captionStream; // Stores segments seen before trackId and timescale are set
42469
42470      var segmentCache; // Stores video track ID of the track being parsed
42471
42472      var trackId; // Stores the timescale of the track being parsed
42473
42474      var timescale; // Stores captions parsed so far
42475
42476      var parsedCaptions; // Stores whether we are receiving partial data or not
42477
42478      var parsingPartial;
42479      /**
42480       * A method to indicate whether a CaptionParser has been initalized
42481       * @returns {Boolean}
42482       **/
42483
42484      this.isInitialized = function () {
42485        return isInitialized;
42486      };
42487      /**
42488       * Initializes the underlying CaptionStream, SEI NAL parsing
42489       * and management, and caption collection
42490       **/
42491
42492      this.init = function (options) {
42493        captionStream = new CaptionStream();
42494        isInitialized = true;
42495        parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
42496
42497        captionStream.on('data', function (event) {
42498          // Convert to seconds in the source's timescale
42499          event.startTime = event.startPts / timescale;
42500          event.endTime = event.endPts / timescale;
42501          parsedCaptions.captions.push(event);
42502          parsedCaptions.captionStreams[event.stream] = true;
42503        });
42504        captionStream.on('log', function (log) {
42505          parsedCaptions.logs.push(log);
42506        });
42507      };
42508      /**
42509       * Determines if a new video track will be selected
42510       * or if the timescale changed
42511       * @return {Boolean}
42512       **/
42513
42514      this.isNewInit = function (videoTrackIds, timescales) {
42515        if (
42516          (videoTrackIds && videoTrackIds.length === 0) ||
42517          (timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0)
42518        ) {
42519          return false;
42520        }
42521        return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
42522      };
42523      /**
42524       * Parses out SEI captions and interacts with underlying
42525       * CaptionStream to return dispatched captions
42526       *
42527       * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
42528       * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
42529       * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
42530       * @see parseEmbeddedCaptions
42531       * @see m2ts/caption-stream.js
42532       **/
42533
42534      this.parse = function (segment, videoTrackIds, timescales) {
42535        var parsedData;
42536        if (!this.isInitialized()) {
42537          return null; // This is not likely to be a video segment
42538        } else if (!videoTrackIds || !timescales) {
42539          return null;
42540        } else if (this.isNewInit(videoTrackIds, timescales)) {
42541          // Use the first video track only as there is no
42542          // mechanism to switch to other video tracks
42543          trackId = videoTrackIds[0];
42544          timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
42545          // data until we have one.
42546          // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
42547        } else if (trackId === null || !timescale) {
42548          segmentCache.push(segment);
42549          return null;
42550        } // Now that a timescale and trackId is set, parse cached segments
42551
42552        while (segmentCache.length > 0) {
42553          var cachedSegment = segmentCache.shift();
42554          this.parse(cachedSegment, videoTrackIds, timescales);
42555        }
42556        parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
42557        if (parsedData && parsedData.logs) {
42558          parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
42559        }
42560        if (parsedData === null || !parsedData.seiNals) {
42561          if (parsedCaptions.logs.length) {
42562            return {
42563              logs: parsedCaptions.logs,
42564              captions: [],
42565              captionStreams: [],
42566            };
42567          }
42568          return null;
42569        }
42570        this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
42571
42572        this.flushStream();
42573        return parsedCaptions;
42574      };
42575      /**
42576       * Pushes SEI NALUs onto CaptionStream
42577       * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
42578       * Assumes that `parseCaptionNals` has been called first
42579       * @see m2ts/caption-stream.js
42580       **/
42581
42582      this.pushNals = function (nals) {
42583        if (!this.isInitialized() || !nals || nals.length === 0) {
42584          return null;
42585        }
42586        nals.forEach(function (nal) {
42587          captionStream.push(nal);
42588        });
42589      };
42590      /**
42591       * Flushes underlying CaptionStream to dispatch processed, displayable captions
42592       * @see m2ts/caption-stream.js
42593       **/
42594
42595      this.flushStream = function () {
42596        if (!this.isInitialized()) {
42597          return null;
42598        }
42599        if (!parsingPartial) {
42600          captionStream.flush();
42601        } else {
42602          captionStream.partialFlush();
42603        }
42604      };
42605      /**
42606       * Reset caption buckets for new data
42607       **/
42608
42609      this.clearParsedCaptions = function () {
42610        parsedCaptions.captions = [];
42611        parsedCaptions.captionStreams = {};
42612        parsedCaptions.logs = [];
42613      };
42614      /**
42615       * Resets underlying CaptionStream
42616       * @see m2ts/caption-stream.js
42617       **/
42618
42619      this.resetCaptionStream = function () {
42620        if (!this.isInitialized()) {
42621          return null;
42622        }
42623        captionStream.reset();
42624      };
42625      /**
42626       * Convenience method to clear all captions flushed from the
42627       * CaptionStream and still being parsed
42628       * @see m2ts/caption-stream.js
42629       **/
42630
42631      this.clearAllCaptions = function () {
42632        this.clearParsedCaptions();
42633        this.resetCaptionStream();
42634      };
42635      /**
42636       * Reset caption parser
42637       **/
42638
42639      this.reset = function () {
42640        segmentCache = [];
42641        trackId = null;
42642        timescale = null;
42643        if (!parsedCaptions) {
42644          parsedCaptions = {
42645            captions: [],
42646            // CC1, CC2, CC3, CC4
42647            captionStreams: {},
42648            logs: [],
42649          };
42650        } else {
42651          this.clearParsedCaptions();
42652        }
42653        this.resetCaptionStream();
42654      };
42655      this.reset();
42656    };
42657    var captionParser = CaptionParser;
42658    const { parseTfdt } = mp4Inspector;
42659    const findBox = findBox_1;
42660    const { getTimescaleFromMediaHeader } = probe$2;
42661    const { parseSamples, getMdatTrafPairs } = samples;
42662    /**
42663     * Module for parsing WebVTT text and styles from FMP4 segments.
42664     * Based on the ISO/IEC 14496-30.
42665     */
42666
42667    const WebVttParser = function () {
42668      // default timescale to 90k
42669      let timescale = 90e3;
42670      /**
42671       * Parses the timescale from the init segment.
42672       * @param {Array<Uint8Array>} segment The initialization segment to parse the timescale from.
42673       */
42674
42675      this.init = function (segment) {
42676        // We just need the timescale from the init segment.
42677        const mdhd = findBox(segment, ['moov', 'trak', 'mdia', 'mdhd'])[0];
42678        if (mdhd) {
42679          timescale = getTimescaleFromMediaHeader(mdhd);
42680        }
42681      };
42682      /**
42683       * Parses a WebVTT FMP4 segment.
42684       * @param {Array<Uint8Array>} segment The content segment to parse the WebVTT cues from.
42685       * @returns The WebVTT cue text, styling, and timing info as an array of cue objects.
42686       */
42687
42688      this.parseSegment = function (segment) {
42689        const vttCues = [];
42690        const mdatTrafPairs = getMdatTrafPairs(segment);
42691        let baseMediaDecodeTime = 0;
42692        mdatTrafPairs.forEach(function (pair) {
42693          const mdatBox = pair.mdat;
42694          const trafBox = pair.traf; // zero or one.
42695
42696          const tfdtBox = findBox(trafBox, ['tfdt'])[0]; // zero or one.
42697
42698          const tfhdBox = findBox(trafBox, ['tfhd'])[0]; // zero or more.
42699
42700          const trunBoxes = findBox(trafBox, ['trun']);
42701          if (tfdtBox) {
42702            const tfdt = parseTfdt(tfdtBox);
42703            baseMediaDecodeTime = tfdt.baseMediaDecodeTime;
42704          }
42705          if (trunBoxes.length && tfhdBox) {
42706            const samples = parseSamples(trunBoxes, baseMediaDecodeTime, tfhdBox);
42707            let mdatOffset = 0;
42708            samples.forEach(function (sample) {
42709              // decode utf8 payload
42710              const UTF_8 = 'utf-8';
42711              const textDecoder = new TextDecoder(UTF_8); // extract sample data from the mdat box.
42712              // WebVTT Sample format:
42713              // Exactly one VTTEmptyCueBox box
42714              // OR one or more VTTCueBox boxes.
42715
42716              const sampleData = mdatBox.slice(mdatOffset, mdatOffset + sample.size); // single vtte box.
42717
42718              const vtteBox = findBox(sampleData, ['vtte'])[0]; // empty box
42719
42720              if (vtteBox) {
42721                mdatOffset += sample.size;
42722                return;
42723              } // TODO: Support 'vtta' boxes.
42724              // VTTAdditionalTextBoxes can be interleaved between VTTCueBoxes.
42725
42726              const vttcBoxes = findBox(sampleData, ['vttc']);
42727              vttcBoxes.forEach(function (vttcBox) {
42728                // mandatory payload box.
42729                const paylBox = findBox(vttcBox, ['payl'])[0]; // optional settings box
42730
42731                const sttgBox = findBox(vttcBox, ['sttg'])[0];
42732                const start = sample.pts / timescale;
42733                const end = (sample.pts + sample.duration) / timescale;
42734                let cueText, settings; // contains cue text.
42735
42736                if (paylBox) {
42737                  try {
42738                    cueText = textDecoder.decode(paylBox);
42739                  } catch (e) {
42740                    console.error(e);
42741                  }
42742                } // settings box contains styling.
42743
42744                if (sttgBox) {
42745                  try {
42746                    settings = textDecoder.decode(sttgBox);
42747                  } catch (e) {
42748                    console.error(e);
42749                  }
42750                }
42751                if (sample.duration && cueText) {
42752                  vttCues.push({
42753                    cueText,
42754                    start,
42755                    end,
42756                    settings,
42757                  });
42758                }
42759              });
42760              mdatOffset += sample.size;
42761            });
42762          }
42763        });
42764        return vttCues;
42765      };
42766    };
42767    var webvttParser = WebVttParser;
42768    /**
42769     * mux.js
42770     *
42771     * Copyright (c) Brightcove
42772     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
42773     *
42774     * Utilities to detect basic properties and metadata about TS Segments.
42775     */
42776
42777    var StreamTypes$1 = streamTypes;
42778    var parsePid = function (packet) {
42779      var pid = packet[1] & 0x1f;
42780      pid <<= 8;
42781      pid |= packet[2];
42782      return pid;
42783    };
42784    var parsePayloadUnitStartIndicator = function (packet) {
42785      return !!(packet[1] & 0x40);
42786    };
42787    var parseAdaptionField = function (packet) {
42788      var offset = 0; // if an adaption field is present, its length is specified by the
42789      // fifth byte of the TS packet header. The adaptation field is
42790      // used to add stuffing to PES packets that don't fill a complete
42791      // TS packet, and to specify some forms of timing and control data
42792      // that we do not currently use.
42793
42794      if ((packet[3] & 0x30) >>> 4 > 0x01) {
42795        offset += packet[4] + 1;
42796      }
42797      return offset;
42798    };
42799    var parseType = function (packet, pmtPid) {
42800      var pid = parsePid(packet);
42801      if (pid === 0) {
42802        return 'pat';
42803      } else if (pid === pmtPid) {
42804        return 'pmt';
42805      } else if (pmtPid) {
42806        return 'pes';
42807      }
42808      return null;
42809    };
42810    var parsePat = function (packet) {
42811      var pusi = parsePayloadUnitStartIndicator(packet);
42812      var offset = 4 + parseAdaptionField(packet);
42813      if (pusi) {
42814        offset += packet[offset] + 1;
42815      }
42816      return ((packet[offset + 10] & 0x1f) << 8) | packet[offset + 11];
42817    };
42818    var parsePmt = function (packet) {
42819      var programMapTable = {};
42820      var pusi = parsePayloadUnitStartIndicator(packet);
42821      var payloadOffset = 4 + parseAdaptionField(packet);
42822      if (pusi) {
42823        payloadOffset += packet[payloadOffset] + 1;
42824      } // PMTs can be sent ahead of the time when they should actually
42825      // take effect. We don't believe this should ever be the case
42826      // for HLS but we'll ignore "forward" PMT declarations if we see
42827      // them. Future PMT declarations have the current_next_indicator
42828      // set to zero.
42829
42830      if (!(packet[payloadOffset + 5] & 0x01)) {
42831        return;
42832      }
42833      var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
42834
42835      sectionLength = ((packet[payloadOffset + 1] & 0x0f) << 8) | packet[payloadOffset + 2];
42836      tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
42837      // long the program info descriptors are
42838
42839      programInfoLength = ((packet[payloadOffset + 10] & 0x0f) << 8) | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
42840
42841      var offset = 12 + programInfoLength;
42842      while (offset < tableEnd) {
42843        var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
42844
42845        programMapTable[((packet[i + 1] & 0x1f) << 8) | packet[i + 2]] = packet[i]; // move to the next table entry
42846        // skip past the elementary stream descriptors, if present
42847
42848        offset += (((packet[i + 3] & 0x0f) << 8) | packet[i + 4]) + 5;
42849      }
42850      return programMapTable;
42851    };
42852    var parsePesType = function (packet, programMapTable) {
42853      var pid = parsePid(packet);
42854      var type = programMapTable[pid];
42855      switch (type) {
42856        case StreamTypes$1.H264_STREAM_TYPE:
42857          return 'video';
42858        case StreamTypes$1.ADTS_STREAM_TYPE:
42859          return 'audio';
42860        case StreamTypes$1.METADATA_STREAM_TYPE:
42861          return 'timed-metadata';
42862        default:
42863          return null;
42864      }
42865    };
42866    var parsePesTime = function (packet) {
42867      var pusi = parsePayloadUnitStartIndicator(packet);
42868      if (!pusi) {
42869        return null;
42870      }
42871      var offset = 4 + parseAdaptionField(packet);
42872      if (offset >= packet.byteLength) {
42873        // From the H 222.0 MPEG-TS spec
42874        // "For transport stream packets carrying PES packets, stuffing is needed when there
42875        //  is insufficient PES packet data to completely fill the transport stream packet
42876        //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
42877        //  the sum of the lengths of the data elements in it, so that the payload bytes
42878        //  remaining after the adaptation field exactly accommodates the available PES packet
42879        //  data."
42880        //
42881        // If the offset is >= the length of the packet, then the packet contains no data
42882        // and instead is just adaption field stuffing bytes
42883        return null;
42884      }
42885      var pes = null;
42886      var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
42887      // and a DTS value. Determine what combination of values is
42888      // available to work with.
42889
42890      ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
42891      // performs all bitwise operations on 32-bit integers but javascript
42892      // supports a much greater range (52-bits) of integer using standard
42893      // mathematical operations.
42894      // We construct a 31-bit value using bitwise operators over the 31
42895      // most significant bits and then multiply by 4 (equal to a left-shift
42896      // of 2) before we add the final 2 least significant bits of the
42897      // timestamp (equal to an OR.)
42898
42899      if (ptsDtsFlags & 0xc0) {
42900        pes = {}; // the PTS and DTS are not written out directly. For information
42901        // on how they are encoded, see
42902        // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
42903
42904        pes.pts =
42905          ((packet[offset + 9] & 0x0e) << 27) |
42906          ((packet[offset + 10] & 0xff) << 20) |
42907          ((packet[offset + 11] & 0xfe) << 12) |
42908          ((packet[offset + 12] & 0xff) << 5) |
42909          ((packet[offset + 13] & 0xfe) >>> 3);
42910        pes.pts *= 4; // Left shift by 2
42911
42912        pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
42913
42914        pes.dts = pes.pts;
42915        if (ptsDtsFlags & 0x40) {
42916          pes.dts =
42917            ((packet[offset + 14] & 0x0e) << 27) |
42918            ((packet[offset + 15] & 0xff) << 20) |
42919            ((packet[offset + 16] & 0xfe) << 12) |
42920            ((packet[offset + 17] & 0xff) << 5) |
42921            ((packet[offset + 18] & 0xfe) >>> 3);
42922          pes.dts *= 4; // Left shift by 2
42923
42924          pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
42925        }
42926      }
42927      return pes;
42928    };
42929    var parseNalUnitType = function (type) {
42930      switch (type) {
42931        case 0x05:
42932          return 'slice_layer_without_partitioning_rbsp_idr';
42933        case 0x06:
42934          return 'sei_rbsp';
42935        case 0x07:
42936          return 'seq_parameter_set_rbsp';
42937        case 0x08:
42938          return 'pic_parameter_set_rbsp';
42939        case 0x09:
42940          return 'access_unit_delimiter_rbsp';
42941        default:
42942          return null;
42943      }
42944    };
42945    var videoPacketContainsKeyFrame = function (packet) {
42946      var offset = 4 + parseAdaptionField(packet);
42947      var frameBuffer = packet.subarray(offset);
42948      var frameI = 0;
42949      var frameSyncPoint = 0;
42950      var foundKeyFrame = false;
42951      var nalType; // advance the sync point to a NAL start, if necessary
42952
42953      for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
42954        if (frameBuffer[frameSyncPoint + 2] === 1) {
42955          // the sync point is properly aligned
42956          frameI = frameSyncPoint + 5;
42957          break;
42958        }
42959      }
42960      while (frameI < frameBuffer.byteLength) {
42961        // look at the current byte to determine if we've hit the end of
42962        // a NAL unit boundary
42963        switch (frameBuffer[frameI]) {
42964          case 0:
42965            // skip past non-sync sequences
42966            if (frameBuffer[frameI - 1] !== 0) {
42967              frameI += 2;
42968              break;
42969            } else if (frameBuffer[frameI - 2] !== 0) {
42970              frameI++;
42971              break;
42972            }
42973            if (frameSyncPoint + 3 !== frameI - 2) {
42974              nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
42975              if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
42976                foundKeyFrame = true;
42977              }
42978            } // drop trailing zeroes
42979
42980            do {
42981              frameI++;
42982            } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
42983            frameSyncPoint = frameI - 2;
42984            frameI += 3;
42985            break;
42986          case 1:
42987            // skip past non-sync sequences
42988            if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
42989              frameI += 3;
42990              break;
42991            }
42992            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
42993            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
42994              foundKeyFrame = true;
42995            }
42996            frameSyncPoint = frameI - 2;
42997            frameI += 3;
42998            break;
42999          default:
43000            // the current byte isn't a one or zero, so it cannot be part
43001            // of a sync sequence
43002            frameI += 3;
43003            break;
43004        }
43005      }
43006      frameBuffer = frameBuffer.subarray(frameSyncPoint);
43007      frameI -= frameSyncPoint;
43008      frameSyncPoint = 0; // parse the final nal
43009
43010      if (frameBuffer && frameBuffer.byteLength > 3) {
43011        nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
43012        if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
43013          foundKeyFrame = true;
43014        }
43015      }
43016      return foundKeyFrame;
43017    };
43018    var probe$1 = {
43019      parseType: parseType,
43020      parsePat: parsePat,
43021      parsePmt: parsePmt,
43022      parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
43023      parsePesType: parsePesType,
43024      parsePesTime: parsePesTime,
43025      videoPacketContainsKeyFrame: videoPacketContainsKeyFrame,
43026    };
43027    /**
43028     * mux.js
43029     *
43030     * Copyright (c) Brightcove
43031     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43032     *
43033     * Parse mpeg2 transport stream packets to extract basic timing information
43034     */
43035
43036    var StreamTypes = streamTypes;
43037    var handleRollover = timestampRolloverStream.handleRollover;
43038    var probe = {};
43039    probe.ts = probe$1;
43040    probe.aac = utils;
43041    var ONE_SECOND_IN_TS = clock$2.ONE_SECOND_IN_TS;
43042    var MP2T_PACKET_LENGTH = 188,
43043      // bytes
43044      SYNC_BYTE = 0x47;
43045    /**
43046     * walks through segment data looking for pat and pmt packets to parse out
43047     * program map table information
43048     */
43049
43050    var parsePsi_ = function (bytes, pmt) {
43051      var startIndex = 0,
43052        endIndex = MP2T_PACKET_LENGTH,
43053        packet,
43054        type;
43055      while (endIndex < bytes.byteLength) {
43056        // Look for a pair of start and end sync bytes in the data..
43057        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
43058          // We found a packet
43059          packet = bytes.subarray(startIndex, endIndex);
43060          type = probe.ts.parseType(packet, pmt.pid);
43061          switch (type) {
43062            case 'pat':
43063              pmt.pid = probe.ts.parsePat(packet);
43064              break;
43065            case 'pmt':
43066              var table = probe.ts.parsePmt(packet);
43067              pmt.table = pmt.table || {};
43068              Object.keys(table).forEach(function (key) {
43069                pmt.table[key] = table[key];
43070              });
43071              break;
43072          }
43073          startIndex += MP2T_PACKET_LENGTH;
43074          endIndex += MP2T_PACKET_LENGTH;
43075          continue;
43076        } // If we get here, we have somehow become de-synchronized and we need to step
43077        // forward one byte at a time until we find a pair of sync bytes that denote
43078        // a packet
43079
43080        startIndex++;
43081        endIndex++;
43082      }
43083    };
43084    /**
43085     * walks through the segment data from the start and end to get timing information
43086     * for the first and last audio pes packets
43087     */
43088
43089    var parseAudioPes_ = function (bytes, pmt, result) {
43090      var startIndex = 0,
43091        endIndex = MP2T_PACKET_LENGTH,
43092        packet,
43093        type,
43094        pesType,
43095        pusi,
43096        parsed;
43097      var endLoop = false; // Start walking from start of segment to get first audio packet
43098
43099      while (endIndex <= bytes.byteLength) {
43100        // Look for a pair of start and end sync bytes in the data..
43101        if (
43102          bytes[startIndex] === SYNC_BYTE &&
43103          (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)
43104        ) {
43105          // We found a packet
43106          packet = bytes.subarray(startIndex, endIndex);
43107          type = probe.ts.parseType(packet, pmt.pid);
43108          switch (type) {
43109            case 'pes':
43110              pesType = probe.ts.parsePesType(packet, pmt.table);
43111              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
43112              if (pesType === 'audio' && pusi) {
43113                parsed = probe.ts.parsePesTime(packet);
43114                if (parsed) {
43115                  parsed.type = 'audio';
43116                  result.audio.push(parsed);
43117                  endLoop = true;
43118                }
43119              }
43120              break;
43121          }
43122          if (endLoop) {
43123            break;
43124          }
43125          startIndex += MP2T_PACKET_LENGTH;
43126          endIndex += MP2T_PACKET_LENGTH;
43127          continue;
43128        } // If we get here, we have somehow become de-synchronized and we need to step
43129        // forward one byte at a time until we find a pair of sync bytes that denote
43130        // a packet
43131
43132        startIndex++;
43133        endIndex++;
43134      } // Start walking from end of segment to get last audio packet
43135
43136      endIndex = bytes.byteLength;
43137      startIndex = endIndex - MP2T_PACKET_LENGTH;
43138      endLoop = false;
43139      while (startIndex >= 0) {
43140        // Look for a pair of start and end sync bytes in the data..
43141        if (
43142          bytes[startIndex] === SYNC_BYTE &&
43143          (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)
43144        ) {
43145          // We found a packet
43146          packet = bytes.subarray(startIndex, endIndex);
43147          type = probe.ts.parseType(packet, pmt.pid);
43148          switch (type) {
43149            case 'pes':
43150              pesType = probe.ts.parsePesType(packet, pmt.table);
43151              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
43152              if (pesType === 'audio' && pusi) {
43153                parsed = probe.ts.parsePesTime(packet);
43154                if (parsed) {
43155                  parsed.type = 'audio';
43156                  result.audio.push(parsed);
43157                  endLoop = true;
43158                }
43159              }
43160              break;
43161          }
43162          if (endLoop) {
43163            break;
43164          }
43165          startIndex -= MP2T_PACKET_LENGTH;
43166          endIndex -= MP2T_PACKET_LENGTH;
43167          continue;
43168        } // If we get here, we have somehow become de-synchronized and we need to step
43169        // forward one byte at a time until we find a pair of sync bytes that denote
43170        // a packet
43171
43172        startIndex--;
43173        endIndex--;
43174      }
43175    };
43176    /**
43177     * walks through the segment data from the start and end to get timing information
43178     * for the first and last video pes packets as well as timing information for the first
43179     * key frame.
43180     */
43181
43182    var parseVideoPes_ = function (bytes, pmt, result) {
43183      var startIndex = 0,
43184        endIndex = MP2T_PACKET_LENGTH,
43185        packet,
43186        type,
43187        pesType,
43188        pusi,
43189        parsed,
43190        frame,
43191        i,
43192        pes;
43193      var endLoop = false;
43194      var currentFrame = {
43195        data: [],
43196        size: 0,
43197      }; // Start walking from start of segment to get first video packet
43198
43199      while (endIndex < bytes.byteLength) {
43200        // Look for a pair of start and end sync bytes in the data..
43201        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
43202          // We found a packet
43203          packet = bytes.subarray(startIndex, endIndex);
43204          type = probe.ts.parseType(packet, pmt.pid);
43205          switch (type) {
43206            case 'pes':
43207              pesType = probe.ts.parsePesType(packet, pmt.table);
43208              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
43209              if (pesType === 'video') {
43210                if (pusi && !endLoop) {
43211                  parsed = probe.ts.parsePesTime(packet);
43212                  if (parsed) {
43213                    parsed.type = 'video';
43214                    result.video.push(parsed);
43215                    endLoop = true;
43216                  }
43217                }
43218                if (!result.firstKeyFrame) {
43219                  if (pusi) {
43220                    if (currentFrame.size !== 0) {
43221                      frame = new Uint8Array(currentFrame.size);
43222                      i = 0;
43223                      while (currentFrame.data.length) {
43224                        pes = currentFrame.data.shift();
43225                        frame.set(pes, i);
43226                        i += pes.byteLength;
43227                      }
43228                      if (probe.ts.videoPacketContainsKeyFrame(frame)) {
43229                        var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
43230                        // the keyframe seems to work fine with HLS playback
43231                        // and definitely preferable to a crash with TypeError...
43232
43233                        if (firstKeyFrame) {
43234                          result.firstKeyFrame = firstKeyFrame;
43235                          result.firstKeyFrame.type = 'video';
43236                        } else {
43237                          // eslint-disable-next-line
43238                          console.warn(
43239                            'Failed to extract PTS/DTS from PES at first keyframe. ' +
43240                              'This could be an unusual TS segment, or else mux.js did not ' +
43241                              'parse your TS segment correctly. If you know your TS ' +
43242                              'segments do contain PTS/DTS on keyframes please file a bug ' +
43243                              'report! You can try ffprobe to double check for yourself.'
43244                          );
43245                        }
43246                      }
43247                      currentFrame.size = 0;
43248                    }
43249                  }
43250                  currentFrame.data.push(packet);
43251                  currentFrame.size += packet.byteLength;
43252                }
43253              }
43254              break;
43255          }
43256          if (endLoop && result.firstKeyFrame) {
43257            break;
43258          }
43259          startIndex += MP2T_PACKET_LENGTH;
43260          endIndex += MP2T_PACKET_LENGTH;
43261          continue;
43262        } // If we get here, we have somehow become de-synchronized and we need to step
43263        // forward one byte at a time until we find a pair of sync bytes that denote
43264        // a packet
43265
43266        startIndex++;
43267        endIndex++;
43268      } // Start walking from end of segment to get last video packet
43269
43270      endIndex = bytes.byteLength;
43271      startIndex = endIndex - MP2T_PACKET_LENGTH;
43272      endLoop = false;
43273      while (startIndex >= 0) {
43274        // Look for a pair of start and end sync bytes in the data..
43275        if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
43276          // We found a packet
43277          packet = bytes.subarray(startIndex, endIndex);
43278          type = probe.ts.parseType(packet, pmt.pid);
43279          switch (type) {
43280            case 'pes':
43281              pesType = probe.ts.parsePesType(packet, pmt.table);
43282              pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
43283              if (pesType === 'video' && pusi) {
43284                parsed = probe.ts.parsePesTime(packet);
43285                if (parsed) {
43286                  parsed.type = 'video';
43287                  result.video.push(parsed);
43288                  endLoop = true;
43289                }
43290              }
43291              break;
43292          }
43293          if (endLoop) {
43294            break;
43295          }
43296          startIndex -= MP2T_PACKET_LENGTH;
43297          endIndex -= MP2T_PACKET_LENGTH;
43298          continue;
43299        } // If we get here, we have somehow become de-synchronized and we need to step
43300        // forward one byte at a time until we find a pair of sync bytes that denote
43301        // a packet
43302
43303        startIndex--;
43304        endIndex--;
43305      }
43306    };
43307    /**
43308     * Adjusts the timestamp information for the segment to account for
43309     * rollover and convert to seconds based on pes packet timescale (90khz clock)
43310     */
43311
43312    var adjustTimestamp_ = function (segmentInfo, baseTimestamp) {
43313      if (segmentInfo.audio && segmentInfo.audio.length) {
43314        var audioBaseTimestamp = baseTimestamp;
43315        if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
43316          audioBaseTimestamp = segmentInfo.audio[0].dts;
43317        }
43318        segmentInfo.audio.forEach(function (info) {
43319          info.dts = handleRollover(info.dts, audioBaseTimestamp);
43320          info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
43321
43322          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
43323          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
43324        });
43325      }
43326      if (segmentInfo.video && segmentInfo.video.length) {
43327        var videoBaseTimestamp = baseTimestamp;
43328        if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
43329          videoBaseTimestamp = segmentInfo.video[0].dts;
43330        }
43331        segmentInfo.video.forEach(function (info) {
43332          info.dts = handleRollover(info.dts, videoBaseTimestamp);
43333          info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
43334
43335          info.dtsTime = info.dts / ONE_SECOND_IN_TS;
43336          info.ptsTime = info.pts / ONE_SECOND_IN_TS;
43337        });
43338        if (segmentInfo.firstKeyFrame) {
43339          var frame = segmentInfo.firstKeyFrame;
43340          frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
43341          frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
43342
43343          frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
43344          frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
43345        }
43346      }
43347    };
43348    /**
43349     * inspects the aac data stream for start and end time information
43350     */
43351
43352    var inspectAac_ = function (bytes) {
43353      var endLoop = false,
43354        audioCount = 0,
43355        sampleRate = null,
43356        timestamp = null,
43357        frameSize = 0,
43358        byteIndex = 0,
43359        packet;
43360      while (bytes.length - byteIndex >= 3) {
43361        var type = probe.aac.parseType(bytes, byteIndex);
43362        switch (type) {
43363          case 'timed-metadata':
43364            // Exit early because we don't have enough to parse
43365            // the ID3 tag header
43366            if (bytes.length - byteIndex < 10) {
43367              endLoop = true;
43368              break;
43369            }
43370            frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
43371            // to emit a full packet
43372
43373            if (frameSize > bytes.length) {
43374              endLoop = true;
43375              break;
43376            }
43377            if (timestamp === null) {
43378              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
43379              timestamp = probe.aac.parseAacTimestamp(packet);
43380            }
43381            byteIndex += frameSize;
43382            break;
43383          case 'audio':
43384            // Exit early because we don't have enough to parse
43385            // the ADTS frame header
43386            if (bytes.length - byteIndex < 7) {
43387              endLoop = true;
43388              break;
43389            }
43390            frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
43391            // to emit a full packet
43392
43393            if (frameSize > bytes.length) {
43394              endLoop = true;
43395              break;
43396            }
43397            if (sampleRate === null) {
43398              packet = bytes.subarray(byteIndex, byteIndex + frameSize);
43399              sampleRate = probe.aac.parseSampleRate(packet);
43400            }
43401            audioCount++;
43402            byteIndex += frameSize;
43403            break;
43404          default:
43405            byteIndex++;
43406            break;
43407        }
43408        if (endLoop) {
43409          return null;
43410        }
43411      }
43412      if (sampleRate === null || timestamp === null) {
43413        return null;
43414      }
43415      var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
43416      var result = {
43417        audio: [
43418          {
43419            type: 'audio',
43420            dts: timestamp,
43421            pts: timestamp,
43422          },
43423          {
43424            type: 'audio',
43425            dts: timestamp + audioCount * 1024 * audioTimescale,
43426            pts: timestamp + audioCount * 1024 * audioTimescale,
43427          },
43428        ],
43429      };
43430      return result;
43431    };
43432    /**
43433     * inspects the transport stream segment data for start and end time information
43434     * of the audio and video tracks (when present) as well as the first key frame's
43435     * start time.
43436     */
43437
43438    var inspectTs_ = function (bytes) {
43439      var pmt = {
43440        pid: null,
43441        table: null,
43442      };
43443      var result = {};
43444      parsePsi_(bytes, pmt);
43445      for (var pid in pmt.table) {
43446        if (pmt.table.hasOwnProperty(pid)) {
43447          var type = pmt.table[pid];
43448          switch (type) {
43449            case StreamTypes.H264_STREAM_TYPE:
43450              result.video = [];
43451              parseVideoPes_(bytes, pmt, result);
43452              if (result.video.length === 0) {
43453                delete result.video;
43454              }
43455              break;
43456            case StreamTypes.ADTS_STREAM_TYPE:
43457              result.audio = [];
43458              parseAudioPes_(bytes, pmt, result);
43459              if (result.audio.length === 0) {
43460                delete result.audio;
43461              }
43462              break;
43463          }
43464        }
43465      }
43466      return result;
43467    };
43468    /**
43469     * Inspects segment byte data and returns an object with start and end timing information
43470     *
43471     * @param {Uint8Array} bytes The segment byte data
43472     * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
43473     *  timestamps for rollover. This value must be in 90khz clock.
43474     * @return {Object} Object containing start and end frame timing info of segment.
43475     */
43476
43477    var inspect = function (bytes, baseTimestamp) {
43478      var isAacData = probe.aac.isLikelyAacData(bytes);
43479      var result;
43480      if (isAacData) {
43481        result = inspectAac_(bytes);
43482      } else {
43483        result = inspectTs_(bytes);
43484      }
43485      if (!result || (!result.audio && !result.video)) {
43486        return null;
43487      }
43488      adjustTimestamp_(result, baseTimestamp);
43489      return result;
43490    };
43491    var tsInspector = {
43492      inspect: inspect,
43493      parseAudioPes_: parseAudioPes_,
43494    };
43495    /* global self */
43496
43497    /**
43498     * Re-emits transmuxer events by converting them into messages to the
43499     * world outside the worker.
43500     *
43501     * @param {Object} transmuxer the transmuxer to wire events on
43502     * @private
43503     */
43504
43505    const wireTransmuxerEvents = function (self, transmuxer) {
43506      transmuxer.on('data', function (segment) {
43507        // transfer ownership of the underlying ArrayBuffer
43508        // instead of doing a copy to save memory
43509        // ArrayBuffers are transferable but generic TypedArrays are not
43510        // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
43511        const initArray = segment.initSegment;
43512        segment.initSegment = {
43513          data: initArray.buffer,
43514          byteOffset: initArray.byteOffset,
43515          byteLength: initArray.byteLength,
43516        };
43517        const typedArray = segment.data;
43518        segment.data = typedArray.buffer;
43519        self.postMessage(
43520          {
43521            action: 'data',
43522            segment,
43523            byteOffset: typedArray.byteOffset,
43524            byteLength: typedArray.byteLength,
43525          },
43526          [segment.data]
43527        );
43528      });
43529      transmuxer.on('done', function (data) {
43530        self.postMessage({
43531          action: 'done',
43532        });
43533      });
43534      transmuxer.on('gopInfo', function (gopInfo) {
43535        self.postMessage({
43536          action: 'gopInfo',
43537          gopInfo,
43538        });
43539      });
43540      transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
43541        const videoSegmentTimingInfo = {
43542          start: {
43543            decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
43544            presentation: clock$2.videoTsToSeconds(timingInfo.start.pts),
43545          },
43546          end: {
43547            decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
43548            presentation: clock$2.videoTsToSeconds(timingInfo.end.pts),
43549          },
43550          baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime),
43551        };
43552        if (timingInfo.prependedContentDuration) {
43553          videoSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(
43554            timingInfo.prependedContentDuration
43555          );
43556        }
43557        self.postMessage({
43558          action: 'videoSegmentTimingInfo',
43559          videoSegmentTimingInfo,
43560        });
43561      });
43562      transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
43563        // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
43564        const audioSegmentTimingInfo = {
43565          start: {
43566            decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
43567            presentation: clock$2.videoTsToSeconds(timingInfo.start.pts),
43568          },
43569          end: {
43570            decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
43571            presentation: clock$2.videoTsToSeconds(timingInfo.end.pts),
43572          },
43573          baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime),
43574        };
43575        if (timingInfo.prependedContentDuration) {
43576          audioSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(
43577            timingInfo.prependedContentDuration
43578          );
43579        }
43580        self.postMessage({
43581          action: 'audioSegmentTimingInfo',
43582          audioSegmentTimingInfo,
43583        });
43584      });
43585      transmuxer.on('id3Frame', function (id3Frame) {
43586        self.postMessage({
43587          action: 'id3Frame',
43588          id3Frame,
43589        });
43590      });
43591      transmuxer.on('caption', function (caption) {
43592        self.postMessage({
43593          action: 'caption',
43594          caption,
43595        });
43596      });
43597      transmuxer.on('trackinfo', function (trackInfo) {
43598        self.postMessage({
43599          action: 'trackinfo',
43600          trackInfo,
43601        });
43602      });
43603      transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
43604        // convert to video TS since we prioritize video time over audio
43605        self.postMessage({
43606          action: 'audioTimingInfo',
43607          audioTimingInfo: {
43608            start: clock$2.videoTsToSeconds(audioTimingInfo.start),
43609            end: clock$2.videoTsToSeconds(audioTimingInfo.end),
43610          },
43611        });
43612      });
43613      transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
43614        self.postMessage({
43615          action: 'videoTimingInfo',
43616          videoTimingInfo: {
43617            start: clock$2.videoTsToSeconds(videoTimingInfo.start),
43618            end: clock$2.videoTsToSeconds(videoTimingInfo.end),
43619          },
43620        });
43621      });
43622      transmuxer.on('log', function (log) {
43623        self.postMessage({
43624          action: 'log',
43625          log,
43626        });
43627      });
43628    };
43629    /**
43630     * All incoming messages route through this hash. If no function exists
43631     * to handle an incoming message, then we ignore the message.
43632     *
43633     * @class MessageHandlers
43634     * @param {Object} options the options to initialize with
43635     */
43636
43637    class MessageHandlers {
43638      constructor(self, options) {
43639        this.options = options || {};
43640        this.self = self;
43641        this.init();
43642      }
43643      /**
43644       * initialize our web worker and wire all the events.
43645       */
43646
43647      init() {
43648        if (this.transmuxer) {
43649          this.transmuxer.dispose();
43650        }
43651        this.transmuxer = new transmuxer.Transmuxer(this.options);
43652        wireTransmuxerEvents(this.self, this.transmuxer);
43653      }
43654      pushMp4Captions(data) {
43655        if (!this.captionParser) {
43656          this.captionParser = new captionParser();
43657          this.captionParser.init();
43658        }
43659        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
43660        const parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
43661        this.self.postMessage(
43662          {
43663            action: 'mp4Captions',
43664            captions: (parsed && parsed.captions) || [],
43665            logs: (parsed && parsed.logs) || [],
43666            data: segment.buffer,
43667          },
43668          [segment.buffer]
43669        );
43670      }
43671      /**
43672       * Initializes the WebVttParser and passes the init segment.
43673       *
43674       * @param {Uint8Array} data mp4 boxed WebVTT init segment data
43675       */
43676
43677      initMp4WebVttParser(data) {
43678        if (!this.webVttParser) {
43679          this.webVttParser = new webvttParser();
43680        }
43681        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); // Set the timescale for the parser.
43682        // This can be called repeatedly in order to set and re-set the timescale.
43683
43684        this.webVttParser.init(segment);
43685      }
43686      /**
43687       * Parse an mp4 encapsulated WebVTT segment and return an array of cues.
43688       *
43689       * @param {Uint8Array} data a text/webvtt segment
43690       * @return {Object[]} an array of parsed cue objects
43691       */
43692
43693      getMp4WebVttText(data) {
43694        if (!this.webVttParser) {
43695          // timescale might not be set yet if the parser is created before an init segment is passed.
43696          // default timescale is 90k.
43697          this.webVttParser = new webvttParser();
43698        }
43699        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
43700        const parsed = this.webVttParser.parseSegment(segment);
43701        this.self.postMessage(
43702          {
43703            action: 'getMp4WebVttText',
43704            mp4VttCues: parsed || [],
43705            data: segment.buffer,
43706          },
43707          [segment.buffer]
43708        );
43709      }
43710      probeMp4StartTime({ timescales, data }) {
43711        const startTime = probe$2.startTime(timescales, data);
43712        this.self.postMessage(
43713          {
43714            action: 'probeMp4StartTime',
43715            startTime,
43716            data,
43717          },
43718          [data.buffer]
43719        );
43720      }
43721      probeMp4Tracks({ data }) {
43722        const tracks = probe$2.tracks(data);
43723        this.self.postMessage(
43724          {
43725            action: 'probeMp4Tracks',
43726            tracks,
43727            data,
43728          },
43729          [data.buffer]
43730        );
43731      }
43732      /**
43733       * Probes an mp4 segment for EMSG boxes containing ID3 data.
43734       * https://aomediacodec.github.io/id3-emsg/
43735       *
43736       * @param {Uint8Array} data segment data
43737       * @param {number} offset segment start time
43738       * @return {Object[]} an array of ID3 frames
43739       */
43740
43741      probeEmsgID3({ data, offset }) {
43742        const id3Frames = probe$2.getEmsgID3(data, offset);
43743        this.self.postMessage(
43744          {
43745            action: 'probeEmsgID3',
43746            id3Frames,
43747            emsgData: data,
43748          },
43749          [data.buffer]
43750        );
43751      }
43752      /**
43753       * Probe an mpeg2-ts segment to determine the start time of the segment in it's
43754       * internal "media time," as well as whether it contains video and/or audio.
43755       *
43756       * @private
43757       * @param {Uint8Array} bytes - segment bytes
43758       * @param {number} baseStartTime
43759       *        Relative reference timestamp used when adjusting frame timestamps for rollover.
43760       *        This value should be in seconds, as it's converted to a 90khz clock within the
43761       *        function body.
43762       * @return {Object} The start time of the current segment in "media time" as well as
43763       *                  whether it contains video and/or audio
43764       */
43765
43766      probeTs({ data, baseStartTime }) {
43767        const tsStartTime =
43768          typeof baseStartTime === 'number' && !isNaN(baseStartTime)
43769            ? baseStartTime * clock$2.ONE_SECOND_IN_TS
43770            : void 0;
43771        const timeInfo = tsInspector.inspect(data, tsStartTime);
43772        let result = null;
43773        if (timeInfo) {
43774          result = {
43775            // each type's time info comes back as an array of 2 times, start and end
43776            hasVideo: (timeInfo.video && timeInfo.video.length === 2) || false,
43777            hasAudio: (timeInfo.audio && timeInfo.audio.length === 2) || false,
43778          };
43779          if (result.hasVideo) {
43780            result.videoStart = timeInfo.video[0].ptsTime;
43781          }
43782          if (result.hasAudio) {
43783            result.audioStart = timeInfo.audio[0].ptsTime;
43784          }
43785        }
43786        this.self.postMessage(
43787          {
43788            action: 'probeTs',
43789            result,
43790            data,
43791          },
43792          [data.buffer]
43793        );
43794      }
43795      clearAllMp4Captions() {
43796        if (this.captionParser) {
43797          this.captionParser.clearAllCaptions();
43798        }
43799      }
43800      clearParsedMp4Captions() {
43801        if (this.captionParser) {
43802          this.captionParser.clearParsedCaptions();
43803        }
43804      }
43805      /**
43806       * Adds data (a ts segment) to the start of the transmuxer pipeline for
43807       * processing.
43808       *
43809       * @param {ArrayBuffer} data data to push into the muxer
43810       */
43811
43812      push(data) {
43813        // Cast array buffer to correct type for transmuxer
43814        const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
43815        this.transmuxer.push(segment);
43816      }
43817      /**
43818       * Recreate the transmuxer so that the next segment added via `push`
43819       * start with a fresh transmuxer.
43820       */
43821
43822      reset() {
43823        this.transmuxer.reset();
43824      }
43825      /**
43826       * Set the value that will be used as the `baseMediaDecodeTime` time for the
43827       * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
43828       * set relative to the first based on the PTS values.
43829       *
43830       * @param {Object} data used to set the timestamp offset in the muxer
43831       */
43832
43833      setTimestampOffset(data) {
43834        const timestampOffset = data.timestampOffset || 0;
43835        this.transmuxer.setBaseMediaDecodeTime(
43836          Math.round(clock$2.secondsToVideoTs(timestampOffset))
43837        );
43838      }
43839      setAudioAppendStart(data) {
43840        this.transmuxer.setAudioAppendStart(Math.ceil(clock$2.secondsToVideoTs(data.appendStart)));
43841      }
43842      setRemux(data) {
43843        this.transmuxer.setRemux(data.remux);
43844      }
43845      /**
43846       * Forces the pipeline to finish processing the last segment and emit it's
43847       * results.
43848       *
43849       * @param {Object} data event data, not really used
43850       */
43851
43852      flush(data) {
43853        this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
43854
43855        self.postMessage({
43856          action: 'done',
43857          type: 'transmuxed',
43858        });
43859      }
43860      endTimeline() {
43861        this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
43862        // timelines
43863
43864        self.postMessage({
43865          action: 'endedtimeline',
43866          type: 'transmuxed',
43867        });
43868      }
43869      alignGopsWith(data) {
43870        this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
43871      }
43872    }
43873    /**
43874     * Our web worker interface so that things can talk to mux.js
43875     * that will be running in a web worker. the scope is passed to this by
43876     * webworkify.
43877     *
43878     * @param {Object} self the scope for the web worker
43879     */
43880
43881    self.onmessage = function (event) {
43882      if (event.data.action === 'init' && event.data.options) {
43883        this.messageHandlers = new MessageHandlers(self, event.data.options);
43884        return;
43885      }
43886      if (!this.messageHandlers) {
43887        this.messageHandlers = new MessageHandlers(self);
43888      }
43889      if (event.data && event.data.action && event.data.action !== 'init') {
43890        if (this.messageHandlers[event.data.action]) {
43891          this.messageHandlers[event.data.action](event.data);
43892        }
43893      }
43894    };
43895  })
43896);
43897var TransmuxWorker = factory(workerCode$1);
43898/* rollup-plugin-worker-factory end for worker!/home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
43899
43900const handleData_ = (event, transmuxedData, callback) => {
43901  const {
43902    type,
43903    initSegment,
43904    captions,
43905    captionStreams,
43906    metadata,
43907    videoFrameDtsTime,
43908    videoFramePtsTime,
43909  } = event.data.segment;
43910  transmuxedData.buffer.push({
43911    captions,
43912    captionStreams,
43913    metadata,
43914  });
43915  const boxes = event.data.segment.boxes || {
43916    data: event.data.segment.data,
43917  };
43918  const result = {
43919    type,
43920    // cast ArrayBuffer to TypedArray
43921    data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
43922    initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength),
43923  };
43924  if (typeof videoFrameDtsTime !== 'undefined') {
43925    result.videoFrameDtsTime = videoFrameDtsTime;
43926  }
43927  if (typeof videoFramePtsTime !== 'undefined') {
43928    result.videoFramePtsTime = videoFramePtsTime;
43929  }
43930  callback(result);
43931};
43932const handleDone_ = ({ transmuxedData, callback }) => {
43933  // Previously we only returned data on data events,
43934  // not on done events. Clear out the buffer to keep that consistent.
43935  transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
43936  // have received
43937
43938  callback(transmuxedData);
43939};
43940const handleGopInfo_ = (event, transmuxedData) => {
43941  transmuxedData.gopInfo = event.data.gopInfo;
43942};
43943const processTransmux = (options) => {
43944  const {
43945    transmuxer,
43946    bytes,
43947    audioAppendStart,
43948    gopsToAlignWith,
43949    remux,
43950    onData,
43951    onTrackInfo,
43952    onAudioTimingInfo,
43953    onVideoTimingInfo,
43954    onVideoSegmentTimingInfo,
43955    onAudioSegmentTimingInfo,
43956    onId3,
43957    onCaptions,
43958    onDone,
43959    onEndedTimeline,
43960    onTransmuxerLog,
43961    isEndOfTimeline,
43962    segment,
43963    triggerSegmentEventFn,
43964  } = options;
43965  const transmuxedData = {
43966    buffer: [],
43967  };
43968  let waitForEndedTimelineEvent = isEndOfTimeline;
43969  const handleMessage = (event) => {
43970    if (transmuxer.currentTransmux !== options) {
43971      // disposed
43972      return;
43973    }
43974    if (event.data.action === 'data') {
43975      handleData_(event, transmuxedData, onData);
43976    }
43977    if (event.data.action === 'trackinfo') {
43978      onTrackInfo(event.data.trackInfo);
43979    }
43980    if (event.data.action === 'gopInfo') {
43981      handleGopInfo_(event, transmuxedData);
43982    }
43983    if (event.data.action === 'audioTimingInfo') {
43984      onAudioTimingInfo(event.data.audioTimingInfo);
43985    }
43986    if (event.data.action === 'videoTimingInfo') {
43987      onVideoTimingInfo(event.data.videoTimingInfo);
43988    }
43989    if (event.data.action === 'videoSegmentTimingInfo') {
43990      onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
43991    }
43992    if (event.data.action === 'audioSegmentTimingInfo') {
43993      onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
43994    }
43995    if (event.data.action === 'id3Frame') {
43996      onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
43997    }
43998    if (event.data.action === 'caption') {
43999      onCaptions(event.data.caption);
44000    }
44001    if (event.data.action === 'endedtimeline') {
44002      waitForEndedTimelineEvent = false;
44003      onEndedTimeline();
44004    }
44005    if (event.data.action === 'log') {
44006      onTransmuxerLog(event.data.log);
44007    } // wait for the transmuxed event since we may have audio and video
44008
44009    if (event.data.type !== 'transmuxed') {
44010      return;
44011    } // If the "endedtimeline" event has not yet fired, and this segment represents the end
44012    // of a timeline, that means there may still be data events before the segment
44013    // processing can be considerred complete. In that case, the final event should be
44014    // an "endedtimeline" event with the type "transmuxed."
44015
44016    if (waitForEndedTimelineEvent) {
44017      return;
44018    }
44019    transmuxer.onmessage = null;
44020    handleDone_({
44021      transmuxedData,
44022      callback: onDone,
44023    });
44024    /* eslint-disable no-use-before-define */
44025
44026    dequeue(transmuxer);
44027    /* eslint-enable */
44028  };
44029  const handleError = () => {
44030    const error = {
44031      message: 'Received an error message from the transmuxer worker',
44032      metadata: {
44033        errorType: videojs.Error.StreamingFailedToTransmuxSegment,
44034        segmentInfo: segmentInfoPayload({
44035          segment,
44036        }),
44037      },
44038    };
44039    onDone(null, error);
44040  };
44041  transmuxer.onmessage = handleMessage;
44042  transmuxer.onerror = handleError;
44043  if (audioAppendStart) {
44044    transmuxer.postMessage({
44045      action: 'setAudioAppendStart',
44046      appendStart: audioAppendStart,
44047    });
44048  } // allow empty arrays to be passed to clear out GOPs
44049
44050  if (Array.isArray(gopsToAlignWith)) {
44051    transmuxer.postMessage({
44052      action: 'alignGopsWith',
44053      gopsToAlignWith,
44054    });
44055  }
44056  if (typeof remux !== 'undefined') {
44057    transmuxer.postMessage({
44058      action: 'setRemux',
44059      remux,
44060    });
44061  }
44062  if (bytes.byteLength) {
44063    const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
44064    const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
44065    triggerSegmentEventFn({
44066      type: 'segmenttransmuxingstart',
44067      segment,
44068    });
44069    transmuxer.postMessage(
44070      {
44071        action: 'push',
44072        // Send the typed-array of data as an ArrayBuffer so that
44073        // it can be sent as a "Transferable" and avoid the costly
44074        // memory copy
44075        data: buffer,
44076        // To recreate the original typed-array, we need information
44077        // about what portion of the ArrayBuffer it was a view into
44078        byteOffset,
44079        byteLength: bytes.byteLength,
44080      },
44081      [buffer]
44082    );
44083  }
44084  if (isEndOfTimeline) {
44085    transmuxer.postMessage({
44086      action: 'endTimeline',
44087    });
44088  } // even if we didn't push any bytes, we have to make sure we flush in case we reached
44089  // the end of the segment
44090
44091  transmuxer.postMessage({
44092    action: 'flush',
44093  });
44094};
44095const dequeue = (transmuxer) => {
44096  transmuxer.currentTransmux = null;
44097  if (transmuxer.transmuxQueue.length) {
44098    transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
44099    if (typeof transmuxer.currentTransmux === 'function') {
44100      transmuxer.currentTransmux();
44101    } else {
44102      processTransmux(transmuxer.currentTransmux);
44103    }
44104  }
44105};
44106const processAction = (transmuxer, action) => {
44107  transmuxer.postMessage({
44108    action,
44109  });
44110  dequeue(transmuxer);
44111};
44112const enqueueAction = (action, transmuxer) => {
44113  if (!transmuxer.currentTransmux) {
44114    transmuxer.currentTransmux = action;
44115    processAction(transmuxer, action);
44116    return;
44117  }
44118  transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
44119};
44120const reset = (transmuxer) => {
44121  enqueueAction('reset', transmuxer);
44122};
44123const endTimeline = (transmuxer) => {
44124  enqueueAction('endTimeline', transmuxer);
44125};
44126const transmux = (options) => {
44127  if (!options.transmuxer.currentTransmux) {
44128    options.transmuxer.currentTransmux = options;
44129    processTransmux(options);
44130    return;
44131  }
44132  options.transmuxer.transmuxQueue.push(options);
44133};
44134const createTransmuxer = (options) => {
44135  const transmuxer = new TransmuxWorker();
44136  transmuxer.currentTransmux = null;
44137  transmuxer.transmuxQueue = [];
44138  const term = transmuxer.terminate;
44139  transmuxer.terminate = () => {
44140    transmuxer.currentTransmux = null;
44141    transmuxer.transmuxQueue.length = 0;
44142    return term.call(transmuxer);
44143  };
44144  transmuxer.postMessage({
44145    action: 'init',
44146    options,
44147  });
44148  return transmuxer;
44149};
44150var segmentTransmuxer = {
44151  reset,
44152  endTimeline,
44153  transmux,
44154  createTransmuxer,
44155};
44156const workerCallback = function (options) {
44157  const transmuxer = options.transmuxer;
44158  const endAction = options.endAction || options.action;
44159  const callback = options.callback;
44160  const message = _extends__default['default']({}, options, {
44161    endAction: null,
44162    transmuxer: null,
44163    callback: null,
44164  });
44165  const listenForEndEvent = (event) => {
44166    if (event.data.action !== endAction) {
44167      return;
44168    }
44169    transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
44170
44171    if (event.data.data) {
44172      event.data.data = new Uint8Array(
44173        event.data.data,
44174        options.byteOffset || 0,
44175        options.byteLength || event.data.data.byteLength
44176      );
44177      if (options.data) {
44178        options.data = event.data.data;
44179      }
44180    }
44181    callback(event.data);
44182  };
44183  transmuxer.addEventListener('message', listenForEndEvent);
44184  if (options.data) {
44185    const isArrayBuffer = options.data instanceof ArrayBuffer;
44186    message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
44187    message.byteLength = options.data.byteLength;
44188    const transfers = [isArrayBuffer ? options.data : options.data.buffer];
44189    transmuxer.postMessage(message, transfers);
44190  } else {
44191    transmuxer.postMessage(message);
44192  }
44193};
44194const REQUEST_ERRORS = {
44195  FAILURE: 2,
44196  TIMEOUT: -101,
44197  ABORTED: -102,
44198};
44199const WEB_VTT_CODEC = 'wvtt';
44200/**
44201 * Abort all requests
44202 *
44203 * @param {Object} activeXhrs - an object that tracks all XHR requests
44204 */
44205
44206const abortAll = (activeXhrs) => {
44207  activeXhrs.forEach((xhr) => {
44208    xhr.abort();
44209  });
44210};
44211/**
44212 * Gather important bandwidth stats once a request has completed
44213 *
44214 * @param {Object} request - the XHR request from which to gather stats
44215 */
44216
44217const getRequestStats = (request) => {
44218  return {
44219    bandwidth: request.bandwidth,
44220    bytesReceived: request.bytesReceived || 0,
44221    roundTripTime: request.roundTripTime || 0,
44222  };
44223};
44224/**
44225 * If possible gather bandwidth stats as a request is in
44226 * progress
44227 *
44228 * @param {Event} progressEvent - an event object from an XHR's progress event
44229 */
44230
44231const getProgressStats = (progressEvent) => {
44232  const request = progressEvent.target;
44233  const roundTripTime = Date.now() - request.requestTime;
44234  const stats = {
44235    bandwidth: Infinity,
44236    bytesReceived: 0,
44237    roundTripTime: roundTripTime || 0,
44238  };
44239  stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
44240  // because we should only use bandwidth stats on progress to determine when
44241  // abort a request early due to insufficient bandwidth
44242
44243  stats.bandwidth = Math.floor((stats.bytesReceived / stats.roundTripTime) * 8 * 1000);
44244  return stats;
44245};
44246/**
44247 * Handle all error conditions in one place and return an object
44248 * with all the information
44249 *
44250 * @param {Error|null} error - if non-null signals an error occured with the XHR
44251 * @param {Object} request -  the XHR request that possibly generated the error
44252 */
44253
44254const handleErrors = (error, request) => {
44255  const { requestType } = request;
44256  const metadata = getStreamingNetworkErrorMetadata({
44257    requestType,
44258    request,
44259    error,
44260  });
44261  if (request.timedout) {
44262    return {
44263      status: request.status,
44264      message: 'HLS request timed-out at URL: ' + request.uri,
44265      code: REQUEST_ERRORS.TIMEOUT,
44266      xhr: request,
44267      metadata,
44268    };
44269  }
44270  if (request.aborted) {
44271    return {
44272      status: request.status,
44273      message: 'HLS request aborted at URL: ' + request.uri,
44274      code: REQUEST_ERRORS.ABORTED,
44275      xhr: request,
44276      metadata,
44277    };
44278  }
44279  if (error) {
44280    return {
44281      status: request.status,
44282      message: 'HLS request errored at URL: ' + request.uri,
44283      code: REQUEST_ERRORS.FAILURE,
44284      xhr: request,
44285      metadata,
44286    };
44287  }
44288  if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
44289    return {
44290      status: request.status,
44291      message: 'Empty HLS response at URL: ' + request.uri,
44292      code: REQUEST_ERRORS.FAILURE,
44293      xhr: request,
44294      metadata,
44295    };
44296  }
44297  return null;
44298};
44299/**
44300 * Handle responses for key data and convert the key data to the correct format
44301 * for the decryption step later
44302 *
44303 * @param {Object} segment - a simplified copy of the segmentInfo object
44304 *                           from SegmentLoader
44305 * @param {Array} objects - objects to add the key bytes to.
44306 * @param {Function} finishProcessingFn - a callback to execute to continue processing
44307 *                                        this request
44308 */
44309
44310const handleKeyResponse =
44311  (segment, objects, finishProcessingFn, triggerSegmentEventFn) => (error, request) => {
44312    const response = request.response;
44313    const errorObj = handleErrors(error, request);
44314    if (errorObj) {
44315      return finishProcessingFn(errorObj, segment);
44316    }
44317    if (response.byteLength !== 16) {
44318      return finishProcessingFn(
44319        {
44320          status: request.status,
44321          message: 'Invalid HLS key at URL: ' + request.uri,
44322          code: REQUEST_ERRORS.FAILURE,
44323          xhr: request,
44324        },
44325        segment
44326      );
44327    }
44328    const view = new DataView(response);
44329    const bytes = new Uint32Array([
44330      view.getUint32(0),
44331      view.getUint32(4),
44332      view.getUint32(8),
44333      view.getUint32(12),
44334    ]);
44335    for (let i = 0; i < objects.length; i++) {
44336      objects[i].bytes = bytes;
44337    }
44338    const keyInfo = {
44339      uri: request.uri,
44340    };
44341    triggerSegmentEventFn({
44342      type: 'segmentkeyloadcomplete',
44343      segment,
44344      keyInfo,
44345    });
44346    return finishProcessingFn(null, segment);
44347  };
44348/**
44349 * Processes an mp4 init segment depending on the codec through the transmuxer.
44350 *
44351 * @param {Object} segment init segment to process
44352 * @param {string} codec the codec of the text segments
44353 */
44354
44355const initMp4Text = (segment, codec) => {
44356  if (codec === WEB_VTT_CODEC) {
44357    segment.transmuxer.postMessage({
44358      action: 'initMp4WebVttParser',
44359      data: segment.map.bytes,
44360    });
44361  }
44362};
44363/**
44364 * Parses an mp4 text segment with the transmuxer and calls the doneFn from
44365 * the segment loader.
44366 *
44367 * @param {Object} segment the text segment to parse
44368 * @param {string} codec the codec of the text segment
44369 * @param {Function} doneFn the doneFn passed from the segment loader
44370 */
44371
44372const parseMp4TextSegment = (segment, codec, doneFn) => {
44373  if (codec === WEB_VTT_CODEC) {
44374    workerCallback({
44375      action: 'getMp4WebVttText',
44376      data: segment.bytes,
44377      transmuxer: segment.transmuxer,
44378      callback: ({ data, mp4VttCues }) => {
44379        segment.bytes = data;
44380        doneFn(null, segment, {
44381          mp4VttCues,
44382        });
44383      },
44384    });
44385  }
44386};
44387const parseInitSegment = (segment, callback) => {
44388  const type = containers.detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
44389  // only know how to parse mp4 init segments at the moment
44390
44391  if (type !== 'mp4') {
44392    const uri = segment.map.resolvedUri || segment.map.uri;
44393    const mediaType = type || 'unknown';
44394    return callback({
44395      internal: true,
44396      message: `Found unsupported ${mediaType} container for initialization segment at URL: ${uri}`,
44397      code: REQUEST_ERRORS.FAILURE,
44398      metadata: {
44399        mediaType,
44400      },
44401    });
44402  }
44403  workerCallback({
44404    action: 'probeMp4Tracks',
44405    data: segment.map.bytes,
44406    transmuxer: segment.transmuxer,
44407    callback: ({ tracks, data }) => {
44408      // transfer bytes back to us
44409      segment.map.bytes = data;
44410      tracks.forEach(function (track) {
44411        segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
44412
44413        if (segment.map.tracks[track.type]) {
44414          return;
44415        }
44416        segment.map.tracks[track.type] = track;
44417        if (typeof track.id === 'number' && track.timescale) {
44418          segment.map.timescales = segment.map.timescales || {};
44419          segment.map.timescales[track.id] = track.timescale;
44420        }
44421        if (track.type === 'text') {
44422          initMp4Text(segment, track.codec);
44423        }
44424      });
44425      return callback(null);
44426    },
44427  });
44428};
44429/**
44430 * Handle init-segment responses
44431 *
44432 * @param {Object} segment - a simplified copy of the segmentInfo object
44433 *                           from SegmentLoader
44434 * @param {Function} finishProcessingFn - a callback to execute to continue processing
44435 *                                        this request
44436 */
44437
44438const handleInitSegmentResponse =
44439  ({ segment, finishProcessingFn, triggerSegmentEventFn }) =>
44440  (error, request) => {
44441    const errorObj = handleErrors(error, request);
44442    if (errorObj) {
44443      return finishProcessingFn(errorObj, segment);
44444    }
44445    const bytes = new Uint8Array(request.response);
44446    triggerSegmentEventFn({
44447      type: 'segmentloaded',
44448      segment,
44449    }); // init segment is encypted, we will have to wait
44450    // until the key request is done to decrypt.
44451
44452    if (segment.map.key) {
44453      segment.map.encryptedBytes = bytes;
44454      return finishProcessingFn(null, segment);
44455    }
44456    segment.map.bytes = bytes;
44457    parseInitSegment(segment, function (parseError) {
44458      if (parseError) {
44459        parseError.xhr = request;
44460        parseError.status = request.status;
44461        return finishProcessingFn(parseError, segment);
44462      }
44463      finishProcessingFn(null, segment);
44464    });
44465  };
44466/**
44467 * Response handler for segment-requests being sure to set the correct
44468 * property depending on whether the segment is encryped or not
44469 * Also records and keeps track of stats that are used for ABR purposes
44470 *
44471 * @param {Object} segment - a simplified copy of the segmentInfo object
44472 *                           from SegmentLoader
44473 * @param {Function} finishProcessingFn - a callback to execute to continue processing
44474 *                                        this request
44475 */
44476
44477const handleSegmentResponse =
44478  ({ segment, finishProcessingFn, responseType, triggerSegmentEventFn }) =>
44479  (error, request) => {
44480    const errorObj = handleErrors(error, request);
44481    if (errorObj) {
44482      return finishProcessingFn(errorObj, segment);
44483    }
44484    triggerSegmentEventFn({
44485      type: 'segmentloaded',
44486      segment,
44487    });
44488    const newBytes =
44489      // although responseText "should" exist, this guard serves to prevent an error being
44490      // thrown for two primary cases:
44491      // 1. the mime type override stops working, or is not implemented for a specific
44492      //    browser
44493      // 2. when using mock XHR libraries like sinon that do not allow the override behavior
44494      responseType === 'arraybuffer' || !request.responseText
44495        ? request.response
44496        : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
44497    segment.stats = getRequestStats(request);
44498    if (segment.key) {
44499      segment.encryptedBytes = new Uint8Array(newBytes);
44500    } else {
44501      segment.bytes = new Uint8Array(newBytes);
44502    }
44503    return finishProcessingFn(null, segment);
44504  };
44505const transmuxAndNotify = ({
44506  segment,
44507  bytes,
44508  trackInfoFn,
44509  timingInfoFn,
44510  videoSegmentTimingInfoFn,
44511  audioSegmentTimingInfoFn,
44512  id3Fn,
44513  captionsFn,
44514  isEndOfTimeline,
44515  endedTimelineFn,
44516  dataFn,
44517  doneFn,
44518  onTransmuxerLog,
44519  triggerSegmentEventFn,
44520}) => {
44521  const fmp4Tracks = (segment.map && segment.map.tracks) || {};
44522  const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
44523  // One reason for this is that in the case of full segments, we want to trust start
44524  // times from the probe, rather than the transmuxer.
44525
44526  let audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
44527  const audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
44528  let videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
44529  const videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
44530  const finish = () =>
44531    transmux({
44532      bytes,
44533      transmuxer: segment.transmuxer,
44534      audioAppendStart: segment.audioAppendStart,
44535      gopsToAlignWith: segment.gopsToAlignWith,
44536      remux: isMuxed,
44537      onData: (result) => {
44538        result.type = result.type === 'combined' ? 'video' : result.type;
44539        dataFn(segment, result);
44540      },
44541      onTrackInfo: (trackInfo) => {
44542        if (trackInfoFn) {
44543          if (isMuxed) {
44544            trackInfo.isMuxed = true;
44545          }
44546          trackInfoFn(segment, trackInfo);
44547        }
44548      },
44549      onAudioTimingInfo: (audioTimingInfo) => {
44550        // we only want the first start value we encounter
44551        if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
44552          audioStartFn(audioTimingInfo.start);
44553          audioStartFn = null;
44554        } // we want to continually update the end time
44555
44556        if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
44557          audioEndFn(audioTimingInfo.end);
44558        }
44559      },
44560      onVideoTimingInfo: (videoTimingInfo) => {
44561        // we only want the first start value we encounter
44562        if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
44563          videoStartFn(videoTimingInfo.start);
44564          videoStartFn = null;
44565        } // we want to continually update the end time
44566
44567        if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
44568          videoEndFn(videoTimingInfo.end);
44569        }
44570      },
44571      onVideoSegmentTimingInfo: (videoSegmentTimingInfo) => {
44572        const timingInfo = {
44573          pts: {
44574            start: videoSegmentTimingInfo.start.presentation,
44575            end: videoSegmentTimingInfo.end.presentation,
44576          },
44577          dts: {
44578            start: videoSegmentTimingInfo.start.decode,
44579            end: videoSegmentTimingInfo.end.decode,
44580          },
44581        };
44582        triggerSegmentEventFn({
44583          type: 'segmenttransmuxingtiminginfoavailable',
44584          segment,
44585          timingInfo,
44586        });
44587        videoSegmentTimingInfoFn(videoSegmentTimingInfo);
44588      },
44589      onAudioSegmentTimingInfo: (audioSegmentTimingInfo) => {
44590        const timingInfo = {
44591          pts: {
44592            start: audioSegmentTimingInfo.start.pts,
44593            end: audioSegmentTimingInfo.end.pts,
44594          },
44595          dts: {
44596            start: audioSegmentTimingInfo.start.dts,
44597            end: audioSegmentTimingInfo.end.dts,
44598          },
44599        };
44600        triggerSegmentEventFn({
44601          type: 'segmenttransmuxingtiminginfoavailable',
44602          segment,
44603          timingInfo,
44604        });
44605        audioSegmentTimingInfoFn(audioSegmentTimingInfo);
44606      },
44607      onId3: (id3Frames, dispatchType) => {
44608        id3Fn(segment, id3Frames, dispatchType);
44609      },
44610      onCaptions: (captions) => {
44611        captionsFn(segment, [captions]);
44612      },
44613      isEndOfTimeline,
44614      onEndedTimeline: () => {
44615        endedTimelineFn();
44616      },
44617      onTransmuxerLog,
44618      onDone: (result, error) => {
44619        if (!doneFn) {
44620          return;
44621        }
44622        result.type = result.type === 'combined' ? 'video' : result.type;
44623        triggerSegmentEventFn({
44624          type: 'segmenttransmuxingcomplete',
44625          segment,
44626        });
44627        doneFn(error, segment, result);
44628      },
44629      segment,
44630      triggerSegmentEventFn,
44631    }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
44632  // Meaning cached frame data may corrupt our notion of where this segment
44633  // really starts. To get around this, probe for the info needed.
44634
44635  workerCallback({
44636    action: 'probeTs',
44637    transmuxer: segment.transmuxer,
44638    data: bytes,
44639    baseStartTime: segment.baseStartTime,
44640    callback: (data) => {
44641      segment.bytes = bytes = data.data;
44642      const probeResult = data.result;
44643      if (probeResult) {
44644        trackInfoFn(segment, {
44645          hasAudio: probeResult.hasAudio,
44646          hasVideo: probeResult.hasVideo,
44647          isMuxed,
44648        });
44649        trackInfoFn = null;
44650      }
44651      finish();
44652    },
44653  });
44654};
44655const handleSegmentBytes = ({
44656  segment,
44657  bytes,
44658  trackInfoFn,
44659  timingInfoFn,
44660  videoSegmentTimingInfoFn,
44661  audioSegmentTimingInfoFn,
44662  id3Fn,
vendor: 4,304 bytes, lines 44663-44773
44663  captionsFn,
44664  isEndOfTimeline,
44665  endedTimelineFn,
44666  dataFn,
44667  doneFn,
44668  onTransmuxerLog,
44669  triggerSegmentEventFn,
44670}) => {
44671  let bytesAsUint8Array = new Uint8Array(bytes); // TODO:
44672  // We should have a handler that fetches the number of bytes required
44673  // to check if something is fmp4. This will allow us to save bandwidth
44674  // because we can only exclude a playlist and abort requests
44675  // by codec after trackinfo triggers.
44676
44677  if (containers.isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
44678    segment.isFmp4 = true;
44679    const { tracks } = segment.map;
44680    const isMp4TextSegment = tracks.text && (!tracks.audio || !tracks.video);
44681    if (isMp4TextSegment) {
44682      dataFn(segment, {
44683        data: bytesAsUint8Array,
44684        type: 'text',
44685      });
44686      parseMp4TextSegment(segment, tracks.text.codec, doneFn);
44687      return;
44688    }
44689    const trackInfo = {
44690      isFmp4: true,
44691      hasVideo: !!tracks.video,
44692      hasAudio: !!tracks.audio,
44693    }; // if we have a audio track, with a codec that is not set to
44694    // encrypted audio
44695
44696    if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
44697      trackInfo.audioCodec = tracks.audio.codec;
44698    } // if we have a video track, with a codec that is not set to
44699    // encrypted video
44700
44701    if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
44702      trackInfo.videoCodec = tracks.video.codec;
44703    }
44704    if (tracks.video && tracks.audio) {
44705      trackInfo.isMuxed = true;
44706    } // since we don't support appending fmp4 data on progress, we know we have the full
44707    // segment here
44708
44709    trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
44710    // time. The end time can be roughly calculated by the receiver using the duration.
44711    //
44712    // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
44713    // that is the true start of the segment (where the playback engine should begin
44714    // decoding).
44715
44716    const finishLoading = (captions, id3Frames) => {
44717      // if the track still has audio at this point it is only possible
44718      // for it to be audio only. See `tracks.video && tracks.audio` if statement
44719      // above.
44720      // we make sure to use segment.bytes here as that
44721      dataFn(segment, {
44722        data: bytesAsUint8Array,
44723        type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video',
44724      });
44725      if (id3Frames && id3Frames.length) {
44726        id3Fn(segment, id3Frames);
44727      }
44728      if (captions && captions.length) {
44729        captionsFn(segment, captions);
44730      }
44731      doneFn(null, segment, {});
44732    };
44733    workerCallback({
44734      action: 'probeMp4StartTime',
44735      timescales: segment.map.timescales,
44736      data: bytesAsUint8Array,
44737      transmuxer: segment.transmuxer,
44738      callback: ({ data, startTime }) => {
44739        // transfer bytes back to us
44740        bytes = data.buffer;
44741        segment.bytes = bytesAsUint8Array = data;
44742        if (trackInfo.hasAudio && !trackInfo.isMuxed) {
44743          timingInfoFn(segment, 'audio', 'start', startTime);
44744        }
44745        if (trackInfo.hasVideo) {
44746          timingInfoFn(segment, 'video', 'start', startTime);
44747        }
44748        workerCallback({
44749          action: 'probeEmsgID3',
44750          data: bytesAsUint8Array,
44751          transmuxer: segment.transmuxer,
44752          offset: startTime,
44753          callback: ({ emsgData, id3Frames }) => {
44754            // transfer bytes back to us
44755            bytes = emsgData.buffer;
44756            segment.bytes = bytesAsUint8Array = emsgData; // Run through the CaptionParser in case there are captions.
44757            // Initialize CaptionParser if it hasn't been yet
44758
44759            if (!tracks.video || !emsgData.byteLength || !segment.transmuxer) {
44760              finishLoading(undefined, id3Frames);
44761              return;
44762            }
44763            workerCallback({
44764              action: 'pushMp4Captions',
44765              endAction: 'mp4Captions',
44766              transmuxer: segment.transmuxer,
44767              data: bytesAsUint8Array,
44768              timescales: segment.map.timescales,
44769              trackIds: [tracks.video.id],
44770              callback: (message) => {
44771                // transfer bytes back to us
44772                bytes = message.data.buffer;
44773                segment.bytes = bytesAsUint8Array = message.data;
44774                message.logs.forEach(function (log) {
44775                  onTransmuxerLog(
44776                    merge(log, {
44777                      stream: 'mp4CaptionParser',
44778                    })
44779                  );
44780                });
44781                finishLoading(message.captions, id3Frames);
44782              },
44783            });
44784          },
44785        });
44786      },
44787    });
44788    return;
44789  } // VTT or other segments that don't need processing
44790
44791  if (!segment.transmuxer) {
44792    doneFn(null, segment, {});
44793    return;
44794  }
44795  if (typeof segment.container === 'undefined') {
44796    segment.container = containers.detectContainerForBytes(bytesAsUint8Array);
44797  }
44798  if (segment.container !== 'ts' && segment.container !== 'aac') {
44799    trackInfoFn(segment, {
44800      hasAudio: false,
44801      hasVideo: false,
44802    });
44803    doneFn(null, segment, {});
44804    return;
44805  } // ts or aac
44806
44807  transmuxAndNotify({
44808    segment,
44809    bytes,
44810    trackInfoFn,
44811    timingInfoFn,
44812    videoSegmentTimingInfoFn,
44813    audioSegmentTimingInfoFn,
44814    id3Fn,
44815    captionsFn,
44816    isEndOfTimeline,
44817    endedTimelineFn,
44818    dataFn,
44819    doneFn,
44820    onTransmuxerLog,
44821    triggerSegmentEventFn,
44822  });
44823};
44824const decrypt = function (
44825  { id, key, encryptedBytes, decryptionWorker, segment, doneFn },
44826  callback
44827) {
44828  const decryptionHandler = (event) => {
44829    if (event.data.source === id) {
44830      decryptionWorker.removeEventListener('message', decryptionHandler);
44831      const decrypted = event.data.decrypted;
44832      callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
44833    }
44834  };
44835  decryptionWorker.onerror = () => {
44836    const message = 'An error occurred in the decryption worker';
44837    const segmentInfo = segmentInfoPayload({
44838      segment,
44839    });
44840    const decryptError = {
44841      message,
44842      metadata: {
44843        error: new Error(message),
44844        errorType: videojs.Error.StreamingFailedToDecryptSegment,
44845        segmentInfo,
44846        keyInfo: {
44847          uri: segment.key.resolvedUri || segment.map.key.resolvedUri,
44848        },
44849      },
44850    };
44851    doneFn(decryptError, segment);
44852  };
44853  decryptionWorker.addEventListener('message', decryptionHandler);
44854  let keyBytes;
44855  if (key.bytes.slice) {
44856    keyBytes = key.bytes.slice();
44857  } else {
44858    keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
44859  } // incrementally decrypt the bytes
44860
44861  decryptionWorker.postMessage(
44862    createTransferableMessage({
44863      source: id,
44864      encrypted: encryptedBytes,
44865      key: keyBytes,
44866      iv: key.iv,
44867    }),
44868    [encryptedBytes.buffer, keyBytes.buffer]
44869  );
44870};
44871/**
44872 * Decrypt the segment via the decryption web worker
44873 *
44874 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
44875 *                                       routines
44876 * @param {Object} segment - a simplified copy of the segmentInfo object
44877 *                           from SegmentLoader
44878 * @param {Function} trackInfoFn - a callback that receives track info
44879 * @param {Function} timingInfoFn - a callback that receives timing info
44880 * @param {Function} videoSegmentTimingInfoFn
44881 *                   a callback that receives video timing info based on media times and
44882 *                   any adjustments made by the transmuxer
44883 * @param {Function} audioSegmentTimingInfoFn
44884 *                   a callback that receives audio timing info based on media times and
44885 *                   any adjustments made by the transmuxer
44886 * @param {boolean}  isEndOfTimeline
44887 *                   true if this segment represents the last segment in a timeline
44888 * @param {Function} endedTimelineFn
44889 *                   a callback made when a timeline is ended, will only be called if
44890 *                   isEndOfTimeline is true
44891 * @param {Function} dataFn - a callback that is executed when segment bytes are available
44892 *                            and ready to use
44893 * @param {Function} doneFn - a callback that is executed after decryption has completed
44894 */
44895
44896const decryptSegment = ({
44897  decryptionWorker,
44898  segment,
44899  trackInfoFn,
44900  timingInfoFn,
44901  videoSegmentTimingInfoFn,
44902  audioSegmentTimingInfoFn,
44903  id3Fn,
44904  captionsFn,
44905  isEndOfTimeline,
44906  endedTimelineFn,
44907  dataFn,
44908  doneFn,
44909  onTransmuxerLog,
44910  triggerSegmentEventFn,
44911}) => {
44912  triggerSegmentEventFn({
44913    type: 'segmentdecryptionstart',
44914  });
44915  decrypt(
44916    {
44917      id: segment.requestId,
44918      key: segment.key,
44919      encryptedBytes: segment.encryptedBytes,
44920      decryptionWorker,
44921      segment,
44922      doneFn,
44923    },
44924    (decryptedBytes) => {
44925      segment.bytes = decryptedBytes;
44926      triggerSegmentEventFn({
44927        type: 'segmentdecryptioncomplete',
44928        segment,
44929      });
44930      handleSegmentBytes({
44931        segment,
44932        bytes: segment.bytes,
44933        trackInfoFn,
44934        timingInfoFn,
44935        videoSegmentTimingInfoFn,
44936        audioSegmentTimingInfoFn,
44937        id3Fn,
44938        captionsFn,
44939        isEndOfTimeline,
44940        endedTimelineFn,
44941        dataFn,
44942        doneFn,
44943        onTransmuxerLog,
44944        triggerSegmentEventFn,
44945      });
44946    }
44947  );
44948};
44949/**
44950 * This function waits for all XHRs to finish (with either success or failure)
44951 * before continueing processing via it's callback. The function gathers errors
44952 * from each request into a single errors array so that the error status for
44953 * each request can be examined later.
44954 *
44955 * @param {Object} activeXhrs - an object that tracks all XHR requests
44956 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
44957 *                                       routines
44958 * @param {Function} trackInfoFn - a callback that receives track info
44959 * @param {Function} timingInfoFn - a callback that receives timing info
44960 * @param {Function} videoSegmentTimingInfoFn
44961 *                   a callback that receives video timing info based on media times and
44962 *                   any adjustments made by the transmuxer
44963 * @param {Function} audioSegmentTimingInfoFn
44964 *                   a callback that receives audio timing info based on media times and
44965 *                   any adjustments made by the transmuxer
44966 * @param {Function} id3Fn - a callback that receives ID3 metadata
44967 * @param {Function} captionsFn - a callback that receives captions
44968 * @param {boolean}  isEndOfTimeline
44969 *                   true if this segment represents the last segment in a timeline
44970 * @param {Function} endedTimelineFn
44971 *                   a callback made when a timeline is ended, will only be called if
44972 *                   isEndOfTimeline is true
44973 * @param {Function} dataFn - a callback that is executed when segment bytes are available
44974 *                            and ready to use
44975 * @param {Function} doneFn - a callback that is executed after all resources have been
44976 *                            downloaded and any decryption completed
44977 */
44978
44979const waitForCompletion = ({
44980  activeXhrs,
44981  decryptionWorker,
44982  trackInfoFn,
44983  timingInfoFn,
44984  videoSegmentTimingInfoFn,
44985  audioSegmentTimingInfoFn,
44986  id3Fn,
44987  captionsFn,
44988  isEndOfTimeline,
44989  endedTimelineFn,
44990  dataFn,
44991  doneFn,
44992  onTransmuxerLog,
44993  triggerSegmentEventFn,
44994}) => {
44995  let count = 0;
44996  let didError = false;
44997  return (error, segment) => {
44998    if (didError) {
44999      return;
45000    }
45001    if (error) {
45002      didError = true; // If there are errors, we have to abort any outstanding requests
45003
45004      abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
45005      // handle the aborted events from those requests, there are some cases where we may
45006      // never get an aborted event. For instance, if the network connection is lost and
45007      // there were two requests, the first may have triggered an error immediately, while
45008      // the second request remains unsent. In that case, the aborted algorithm will not
45009      // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
45010      //
45011      // We also can't rely on the ready state of the XHR, since the request that
45012      // triggered the connection error may also show as a ready state of 0 (unsent).
45013      // Therefore, we have to finish this group of requests immediately after the first
45014      // seen error.
45015
45016      return doneFn(error, segment);
45017    }
45018    count += 1;
45019    if (count === activeXhrs.length) {
45020      const segmentFinish = function () {
45021        if (segment.encryptedBytes) {
45022          return decryptSegment({
45023            decryptionWorker,
45024            segment,
45025            trackInfoFn,
45026            timingInfoFn,
45027            videoSegmentTimingInfoFn,
45028            audioSegmentTimingInfoFn,
45029            id3Fn,
45030            captionsFn,
45031            isEndOfTimeline,
45032            endedTimelineFn,
45033            dataFn,
45034            doneFn,
45035            onTransmuxerLog,
45036            triggerSegmentEventFn,
45037          });
45038        } // Otherwise, everything is ready just continue
45039
45040        handleSegmentBytes({
45041          segment,
45042          bytes: segment.bytes,
45043          trackInfoFn,
45044          timingInfoFn,
45045          videoSegmentTimingInfoFn,
45046          audioSegmentTimingInfoFn,
45047          id3Fn,
45048          captionsFn,
45049          isEndOfTimeline,
45050          endedTimelineFn,
45051          dataFn,
45052          doneFn,
45053          onTransmuxerLog,
45054          triggerSegmentEventFn,
45055        });
45056      }; // Keep track of when *all* of the requests have completed
45057
45058      segment.endOfAllRequests = Date.now();
45059      if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
45060        triggerSegmentEventFn({
45061          type: 'segmentdecryptionstart',
45062          segment,
45063        });
45064        return decrypt(
45065          {
45066            decryptionWorker,
45067            // add -init to the "id" to differentiate between segment
45068            // and init segment decryption, just in case they happen
45069            // at the same time at some point in the future.
45070            id: segment.requestId + '-init',
45071            encryptedBytes: segment.map.encryptedBytes,
45072            key: segment.map.key,
45073            segment,
45074            doneFn,
45075          },
45076          (decryptedBytes) => {
45077            segment.map.bytes = decryptedBytes;
45078            triggerSegmentEventFn({
45079              type: 'segmentdecryptioncomplete',
45080              segment,
45081            });
45082            parseInitSegment(segment, (parseError) => {
45083              if (parseError) {
45084                abortAll(activeXhrs);
45085                return doneFn(parseError, segment);
45086              }
45087              segmentFinish();
45088            });
45089          }
45090        );
45091      }
45092      segmentFinish();
45093    }
45094  };
45095};
45096/**
45097 * Calls the abort callback if any request within the batch was aborted. Will only call
45098 * the callback once per batch of requests, even if multiple were aborted.
45099 *
45100 * @param {Object} loadendState - state to check to see if the abort function was called
45101 * @param {Function} abortFn - callback to call for abort
45102 */
45103
45104const handleLoadEnd =
45105  ({ loadendState, abortFn }) =>
45106  (event) => {
45107    const request = event.target;
45108    if (request.aborted && abortFn && !loadendState.calledAbortFn) {
45109      abortFn();
45110      loadendState.calledAbortFn = true;
45111    }
45112  };
45113/**
45114 * Simple progress event callback handler that gathers some stats before
45115 * executing a provided callback with the `segment` object
45116 *
45117 * @param {Object} segment - a simplified copy of the segmentInfo object
45118 *                           from SegmentLoader
45119 * @param {Function} progressFn - a callback that is executed each time a progress event
45120 *                                is received
45121 * @param {Function} trackInfoFn - a callback that receives track info
45122 * @param {Function} timingInfoFn - a callback that receives timing info
45123 * @param {Function} videoSegmentTimingInfoFn
45124 *                   a callback that receives video timing info based on media times and
45125 *                   any adjustments made by the transmuxer
45126 * @param {Function} audioSegmentTimingInfoFn
45127 *                   a callback that receives audio timing info based on media times and
45128 *                   any adjustments made by the transmuxer
45129 * @param {boolean}  isEndOfTimeline
45130 *                   true if this segment represents the last segment in a timeline
45131 * @param {Function} endedTimelineFn
45132 *                   a callback made when a timeline is ended, will only be called if
45133 *                   isEndOfTimeline is true
45134 * @param {Function} dataFn - a callback that is executed when segment bytes are available
45135 *                            and ready to use
45136 * @param {Event} event - the progress event object from XMLHttpRequest
45137 */
45138
45139const handleProgress =
45140  ({
45141    segment,
45142    progressFn,
45143    trackInfoFn,
45144    timingInfoFn,
45145    videoSegmentTimingInfoFn,
45146    audioSegmentTimingInfoFn,
45147    id3Fn,
45148    captionsFn,
45149    isEndOfTimeline,
45150    endedTimelineFn,
45151    dataFn,
45152  }) =>
45153  (event) => {
45154    const request = event.target;
45155    if (request.aborted) {
45156      return;
45157    }
45158    segment.stats = merge(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
45159
45160    if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
45161      segment.stats.firstBytesReceivedAt = Date.now();
45162    }
45163    return progressFn(event, segment);
45164  };
45165/**
45166 * Load all resources and does any processing necessary for a media-segment
45167 *
45168 * Features:
45169 *   decrypts the media-segment if it has a key uri and an iv
45170 *   aborts *all* requests if *any* one request fails
45171 *
45172 * The segment object, at minimum, has the following format:
45173 * {
45174 *   resolvedUri: String,
45175 *   [transmuxer]: Object,
45176 *   [byterange]: {
45177 *     offset: Number,
45178 *     length: Number
45179 *   },
45180 *   [key]: {
45181 *     resolvedUri: String
45182 *     [byterange]: {
45183 *       offset: Number,
45184 *       length: Number
45185 *     },
45186 *     iv: {
45187 *       bytes: Uint32Array
45188 *     }
45189 *   },
45190 *   [map]: {
45191 *     resolvedUri: String,
45192 *     [byterange]: {
45193 *       offset: Number,
45194 *       length: Number
45195 *     },
45196 *     [bytes]: Uint8Array
45197 *   }
45198 * }
45199 * ...where [name] denotes optional properties
45200 *
45201 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
45202 * @param {Object} xhrOptions - the base options to provide to all xhr requests
45203 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
45204 *                                       decryption routines
45205 * @param {Object} segment - a simplified copy of the segmentInfo object
45206 *                           from SegmentLoader
45207 * @param {Function} abortFn - a callback called (only once) if any piece of a request was
45208 *                             aborted
45209 * @param {Function} progressFn - a callback that receives progress events from the main
45210 *                                segment's xhr request
45211 * @param {Function} trackInfoFn - a callback that receives track info
45212 * @param {Function} timingInfoFn - a callback that receives timing info
45213 * @param {Function} videoSegmentTimingInfoFn
45214 *                   a callback that receives video timing info based on media times and
45215 *                   any adjustments made by the transmuxer
45216 * @param {Function} audioSegmentTimingInfoFn
45217 *                   a callback that receives audio timing info based on media times and
45218 *                   any adjustments made by the transmuxer
45219 * @param {Function} id3Fn - a callback that receives ID3 metadata
45220 * @param {Function} captionsFn - a callback that receives captions
45221 * @param {boolean}  isEndOfTimeline
45222 *                   true if this segment represents the last segment in a timeline
45223 * @param {Function} endedTimelineFn
45224 *                   a callback made when a timeline is ended, will only be called if
45225 *                   isEndOfTimeline is true
45226 * @param {Function} dataFn - a callback that receives data from the main segment's xhr
45227 *                            request, transmuxed if needed
45228 * @param {Function} doneFn - a callback that is executed only once all requests have
45229 *                            succeeded or failed
45230 * @return {Function} a function that, when invoked, immediately aborts all
45231 *                     outstanding requests
45232 */
45233
45234const mediaSegmentRequest = ({
45235  xhr,
45236  xhrOptions,
45237  decryptionWorker,
45238  segment,
45239  abortFn,
45240  progressFn,
45241  trackInfoFn,
45242  timingInfoFn,
45243  videoSegmentTimingInfoFn,
45244  audioSegmentTimingInfoFn,
45245  id3Fn,
45246  captionsFn,
45247  isEndOfTimeline,
45248  endedTimelineFn,
45249  dataFn,
45250  doneFn,
45251  onTransmuxerLog,
45252  triggerSegmentEventFn,
45253}) => {
45254  const activeXhrs = [];
45255  const finishProcessingFn = waitForCompletion({
45256    activeXhrs,
45257    decryptionWorker,
45258    trackInfoFn,
45259    timingInfoFn,
45260    videoSegmentTimingInfoFn,
45261    audioSegmentTimingInfoFn,
45262    id3Fn,
45263    captionsFn,
45264    isEndOfTimeline,
45265    endedTimelineFn,
45266    dataFn,
45267    doneFn,
45268    onTransmuxerLog,
45269    triggerSegmentEventFn,
45270  }); // optionally, request the decryption key
45271
45272  if (segment.key && !segment.key.bytes) {
45273    const objects = [segment.key];
45274    if (
45275      segment.map &&
45276      !segment.map.bytes &&
45277      segment.map.key &&
45278      segment.map.key.resolvedUri === segment.key.resolvedUri
45279    ) {
45280      objects.push(segment.map.key);
45281    }
45282    const keyRequestOptions = merge(xhrOptions, {
45283      uri: segment.key.resolvedUri,
45284      responseType: 'arraybuffer',
45285      requestType: 'segment-key',
45286    });
45287    const keyRequestCallback = handleKeyResponse(
45288      segment,
45289      objects,
45290      finishProcessingFn,
45291      triggerSegmentEventFn
45292    );
45293    const keyInfo = {
45294      uri: segment.key.resolvedUri,
45295    };
45296    triggerSegmentEventFn({
45297      type: 'segmentkeyloadstart',
45298      segment,
45299      keyInfo,
45300    });
45301    const keyXhr = xhr(keyRequestOptions, keyRequestCallback);
45302    activeXhrs.push(keyXhr);
45303  } // optionally, request the associated media init segment
45304
45305  if (segment.map && !segment.map.bytes) {
45306    const differentMapKey =
45307      segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
45308    if (differentMapKey) {
45309      const mapKeyRequestOptions = merge(xhrOptions, {
45310        uri: segment.map.key.resolvedUri,
45311        responseType: 'arraybuffer',
45312        requestType: 'segment-key',
45313      });
45314      const mapKeyRequestCallback = handleKeyResponse(
45315        segment,
45316        [segment.map.key],
45317        finishProcessingFn,
45318        triggerSegmentEventFn
45319      );
45320      const keyInfo = {
45321        uri: segment.map.key.resolvedUri,
45322      };
45323      triggerSegmentEventFn({
45324        type: 'segmentkeyloadstart',
45325        segment,
45326        keyInfo,
45327      });
45328      const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
45329      activeXhrs.push(mapKeyXhr);
45330    }
45331    const initSegmentOptions = merge(xhrOptions, {
45332      uri: segment.map.resolvedUri,
45333      responseType: 'arraybuffer',
45334      headers: segmentXhrHeaders(segment.map),
45335      requestType: 'segment-media-initialization',
45336    });
45337    const initSegmentRequestCallback = handleInitSegmentResponse({
45338      segment,
45339      finishProcessingFn,
45340      triggerSegmentEventFn,
45341    });
45342    triggerSegmentEventFn({
45343      type: 'segmentloadstart',
45344      segment,
45345    });
45346    const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
45347    activeXhrs.push(initSegmentXhr);
45348  }
45349  const segmentRequestOptions = merge(xhrOptions, {
45350    uri: (segment.part && segment.part.resolvedUri) || segment.resolvedUri,
45351    responseType: 'arraybuffer',
45352    headers: segmentXhrHeaders(segment),
45353    requestType: 'segment',
45354  });
45355  const segmentRequestCallback = handleSegmentResponse({
45356    segment,
45357    finishProcessingFn,
45358    responseType: segmentRequestOptions.responseType,
45359    triggerSegmentEventFn,
45360  });
45361  triggerSegmentEventFn({
45362    type: 'segmentloadstart',
45363    segment,
45364  });
45365  const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
45366  segmentXhr.addEventListener(
45367    'progress',
45368    handleProgress({
45369      segment,
45370      progressFn,
45371      trackInfoFn,
45372      timingInfoFn,
45373      videoSegmentTimingInfoFn,
45374      audioSegmentTimingInfoFn,
45375      id3Fn,
45376      captionsFn,
45377      isEndOfTimeline,
45378      endedTimelineFn,
45379      dataFn,
45380    })
45381  );
45382  activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
45383  // multiple times, provide a shared state object
45384
45385  const loadendState = {};
45386  activeXhrs.forEach((activeXhr) => {
45387    activeXhr.addEventListener(
45388      'loadend',
45389      handleLoadEnd({
45390        loadendState,
45391        abortFn,
45392      })
45393    );
45394  });
45395  return () => abortAll(activeXhrs);
45396};
45397
45398/**
45399 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
45400 * codec strings, or translating codec strings into objects that can be examined.
45401 */
45402const logFn$1 = logger('CodecUtils');
45403/**
45404 * Returns a set of codec strings parsed from the playlist or the default
45405 * codec strings if no codecs were specified in the playlist
45406 *
45407 * @param {Playlist} media the current media playlist
45408 * @return {Object} an object with the video and audio codecs
45409 */
45410
45411const getCodecs = function (media) {
45412  // if the codecs were explicitly specified, use them instead of the
45413  // defaults
45414  const mediaAttributes = media.attributes || {};
45415  if (mediaAttributes.CODECS) {
45416    return codecs_js.parseCodecs(mediaAttributes.CODECS);
45417  }
45418};
45419const isMaat = (main, media) => {
45420  const mediaAttributes = media.attributes || {};
45421  return (
45422    main &&
45423    main.mediaGroups &&
45424    main.mediaGroups.AUDIO &&
45425    mediaAttributes.AUDIO &&
45426    main.mediaGroups.AUDIO[mediaAttributes.AUDIO]
45427  );
45428};
45429const isMuxed = (main, media) => {
45430  if (!isMaat(main, media)) {
45431    return true;
45432  }
45433  const mediaAttributes = media.attributes || {};
45434  const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
45435  for (const groupId in audioGroup) {
45436    // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
45437    // or there are listed playlists (the case for DASH, as the manifest will have already
45438    // provided all of the details necessary to generate the audio playlist, as opposed to
45439    // HLS' externally requested playlists), then the content is demuxed.
45440    if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
45441      return true;
45442    }
45443  }
45444  return false;
vendor: 5,133 bytes, lines 45445-45599
45445};
45446const unwrapCodecList = function (codecList) {
45447  const codecs = {};
45448  codecList.forEach(({ mediaType, type, details }) => {
45449    codecs[mediaType] = codecs[mediaType] || [];
45450    codecs[mediaType].push(codecs_js.translateLegacyCodec(`${type}${details}`));
45451  });
45452  Object.keys(codecs).forEach(function (mediaType) {
45453    if (codecs[mediaType].length > 1) {
45454      logFn$1(
45455        `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.`
45456      );
45457      codecs[mediaType] = null;
45458      return;
45459    }
45460    codecs[mediaType] = codecs[mediaType][0];
45461  });
45462  return codecs;
45463};
45464const codecCount = function (codecObj) {
45465  let count = 0;
45466  if (codecObj.audio) {
45467    count++;
45468  }
45469  if (codecObj.video) {
45470    count++;
45471  }
45472  return count;
45473};
45474/**
45475 * Calculates the codec strings for a working configuration of
45476 * SourceBuffers to play variant streams in a main playlist. If
45477 * there is no possible working configuration, an empty object will be
45478 * returned.
45479 *
45480 * @param main {Object} the m3u8 object for the main playlist
45481 * @param media {Object} the m3u8 object for the variant playlist
45482 * @return {Object} the codec strings.
45483 *
45484 * @private
45485 */
45486
45487const codecsForPlaylist = function (main, media) {
45488  const mediaAttributes = media.attributes || {};
45489  const codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
45490  // Put another way, there is no way to have a video-only multiple-audio HLS!
45491
45492  if (isMaat(main, media) && !codecInfo.audio) {
45493    if (!isMuxed(main, media)) {
45494      // It is possible for codecs to be specified on the audio media group playlist but
45495      // not on the rendition playlist. This is mostly the case for DASH, where audio and
45496      // video are always separate (and separately specified).
45497      const defaultCodecs = unwrapCodecList(
45498        codecs_js.codecsFromDefault(main, mediaAttributes.AUDIO) || []
45499      );
45500      if (defaultCodecs.audio) {
45501        codecInfo.audio = defaultCodecs.audio;
45502      }
45503    }
45504  }
45505  return codecInfo;
45506};
45507const logFn = logger('PlaylistSelector');
45508const representationToString = function (representation) {
45509  if (!representation || !representation.playlist) {
45510    return;
45511  }
45512  const playlist = representation.playlist;
45513  return JSON.stringify({
45514    id: playlist.id,
45515    bandwidth: representation.bandwidth,
45516    width: representation.width,
45517    height: representation.height,
45518    codecs: (playlist.attributes && playlist.attributes.CODECS) || '',
45519  });
45520}; // Utilities
45521
45522/**
45523 * Returns the CSS value for the specified property on an element
45524 * using `getComputedStyle`. Firefox has a long-standing issue where
45525 * getComputedStyle() may return null when running in an iframe with
45526 * `display: none`.
45527 *
45528 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
45529 * @param {HTMLElement} el the htmlelement to work on
45530 * @param {string} the proprety to get the style for
45531 */
45532
45533const safeGetComputedStyle = function (el, property) {
45534  if (!el) {
45535    return '';
45536  }
45537  const result = window__default['default'].getComputedStyle(el);
45538  if (!result) {
45539    return '';
45540  }
45541  return result[property];
45542};
45543/**
45544 * Resuable stable sort function
45545 *
45546 * @param {Playlists} array
45547 * @param {Function} sortFn Different comparators
45548 * @function stableSort
45549 */
45550
45551const stableSort = function (array, sortFn) {
45552  const newArray = array.slice();
45553  array.sort(function (left, right) {
45554    const cmp = sortFn(left, right);
45555    if (cmp === 0) {
45556      return newArray.indexOf(left) - newArray.indexOf(right);
45557    }
45558    return cmp;
45559  });
45560};
45561/**
45562 * A comparator function to sort two playlist object by bandwidth.
45563 *
45564 * @param {Object} left a media playlist object
45565 * @param {Object} right a media playlist object
45566 * @return {number} Greater than zero if the bandwidth attribute of
45567 * left is greater than the corresponding attribute of right. Less
45568 * than zero if the bandwidth of right is greater than left and
45569 * exactly zero if the two are equal.
45570 */
45571
45572const comparePlaylistBandwidth = function (left, right) {
45573  let leftBandwidth;
45574  let rightBandwidth;
45575  if (left.attributes.BANDWIDTH) {
45576    leftBandwidth = left.attributes.BANDWIDTH;
45577  }
45578  leftBandwidth = leftBandwidth || window__default['default'].Number.MAX_VALUE;
45579  if (right.attributes.BANDWIDTH) {
45580    rightBandwidth = right.attributes.BANDWIDTH;
45581  }
45582  rightBandwidth = rightBandwidth || window__default['default'].Number.MAX_VALUE;
45583  return leftBandwidth - rightBandwidth;
45584};
45585/**
45586 * A comparator function to sort two playlist object by resolution (width).
45587 *
45588 * @param {Object} left a media playlist object
45589 * @param {Object} right a media playlist object
45590 * @return {number} Greater than zero if the resolution.width attribute of
45591 * left is greater than the corresponding attribute of right. Less
45592 * than zero if the resolution.width of right is greater than left and
45593 * exactly zero if the two are equal.
45594 */
45595
45596const comparePlaylistResolution = function (left, right) {
45597  let leftWidth;
45598  let rightWidth;
45599  if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
45600    leftWidth = left.attributes.RESOLUTION.width;
45601  }
45602  leftWidth = leftWidth || window__default['default'].Number.MAX_VALUE;
45603  if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
45604    rightWidth = right.attributes.RESOLUTION.width;
45605  }
45606  rightWidth = rightWidth || window__default['default'].Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
45607  // have the same media dimensions/ resolution
45608
45609  if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
45610    return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
45611  }
45612  return leftWidth - rightWidth;
45613};
45614/**
45615 * Chooses the appropriate media playlist based on bandwidth and player size
45616 *
45617 * @param {Object} main
45618 *        Object representation of the main manifest
45619 * @param {number} playerBandwidth
45620 *        Current calculated bandwidth of the player
45621 * @param {number} playerWidth
45622 *        Current width of the player element (should account for the device pixel ratio)
45623 * @param {number} playerHeight
45624 *        Current height of the player element (should account for the device pixel ratio)
45625 * @param {boolean} limitRenditionByPlayerDimensions
45626 *        True if the player width and height should be used during the selection, false otherwise
45627 * @param {Object} playlistController
45628 *        the current playlistController object
45629 * @return {Playlist} the highest bitrate playlist less than the
45630 * currently detected bandwidth, accounting for some amount of
45631 * bandwidth variance
45632 */
45633
45634let simpleSelector = function (
45635  main,
45636  playerBandwidth,
45637  playerWidth,
45638  playerHeight,
45639  limitRenditionByPlayerDimensions,
45640  playlistController
45641) {
45642  // If we end up getting called before `main` is available, exit early
45643  if (!main) {
45644    return;
45645  }
45646  const options = {
45647    bandwidth: playerBandwidth,
45648    width: playerWidth,
45649    height: playerHeight,
45650    limitRenditionByPlayerDimensions,
45651  };
45652  let playlists = main.playlists; // if playlist is audio only, select between currently active audio group playlists.
45653
45654  if (Playlist.isAudioOnly(main)) {
45655    playlists = playlistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
45656    // at the buttom of this function for debugging.
45657
45658    options.audioOnly = true;
45659  } // convert the playlists to an intermediary representation to make comparisons easier
45660
45661  let sortedPlaylistReps = playlists.map((playlist) => {
45662    let bandwidth;
45663    const width =
45664      playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
45665    const height =
45666      playlist.attributes &&
45667      playlist.attributes.RESOLUTION &&
45668      playlist.attributes.RESOLUTION.height;
45669    bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
45670    bandwidth = bandwidth || window__default['default'].Number.MAX_VALUE;
45671    return {
45672      bandwidth,
45673      width,
45674      height,
45675      playlist,
45676    };
45677  });
45678  stableSort(sortedPlaylistReps, (left, right) => left.bandwidth - right.bandwidth); // filter out any playlists that have been excluded due to
45679  // incompatible configurations
45680
45681  sortedPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isIncompatible(rep.playlist)); // filter out any playlists that have been disabled manually through the representations
45682  // api or excluded temporarily due to playback errors.
45683
45684  let enabledPlaylistReps = sortedPlaylistReps.filter((rep) => Playlist.isEnabled(rep.playlist));
45685  if (!enabledPlaylistReps.length) {
45686    // if there are no enabled playlists, then they have all been excluded or disabled
45687    // by the user through the representations api. In this case, ignore exclusion and
45688    // fallback to what the user wants by using playlists the user has not disabled.
45689    enabledPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isDisabled(rep.playlist));
45690  } // filter out any variant that has greater effective bitrate
45691  // than the current estimated bandwidth
45692
45693  const bandwidthPlaylistReps = enabledPlaylistReps.filter(
45694    (rep) => rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth
45695  );
45696  let highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
45697  // and then taking the very first element
45698
45699  const bandwidthBestRep = bandwidthPlaylistReps.filter(
45700    (rep) => rep.bandwidth === highestRemainingBandwidthRep.bandwidth
45701  )[0]; // if we're not going to limit renditions by player size, make an early decision.
45702
45703  if (limitRenditionByPlayerDimensions === false) {
45704    const chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
45705    if (chosenRep && chosenRep.playlist) {
45706      let type = 'sortedPlaylistReps';
45707      if (bandwidthBestRep) {
45708        type = 'bandwidthBestRep';
45709      }
45710      if (enabledPlaylistReps[0]) {
45711        type = 'enabledPlaylistReps';
45712      }
45713      logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
45714      return chosenRep.playlist;
45715    }
45716    logFn('could not choose a playlist with options', options);
45717    return null;
45718  } // filter out playlists without resolution information
45719
45720  const haveResolution = bandwidthPlaylistReps.filter((rep) => rep.width && rep.height); // sort variants by resolution
45721
45722  stableSort(haveResolution, (left, right) => left.width - right.width); // if we have the exact resolution as the player use it
45723
45724  const resolutionBestRepList = haveResolution.filter(
45725    (rep) => rep.width === playerWidth && rep.height === playerHeight
45726  );
45727  highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
45728
45729  const resolutionBestRep = resolutionBestRepList.filter(
45730    (rep) => rep.bandwidth === highestRemainingBandwidthRep.bandwidth
45731  )[0];
45732  let resolutionPlusOneList;
45733  let resolutionPlusOneSmallest;
45734  let resolutionPlusOneRep; // find the smallest variant that is larger than the player
45735  // if there is no match of exact resolution
45736
45737  if (!resolutionBestRep) {
45738    resolutionPlusOneList = haveResolution.filter(
45739      (rep) => rep.width > playerWidth || rep.height >
vendor: 6,685 bytes, lines 45739-45917
45739 playerHeight
45740    ); // find all the variants have the same smallest resolution
45741
45742    resolutionPlusOneSmallest = resolutionPlusOneList.filter(
45743      (rep) =>
45744        rep.width === resolutionPlusOneList[0].width &&
45745        rep.height === resolutionPlusOneList[0].height
45746    ); // ensure that we also pick the highest bandwidth variant that
45747    // is just-larger-than the video player
45748
45749    highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
45750    resolutionPlusOneRep = resolutionPlusOneSmallest.filter(
45751      (rep) => rep.bandwidth === highestRemainingBandwidthRep.bandwidth
45752    )[0];
45753  }
45754  let leastPixelDiffRep; // If this selector proves to be better than others,
45755  // resolutionPlusOneRep and resolutionBestRep and all
45756  // the code involving them should be removed.
45757
45758  if (playlistController.leastPixelDiffSelector) {
45759    // find the variant that is closest to the player's pixel size
45760    const leastPixelDiffList = haveResolution.map((rep) => {
45761      rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
45762      return rep;
45763    }); // get the highest bandwidth, closest resolution playlist
45764
45765    stableSort(leastPixelDiffList, (left, right) => {
45766      // sort by highest bandwidth if pixelDiff is the same
45767      if (left.pixelDiff === right.pixelDiff) {
45768        return right.bandwidth - left.bandwidth;
45769      }
45770      return left.pixelDiff - right.pixelDiff;
45771    });
45772    leastPixelDiffRep = leastPixelDiffList[0];
45773  } // fallback chain of variants
45774
45775  const chosenRep =
45776    leastPixelDiffRep ||
45777    resolutionPlusOneRep ||
45778    resolutionBestRep ||
45779    bandwidthBestRep ||
45780    enabledPlaylistReps[0] ||
45781    sortedPlaylistReps[0];
45782  if (chosenRep && chosenRep.playlist) {
45783    let type = 'sortedPlaylistReps';
45784    if (leastPixelDiffRep) {
45785      type = 'leastPixelDiffRep';
45786    } else if (resolutionPlusOneRep) {
45787      type = 'resolutionPlusOneRep';
45788    } else if (resolutionBestRep) {
45789      type = 'resolutionBestRep';
45790    } else if (bandwidthBestRep) {
45791      type = 'bandwidthBestRep';
45792    } else if (enabledPlaylistReps[0]) {
45793      type = 'enabledPlaylistReps';
45794    }
45795    logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
45796    return chosenRep.playlist;
45797  }
45798  logFn('could not choose a playlist with options', options);
45799  return null;
45800};
45801
45802/**
45803 * Chooses the appropriate media playlist based on the most recent
45804 * bandwidth estimate and the player size.
45805 *
45806 * Expects to be called within the context of an instance of VhsHandler
45807 *
45808 * @return {Playlist} the highest bitrate playlist less than the
45809 * currently detected bandwidth, accounting for some amount of
45810 * bandwidth variance
45811 */
45812
45813const lastBandwidthSelector = function () {
45814  let pixelRatio = this.useDevicePixelRatio ? window__default['default'].devicePixelRatio || 1 : 1;
45815  if (!isNaN(this.customPixelRatio)) {
45816    pixelRatio = this.customPixelRatio;
45817  }
45818  return simpleSelector(
45819    this.playlists.main,
45820    this.systemBandwidth,
45821    parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
45822    parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
45823    this.limitRenditionByPlayerDimensions,
45824    this.playlistController_
45825  );
45826};
45827/**
45828 * Chooses the appropriate media playlist based on an
45829 * exponential-weighted moving average of the bandwidth after
45830 * filtering for player size.
45831 *
45832 * Expects to be called within the context of an instance of VhsHandler
45833 *
45834 * @param {number} decay - a number between 0 and 1. Higher values of
45835 * this parameter will cause previous bandwidth estimates to lose
45836 * significance more quickly.
45837 * @return {Function} a function which can be invoked to create a new
45838 * playlist selector function.
45839 * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
45840 */
45841
45842const movingAverageBandwidthSelector = function (decay) {
45843  let average = -1;
45844  let lastSystemBandwidth = -1;
45845  if (decay < 0 || decay > 1) {
45846    throw new Error('Moving average bandwidth decay must be between 0 and 1.');
45847  }
45848  return function () {
45849    let pixelRatio = this.useDevicePixelRatio
45850      ? window__default['default'].devicePixelRatio || 1
45851      : 1;
45852    if (!isNaN(this.customPixelRatio)) {
45853      pixelRatio = this.customPixelRatio;
45854    }
45855    if (average < 0) {
45856      average = this.systemBandwidth;
45857      lastSystemBandwidth = this.systemBandwidth;
45858    } // stop the average value from decaying for every 250ms
45859    // when the systemBandwidth is constant
45860    // and
45861    // stop average from setting to a very low value when the
45862    // systemBandwidth becomes 0 in case of chunk cancellation
45863
45864    if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
45865      average = decay * this.systemBandwidth + (1 - decay) * average;
45866      lastSystemBandwidth = this.systemBandwidth;
45867    }
45868    return simpleSelector(
45869      this.playlists.main,
45870      average,
45871      parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
45872      parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
45873      this.limitRenditionByPlayerDimensions,
45874      this.playlistController_
45875    );
45876  };
45877};
45878/**
45879 * Chooses the appropriate media playlist based on the potential to rebuffer
45880 *
45881 * @param {Object} settings
45882 *        Object of information required to use this selector
45883 * @param {Object} settings.main
45884 *        Object representation of the main manifest
45885 * @param {number} settings.currentTime
45886 *        The current time of the player
45887 * @param {number} settings.bandwidth
45888 *        Current measured bandwidth
45889 * @param {number} settings.duration
45890 *        Duration of the media
45891 * @param {number} settings.segmentDuration
45892 *        Segment duration to be used in round trip time calculations
45893 * @param {number} settings.timeUntilRebuffer
45894 *        Time left in seconds until the player has to rebuffer
45895 * @param {number} settings.currentTimeline
45896 *        The current timeline segments are being loaded from
45897 * @param {SyncController} settings.syncController
45898 *        SyncController for determining if we have a sync point for a given playlist
45899 * @return {Object|null}
45900 *         {Object} return.playlist
45901 *         The highest bandwidth playlist with the least amount of rebuffering
45902 *         {Number} return.rebufferingImpact
45903 *         The amount of time in seconds switching to this playlist will rebuffer. A
45904 *         negative value means that switching will cause zero rebuffering.
45905 */
45906
45907const minRebufferMaxBandwidthSelector = function (settings) {
45908  const {
45909    main,
45910    currentTime,
45911    bandwidth,
45912    duration,
45913    segmentDuration,
45914    timeUntilRebuffer,
45915    currentTimeline,
45916    syncController,
45917  }
45917 = settings; // filter out any playlists that have been excluded due to
45918  // incompatible configurations
45919
45920  const compatiblePlaylists = main.playlists.filter(
45921    (playlist) => !Playlist.isIncompatible(playlist)
45922  ); // filter out any playlists that have been disabled manually through the representations
45923  // api or excluded temporarily due to playback errors.
45924
45925  let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
45926  if (!enabledPlaylists.length) {
45927    // if there are no enabled playlists, then they have all been excluded or disabled
45928    // by the user through the representations api. In this case, ignore exclusion and
45929    // fallback to what the user wants by using playlists the user has not disabled.
45930    enabledPlaylists = compatiblePlaylists.filter((playlist) => !Playlist.isDisabled(playlist));
45931  }
45932  const bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
45933  const rebufferingEstimates = bandwidthPlaylists.map((playlist) => {
45934    const syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
45935    // sync request first. This will double the request time
45936
45937    const numRequests = syncPoint ? 1 : 2;
45938    const requestTimeEstimate = Playlist.estimateSegmentRequestTime(
45939      segmentDuration,
45940      bandwidth,
45941      playlist
45942    );
45943    const rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
45944    return {
45945      playlist,
45946      rebufferingImpact,
45947    };
45948  });
45949  const noRebufferingPlaylists = rebufferingEstimates.filter(
45950    (estimate) => estimate.rebufferingImpact <= 0
45951  ); // Sort by bandwidth DESC
45952
45953  stableSort(noRebufferingPlaylists, (a, b) => comparePlaylistBandwidth(b.playlist, a.playlist));
45954  if (noRebufferingPlaylists.length) {
45955    return noRebufferingPlaylists[0];
45956  }
45957  stableSort(rebufferingEstimates, (a, b) => a.rebufferingImpact - b.rebufferingImpact);
45958  return rebufferingEstimates[0] || null;
45959};
45960/**
45961 * Chooses the appropriate media playlist, which in this case is the lowest bitrate
45962 * one with video.  If no renditions with video exist, return the lowest audio rendition.
45963 *
45964 * Expects to be called within the context of an instance of VhsHandler
45965 *
45966 * @return {Object|null}
45967 *         {Object} return.playlist
45968 *         The lowest bitrate playlist that contains a video codec.  If no such rendition
45969 *         exists pick the lowest audio rendition.
45970 */
45971
45972const lowestBitrateCompatibleVariantSelector = function () {
45973  // filter out any playlists that have been excluded due to
45974  // incompatible configurations or playback errors
45975  const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
45976
45977  stableSort(playlists, (a, b) => comparePlaylistBandwidth(a, b)); // Parse and assume that playlists with no video codec have no video
45978  // (this is not necessarily true, although it is generally true).
45979  //
45980  // If an entire manifest has no valid videos everything will get filtered
45981  // out.
45982
45983  const playlistsWithVideo = playlists.filter(
45984    (playlist) => !!codecsForPlaylist(this.playlists.main, playlist).video
45985  );
45986  return playlistsWithVideo[0] || null;
45987};
45988
45989/**
45990 * Combine all segments into a single Uint8Array
45991 *
45992 * @param {Object} segmentObj
45993 * @return {Uint8Array} concatenated bytes
45994 * @private
45995 */
45996const concatSegments = (segmentObj) => {
45997  let offset = 0;
45998  let tempBuffer;
45999  if (segmentObj.bytes) {
46000    tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
46001
46002    segmentObj.segments.forEach((segment) => {
46003      tempBuffer.set(segment, offset);
46004      offset += segment.byteLength;
46005    });
46006  }
46007  return tempBuffer;
46008};
46009/**
46010 * Example:
46011 * https://host.com/path1/path2/path3/segment.ts?arg1=val1
46012 * -->
46013 * path3/segment.ts
46014 *
46015 * @param resolvedUri
46016 * @return {string}
46017 */
46018
46019function compactSegmentUrlDescription(resolvedUri) {
46020  try {
46021    return new URL(resolvedUri).pathname.split('/').slice(-2).join('/');
46022  } catch (e) {
46023    return '';
46024  }
46025}
46026
46027/**
46028 * @file text-tracks.js
46029 */
46030/**
46031 * Create captions text tracks on video.js if they do not exist
46032 *
46033 * @param {Object} inbandTextTracks a reference to current inbandTextTracks
46034 * @param {Object} tech the video.js tech
46035 * @param {Object} captionStream the caption stream to create
46036 * @private
46037 */
46038
46039const createCaptionsTrackIfNotExists = function (inbandTextTracks, tech, captionStream) {
46040  if (!inbandTextTracks[captionStream]) {
46041    tech.trigger({
46042      type: 'usage',
46043      name: 'vhs-608',
46044    });
46045    let instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
46046
46047    if (/^cc708_/.test(captionStream)) {
46048      instreamId = 'SERVICE' + captionStream.split('_')[1];
46049    }
46050    const track = tech.textTracks().getTrackById(instreamId);
46051    if (track) {
46052      // Resuse an existing track with a CC# id because this was
46053      // very likely created by videojs-contrib-hls from information
46054      // in the m3u8 for us to use
46055      inbandTextTracks[captionStream] = track;
46056    } else {
46057      // This section gets called when we have caption services that aren't specified in the manifest.
46058      // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
46059      const captionServices = (tech.options_.vhs && tech.options_.vhs.captionServices) || {};
46060      let label = captionStream;
46061      let language = captionStream;
46062      let def = false;
46063      const captionService = captionServices[instreamId];
46064      if (captionService) {
46065        label = captionService.label;
46066        language = captionService.language;
46067        def = captionService.default;
46068      } // Otherwise, create a track with the default `CC#` label and
46069      // without a language
46070
46071      inbandTextTracks[captionStream] = tech.addRemoteTextTrack(
46072        {
46073          kind: 'captions',
46074          id: instreamId,
46075          // TODO: investigate why this doesn't seem to turn the caption on by default
46076          default: def,
46077          label,
46078          language,
46079        },
46080        false
46081      ).track;
46082    }
46083  }
46084};
46085/**
46086 * Add caption text track data to a source handler given an array of captions
46087 *
46088 * @param {Object}
46089 *   @param {Object} inbandTextTracks the inband text tracks
46090 *   @param {number} timestampOffset the timestamp offset of the source buffer
46091 *   @param {Array} captionArray an array of caption data
46092 * @private
46093 */
46094
46095const addCaptionData = function ({ inbandTextTracks, captionArray, timestampOffset }) {
46096  if (!captionArray) {
46097    return;
46098  }
46099  const Cue = window__default['default'].WebKitDataCue || window__default['default'].VTTCue;
46100  captionArray.forEach((caption) => {
46101    const track = caption.stream; // in CEA 608 captions, video.js/mux.js sends a content array
46102    // with positioning data
46103
46104    if (caption.content) {
46105      caption.content.forEach((value) => {
46106        const cue = new Cue(
46107          caption.startTime + timestampOffset,
46108          caption.endTime + timestampOffset,
46109          value.text
46110        );
46111        cue.line = value.line;
46112        cue.align = 'left';
46113        cue.position = value.position;
46114        cue.positionAlign = 'line-left';
46115        inbandTextTracks[track].addCue(cue);
46116      });
46117    } else {
46118      // otherwise, a text value with combined captions is sent
46119      inbandTextTracks[track].addCue(
46120        new Cue(
46121          caption.startTime + timestampOffset,
46122          caption.endTime + timestampOffset,
46123          caption.text
46124        )
46125      );
46126    }
46127  });
46128};
46129/**
46130 * Define properties on a cue for backwards compatability,
46131 * but warn the user that the way that they are using it
46132 * is depricated and will be removed at a later date.
46133 *
46134 * @param {Cue} cue the cue to add the properties on
46135 * @private
46136 */
46137
46138const deprecateOldCue = function (cue) {
46139  Object.defineProperties(cue.frame, {
46140    id: {
46141      get() {
46142        videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
46143        return cue.value.key;
46144      },
46145    },
46146    value: {
46147      get() {
46148        videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
46149        return cue.value.data;
46150      },
46151    },
46152    privateData: {
46153      get() {
46154        videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
46155        return cue.value.data;
46156      },
46157    },
46158  });
46159};
46160/**
46161 * Add metadata text track data to a source handler given an array of metadata
46162 *
46163 * @param {Object}
46164 *   @param {Object} inbandTextTracks the inband text tracks
46165 *   @param {Array} metadataArray an array of meta data
46166 *   @param {number} timestampOffset the timestamp offset of the source buffer
46167 *   @param {number} videoDuration the duration of the video
46168 * @private
46169 */
46170
46171const addMetadata = ({ inbandTextTracks, metadataArray, timestampOffset, videoDuration }) => {
46172  if (!metadataArray) {
46173    return;
46174  }
46175  const Cue = window__default['default'].WebKitDataCue || window__default['default'].VTTCue;
46176  const metadataTrack = inbandTextTracks.metadataTrack_;
46177  if (!metadataTrack) {
46178    return;
46179  }
46180  metadataArray.forEach((metadata) => {
46181    const time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
46182    // ignore this bit of metadata.
46183    // This likely occurs when you have an non-timed ID3 tag like TIT2,
46184    // which is the "Title/Songname/Content description" frame
46185
46186    if (
46187      typeof time !== 'number' ||
46188      window__default['default'].isNaN(time) ||
46189      time < 0 ||
46190      !(time < Infinity)
46191    ) {
46192      return;
46193    } // If we have no frames, we can't create a cue.
46194
46195    if (!metadata.frames || !metadata.frames.length) {
46196      return;
46197    }
vendor: 7,255 bytes, lines 46198-46441
46198    metadata.frames.forEach((frame) => {
46199      const cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
46200      cue.frame = frame;
46201      cue.value = frame;
46202      deprecateOldCue(cue);
46203      metadataTrack.addCue(cue);
46204    });
46205  });
46206  if (!metadataTrack.cues || !metadataTrack.cues.length) {
46207    return;
46208  } // Updating the metadeta cues so that
46209  // the endTime of each cue is the startTime of the next cue
46210  // the endTime of last cue is the duration of the video
46211
46212  const cues = metadataTrack.cues;
46213  const cuesArray = []; // Create a copy of the TextTrackCueList...
46214  // ...disregarding cues with a falsey value
46215
46216  for (let i = 0; i < cues.length; i++) {
46217    if (cues[i]) {
46218      cuesArray.push(cues[i]);
46219    }
46220  } // Group cues by their startTime value
46221
46222  const cuesGroupedByStartTime = cuesArray.reduce((obj, cue) => {
46223    const timeSlot = obj[cue.startTime] || [];
46224    timeSlot.push(cue);
46225    obj[cue.startTime] = timeSlot;
46226    return obj;
46227  }, {}); // Sort startTimes by ascending order
46228
46229  const sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(
46230    (a, b) => Number(a) - Number(b)
46231  ); // Map each cue group's endTime to the next group's startTime
46232
46233  sortedStartTimes.forEach((startTime, idx) => {
46234    const cueGroup = cuesGroupedByStartTime[startTime];
46235    const finiteDuration = isFinite(videoDuration) ? videoDuration : startTime;
46236    const nextTime = Number(sortedStartTimes[idx + 1]) || finiteDuration; // Map each cue's endTime the next group's startTime
46237
46238    cueGroup.forEach((cue) => {
46239      cue.endTime = nextTime;
46240    });
46241  });
46242}; // object for mapping daterange attributes
46243
46244const dateRangeAttr = {
46245  id: 'ID',
46246  class: 'CLASS',
46247  startDate: 'START-DATE',
46248  duration: 'DURATION',
46249  endDate: 'END-DATE',
46250  endOnNext: 'END-ON-NEXT',
46251  plannedDuration: 'PLANNED-DURATION',
46252  scte35Out: 'SCTE35-OUT',
46253  scte35In: 'SCTE35-IN',
46254};
46255const dateRangeKeysToOmit = new Set([
46256  'id',
46257  'class',
46258  'startDate',
46259  'duration',
46260  'endDate',
46261  'endOnNext',
46262  'startTime',
46263  'endTime',
46264  'processDateRange',
46265]);
46266/**
46267 * Add DateRange metadata text track to a source handler given an array of metadata
46268 *
46269 * @param {Object}
46270 *   @param {Object} inbandTextTracks the inband text tracks
46271 *   @param {Array} dateRanges parsed media playlist
46272 * @private
46273 */
46274
46275const addDateRangeMetadata = ({ inbandTextTracks, dateRanges }) => {
46276  const metadataTrack = inbandTextTracks.metadataTrack_;
46277  if (!metadataTrack) {
46278    return;
46279  }
46280  const Cue = window__default['default'].WebKitDataCue || window__default['default'].VTTCue;
46281  dateRanges.forEach((dateRange) => {
46282    // we generate multiple cues for each date range with different attributes
46283    for (const key of Object.keys(dateRange)) {
46284      if (dateRangeKeysToOmit.has(key)) {
46285        continue;
46286      }
46287      const cue = new Cue(dateRange.startTime, dateRange.endTime, '');
46288      cue.id = dateRange.id;
46289      cue.type = 'com.apple.quicktime.HLS';
46290      cue.value = {
46291        key: dateRangeAttr[key],
46292        data: dateRange[key],
46293      };
46294      if (key === 'scte35Out' || key === 'scte35In') {
46295        cue.value.data = new Uint8Array(cue.value.data.match(/[\da-f]{2}/gi)).buffer;
46296      }
46297      metadataTrack.addCue(cue);
46298    }
46299    dateRange.processDateRange();
46300  });
46301};
46302/**
46303 * Create metadata text track on video.js if it does not exist
46304 *
46305 * @param {Object} inbandTextTracks a reference to current inbandTextTracks
46306 * @param {string} dispatchType the inband metadata track dispatch type
46307 * @param {Object} tech the video.js tech
46308 * @private
46309 */
46310
46311const createMetadataTrackIfNotExists = (inbandTextTracks, dispatchType, tech) => {
46312  if (inbandTextTracks.metadataTrack_) {
46313    return;
46314  }
46315  inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack(
46316    {
46317      kind: 'metadata',
46318      label: 'Timed Metadata',
46319    },
46320    false
46321  ).track;
46322  if (!videojs.browser.IS_ANY_SAFARI) {
46323    inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
46324  }
46325};
46326/**
46327 * Remove cues from a track on video.js.
46328 *
46329 * @param {Double} start start of where we should remove the cue
46330 * @param {Double} end end of where the we should remove the cue
46331 * @param {Object} track the text track to remove the cues from
46332 * @private
46333 */
46334
46335const removeCuesFromTrack = function (start, end, track) {
46336  let i;
46337  let cue;
46338  if (!track) {
46339    return;
46340  }
46341  if (!track.cues) {
46342    return;
46343  }
46344  i = track.cues.length;
46345  while (i--) {
46346    cue = track.cues[i]; // Remove any cue within the provided start and end time
46347
46348    if (cue.startTime >= start && cue.endTime <= end) {
46349      track.removeCue(cue);
46350    }
46351  }
46352};
46353/**
46354 * Remove duplicate cues from a track on video.js (a cue is considered a
46355 * duplicate if it has the same time interval and text as another)
46356 *
46357 * @param {Object} track the text track to remove the duplicate cues from
46358 * @private
46359 */
46360
46361const removeDuplicateCuesFromTrack = function (track) {
46362  const cues = track.cues;
46363  if (!cues) {
46364    return;
46365  }
46366  const uniqueCues = {};
46367  for (let i = cues.length - 1; i >= 0; i--) {
46368    const cue = cues[i];
46369    const cueKey = `${cue.startTime}-${cue.endTime}-${cue.text}`;
46370    if (uniqueCues[cueKey]) {
46371      track.removeCue(cue);
46372    } else {
46373      uniqueCues[cueKey] = cue;
46374    }
46375  }
46376};
46377
46378/**
46379 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
46380 * front of current time.
46381 *
46382 * @param {Array} buffer
46383 *        The current buffer of gop information
46384 * @param {number} currentTime
46385 *        The current time
46386 * @param {Double} mapping
46387 *        Offset to map display time to stream presentation time
46388 * @return {Array}
46389 *         List of gops considered safe to append over
46390 */
46391
46392const gopsSafeToAlignWith = (buffer, currentTime, mapping) => {
46393  if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
46394    return [];
46395  } // pts value for current time + 3 seconds to give a bit more wiggle room
46396
46397  const currentTimePts = Math.ceil((currentTime - mapping + 3) * clock.ONE_SECOND_IN_TS);
46398  let i;
46399  for (i = 0; i < buffer.length; i++) {
46400    if (buffer[i].pts > currentTimePts) {
46401      break;
46402    }
46403  }
46404  return buffer.slice(i);
46405};
46406/**
46407 * Appends gop information (timing and byteLength) received by the transmuxer for the
46408 * gops appended in the last call to appendBuffer
46409 *
46410 * @param {Array} buffer
46411 *        The current buffer of gop information
46412 * @param {Array} gops
46413 *        List of new gop information
46414 * @param {boolean} replace
46415 *        If true, replace the buffer with the new gop information. If false, append the
46416 *        new gop information to the buffer in the right location of time.
46417 * @return {Array}
46418 *         Updated list of gop information
46419 */
46420
46421const updateGopBuffer = (buffer, gops, replace) => {
46422  if (!gops.length) {
46423    return buffer;
46424  }
46425  if (replace) {
46426    // If we are in safe append mode, then completely overwrite the gop buffer
46427    // with the most recent appeneded data. This will make sure that when appending
46428    // future segments, we only try to align with gops that are both ahead of current
46429    // time and in the last segment appended.
46430    return gops.slice();
46431  }
46432  const start = gops[0].pts;
46433  let i = 0;
46434  for (i; i < buffer.length; i++) {
46435    if (buffer[i].pts >= start) {
46436      break;
46437    }
46438  }
46439  return buffer.slice(0, i).concat(gops);
46440};
46441/**
46442 * Removes gop information in buffer that overlaps with provided start and end
46443 *
46444 * @param {Array} buffer
46445 *        The current buffer of gop information
46446 * @param {Double} start
46447 *        position to start the remove at
46448 * @param {Double} end
46449 *        position to end the remove at
46450 * @param {Double} mapping
46451 *        Offset to map display time to stream presentation time
46452 */
46453
46454const removeGopBuffer = (buffer, start, end, mapping) => {
46455  const startPts = Math.ceil((start - mapping) * clock.ONE_SECOND_IN_TS);
46456  const endPts = Math.ceil((end - mapping) * clock.ONE_SECOND_IN_TS);
46457  const updatedBuffer = buffer.slice();
46458  let i = buffer.length;
46459  while (i--) {
46460    if (buffer[i].pts <= endPts) {
46461      break;
46462    }
46463  }
46464  if (i === -1) {
46465    // no removal because end of remove range is before start of buffer
46466    return updatedBuffer;
46467  }
46468  let j = i + 1;
46469  while (j--) {
46470    if (buffer[j].pts <= startPts) {
46471      break;
46472    }
46473  } // clamp remove range start to 0 index
46474
46475  j = Math.max(j, 0);
46476  updatedBuffer.splice(j, i - j + 1);
46477  return updatedBuffer;
46478};
46479const shallowEqual = function (a, b) {
46480  // if both are undefined
46481  // or one or the other is undefined
46482  // they are not equal
46483  if ((!a && !b) || (!a && b) || (a && !b)) {
46484    return false;
46485  } // they are the same object and thus, equal
46486
46487  if (a === b) {
46488    return true;
46489  } // sort keys so we can make sure they have
46490  // all the same keys later.
46491
46492  const akeys = Object.keys(a).sort();
46493  const bkeys = Object.keys(b).sort(); // different number of keys, not equal
46494
46495  if (akeys.length !== bkeys.length) {
46496    return false;
46497  }
46498  for (let i = 0; i < akeys.length; i++) {
46499    const key = akeys[i]; // different sorted keys, not equal
46500
46501    if (key !== bkeys[i]) {
46502      return false;
46503    } // different values, not equal
46504
46505    if (a[key] !== b[key]) {
46506      return false;
46507    }
46508  }
46509  return true;
46510};
46511
46512/**
46513 * The segment loader has no recourse except to fetch a segment in the
46514 * current playlist and use the internal timestamps in that segment to
46515 * generate a syncPoint. This function returns a good candidate index
46516 * for that process.
46517 *
46518 * @param {Array} segments - the segments array from a playlist.
46519 * @return {number} An index of a segment from the playlist to load
46520 */
46521
46522const getSyncSegmentCandidate = function (currentTimeline, segments, targetTime) {
46523  segments = segments || [];
46524  const timelineSegments = [];
46525  let time = 0;
46526  for (let i = 0; i < segments.length; i++) {
46527    const segment = segments[i];
46528    if (currentTimeline === segment.timeline) {
46529      timelineSegments.push(i);
46530      time += segment.duration;
46531      if (time > targetTime) {
46532        return i;
46533      }
46534    }
46535  }
46536  if (timelineSegments.length === 0) {
46537    return 0;
46538  } // default to the last timeline segment
46539
46540  return timelineSegments[timelineSegments.length - 1];
46541}; // In the event of a quota exceeded error, keep at least one second of back buffer. This
46542// number was arbitrarily chosen and may be updated in the future, but seemed reasonable
46543// as a start to prevent any potential issues with removing content too close to the
46544// playhead.
46545
46546const MIN_BACK_BUFFER = 1; // in ms
46547
46548const CHECK_BUFFER_DELAY = 500;
46549const finite = (num) => typeof num === 'number' && isFinite(num); // With most content hovering around 30fps, if a segment has a duration less than a half
46550// frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
46551// not accurately reflect the rest of the content.
46552
46553const MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
46554const illegalMediaSwitch = (loaderType, startingMedia, trackInfo) => {
46555  // Although these checks should most likely cover non 'main' types, for now it narrows
46556  // the scope of our checks.
46557  if (loaderType !== 'main' || !startingMedia || !trackInfo) {
46558    return null;
46559  }
46560  if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
46561    return 'Neither audio nor video found in segment.';
46562  }
46563  if (startingMedia.hasVideo && !trackInfo.hasVideo) {
46564    return (
46565      'Only audio found in segment when we expected video.' +
46566      " We can't switch to audio only from a stream that had video." +
46567      ' To get rid of this message, please add codec information to the manifest.'
46568    );
46569  }
46570  if (!startingMedia.hasVideo && trackInfo.hasVideo) {
46571    return (
46572      'Video found in segment when we expected only audio.' +
46573      " We can't switch to a stream with video from an audio only stream." +
46574      ' To get rid of this message, please add codec information to the manifest.'
46575    );
46576  }
46577  return null;
46578};
46579/**
46580 * Calculates a time value that is safe to remove from the back buffer without interrupting
46581 * playback.
46582 *
46583 * @param {TimeRange} seekable
46584 *        The current seekable range
46585 * @param {number} currentTime
46586 *        The current time of the player
46587 * @param {number} targetDuration
46588 *        The target duration of the current playlist
46589 * @return {number}
46590 *         Time that is safe to remove from the back buffer without interrupting playback
46591 */
46592
46593const safeBackBufferTrimTime = (seekable, currentTime, targetDuration) => {
46594  // 30 seconds before the playhead provides a safe default for trimming.
46595  //
46596  // Choosing a reasonable default is particularly important for high bitrate content and
46597  // VOD videos/live streams with large windows, as the buffer may end up overfilled and
46598  // throw an APPEND_BUFFER_ERR.
46599  let trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
46600  if (seekable.length) {
46601    // Some live playlists may have a shorter window of content than the full allowed back
46602    // buffer. For these playlists, don't save content that's no longer within the window.
46603    trimTime = Math.max(trimTime, seekable.start(0));
46604  } // Don't remove within target duration of the current time to avoid the possibility of
46605  // removing the GOP currently being played, as removing it can cause playback stalls.
46606
46607  const maxTrimTime = currentTime - targetDuration;
46608  return Math.min(maxTrimTime, trimTime);
46609};
46610const segmentInfoString = (segmentInfo) => {
46611  const {
46612    startOfSegment,
46613    duration,
46614    segment,
46615    part,
46616    playlist: { mediaSequence: seq, id, segments = [] },
46617    mediaIndex: index,
46618    partIndex,
46619    timeline,
46620  } = segmentInfo;
46621  const segmentLen = segments.length - 1;
46622  let selection = 'mediaIndex/partIndex increment';
46623  if (segmentInfo.getMediaInfoForTime) {
46624    selection = `getMediaInfoForTime (${segmentInfo.getMediaInfoForTime})`;
46625  } else if (segmentInfo.isSyncRequest) {
46626    selection = 'getSyncSegmentCandidate (isSyncRequest)';
46627  }
46628  if (segmentInfo.independent) {
46629    selection += ` with independent ${segmentInfo.independent}`;
46630  }
46631  const hasPartIndex = typeof partIndex === 'number';
46632  const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
46633  const zeroBasedPartCount = hasPartIndex
46634    ? getKnownPartCount({
46635        preloadSegment: segment,
46636      }) - 1
46637    : 0;
46638  return (
46639    `${name} [${seq + index}/${seq + segmentLen}]` +
46640    (hasPartIndex ? ` part [${partIndex}/${zeroBasedPartCount}]` : '') +
46641    ` segment start/end [${segment.start} => ${segment.end}]` +
46642    (hasPartIndex ? ` part start/end [${part.start} => ${part.end}]` : '') +
46643    ` startOfSegment [${startOfSegment}]` +
46644    ` duration [${duration}]` +
46645    ` timeline [${timeline}]` +
46646    ` selected by [${selection}]` +
46647    ` playlist [${id}]`
46648  );
46649};
46650const timingInfoPropertyForMedia = (mediaType) => `${mediaType}TimingInfo`;
46651/**
46652 * Returns the timestamp offset to use for the segment.
46653 *
46654 * @param {number} segmentTimeline
46655 *        The timeline of the segment
46656 * @param {number} currentTimeline
46657 *        The timeline currently being followed by the loader
46658 * @param {number} startOfSegment
46659 *        The estimated segment start
46660 * @param {TimeRange[]} buffered
46661 *        The loader's buffer
46662 * @param {boolean} overrideCheck
46663 *        If true, no checks are made to see if the timestamp offset value should be set,
46664 *        but sets it directly to a value.
46665 *
46666 * @return {number|null}
46667 *         Either a number representing a new timestamp offset, or null if the segment is
46668 *         part of the same timeline
46669 */
46670
46671const timestampOffsetForSegment = ({
46672  segmentTimeline,
46673  currentTimeline,
46674  startOfSegment,
46675  buffered,
46676  overrideCheck,
46677}) => {
46678  // Check to see if we are crossing a discontinuity to see if we need to set the
46679  // timestamp offset on the transmuxer and source buffer.
46680  //
46681  // Previously, we changed the timestampOffset if the start of this segment was less than
46682  // the currently set timestampOffset, but this isn't desirable as it can produce bad
46683  // behavior, especially around long running live streams.
46684  if (!overrideCheck && segmentTimeline === currentTimeline) {
46685    return null;
46686  } // When changing renditions, it's possible to request a segment on an older timeline. For
46687  // instance, given two renditions with the following:
46688  //
46689  // #EXTINF:10
46690  // segment1
46691  // #EXT-X-DISCONTINUITY
46692  // #EXTINF:10
46693  // segment2
46694  // #EXTINF:10
46695  // segment3
46696  //
46697  // And the current player state:
46698  //
46699  // current time: 8
46700  // buffer: 0 => 20
46701  //
46702  // The next segment on the current rendition would be segment3, filling the buffer from
46703  // 20s onwards. However, if a rendition switch happens after segment2 was requested,
46704  // then the next segment to be requested will be segment1 from the new rendition in
46705  // order to fill time 8 and onwards. Using the buffered end would result in repeated
46706  // content (since it would position segment1 of the new rendition starting at 20s). This
46707  // case can be identified when the new segment's timeline is a prior value. Instead of
46708  // using the buffered end, the startOfSegment can be used, which, hopefully, will be
46709  // more accurate to the actual start time of the segment.
46710
46711  if (segmentTimeline < currentTimeline) {
46712    return startOfSegment;
46713  } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
46714  // value uses the end of the last segment if it is available. While this value
46715  // should often be correct, it's better to rely on the buffered end, as the new
46716  // content post discontinuity should line up with the buffered end as if it were
46717  // time 0 for the new content.
46718
46719  return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
46720};
46721/**
46722 * Returns whether or not the loader should wait for a timeline change from the timeline
46723 * change controller before processing the segment.
46724 *
46725 * Primary timing in VHS goes by video. This is different from most media players, as
46726 * audio is more often used as the primary timing source. For the foreseeable future, VHS
46727 * will continue to use video as the primary timing source, due to the current logic and
46728 * expectations built around it.
46729
46730 * Since the timing follows video, in order to maintain sync, the video loader is
46731 * responsible for setting both audio and video source buffer timestamp offsets.
46732 *
46733 * Setting different values for audio and video source buffers could lead to
46734 * desyncing. The following examples demonstrate some of the situations where this
46735 * distinction is important. Note that all of these cases involve demuxed content. When
46736 * content is muxed, the audio and video are packaged together, therefore syncing
46737 * separate media playlists is not an issue.
46738 *
46739 * CASE 1: Audio prepares to load a new timeline before video:
46740 *
46741 * Timeline:       0                 1
46742 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46743 * Audio Loader:                     ^
46744 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46745 * Video Loader              ^
46746 *
46747 * In the above example, the audio loader is preparing to load the 6th segment, the first
46748 * after a discontinuity, while the video loader is still loading the 5th segment, before
46749 * the discontinuity.
46750 *
46751 * If the audio loader goes ahead and loads and appends the 6th segment before the video
46752 * loader crosses the discontinuity, then when appended, the 6th audio segment will use
46753 * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
46754 * the audio loader must provide the audioAppendStart value to trim the content in the
46755 * transmuxer, and that value relies on the audio timestamp offset. Since the audio
46756 * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
46757 * segment until that value is provided.
46758 *
46759 * CASE 2: Video prepares to load a new timeline before audio:
46760 *
46761 * Timeline:       0                 1
46762 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46763 * Audio Loader:             ^
46764 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46765 * Video Loader                      ^
46766 *
46767 * In the above example, the video loader is preparing to load the 6th segment, the first
46768 * after a discontinuity, while the audio loader is still loading the 5th segment, before
46769 * the discontinuity.
46770 *
46771 * If the video loader goes ahead and loads and appends the 6th segment, then once the
46772 * segment is loaded and processed, both the video and audio timestamp offsets will be
46773 * set, since video is used as the primary timing source. This is to ensure content lines
46774 * up appropriately, as any modifications to the video timing are reflected by audio when
46775 * the video loader sets the audio and video timestamp offsets to the same value. However,
46776 * setting the timestamp offset for audio before audio has had a chance to change
46777 * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
46778 * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
46779 *
46780 * CASE 3: When seeking, audio prepares to load a new timeline before video
46781 *
46782 * Timeline:       0                 1
46783 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46784 * Audio Loader:           ^
46785 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
46786 * Video Loader            ^
46787 *
46788 * In the above example, both audio and video loaders are loading segments from timeline
46789 * 0, but imagine that the seek originated from timeline 1.
46790 *
46791 * When seeking to a new timeline, the timestamp offset will be set based on the expected
46792 * segment start of the loaded video segment. In order to maintain sync, the audio loader
46793 * must wait for the video loader to load its segment and update both the audio and video
46794 * timestamp offsets before it may load and append its own segment. This is the case
46795 * whether the seek results in a mismatched segment request (e.g., the audio loader
46796 * chooses to load segment 3 and the video loader chooses to load segment 4) or the
46797 * loaders choose to load the same segment index from each playlist, as the segments may
46798 * not be aligned perfectly, even for matching segment indexes.
46799 *
46800 * @param {Object} timelinechangeController
46801 * @param {number} currentTimeline
46802 *        The timeline currently being followed by the loader
46803 * @param {number} segmentTimeline
46804 *        The timeline of the segment being loaded
46805 * @param {('main'|'audio')} loaderType
46806 *        The loader type
46807 * @param {boolean} audioDisabled
46808 *        Whether the audio is disabled for the loader. This should only be true when the
46809 *        loader may have muxed audio in its segment, but should not append it, e.g., for
46810 *        the main loader when an alternate audio playlist is active.
46811 *
46812 * @return {boolean}
46813 *         Whether the loader should wait for a timeline change from the timeline change
46814 *         controller before processing the segment
46815 */
46816
46817const shouldWaitForTimelineChange = ({
46818  timelineChangeController,
46819  currentTimeline,
46820  segmentTimeline,
46821  loaderType,
46822  audioDisabled,
46823}) => {
46824  if (currentTimeline === segmentTimeline) {
46825    return false;
46826  }
46827  if (loaderType === 'audio') {
46828    const lastMainTimelineChange = timelineChangeController.lastTimelineChange({
46829      type: 'main',
46830    }); // Audio loader should wait if:
46831    //
46832    // * main hasn't had a timeline change yet (thus has not loaded its first segment)
46833    // * main hasn't yet changed to the timeline audio is looking to load
46834
46835    return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
46836  } // The main loader only needs to wait for timeline changes if there's demuxed audio.
46837  // Otherwise, there's nothing to wait for, since audio would be muxed into the main
46838  // loader's segments (or the content is audio/video only and handled by the main
46839  // loader).
46840
46841  if (loaderType === 'main' && audioDisabled) {
46842    const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
46843      type: 'audio',
46844    }); // Main loader should wait for the audio loader if audio is not pending a timeline
46845    // change to the current timeline.
46846    //
46847    // Since the main loader is responsible for setting the timestamp offset for both
46848    // audio and video, the main loader must wait for audio to be about to change to its
46849    // timeline before setting the offset, otherwise, if audio is behind in loading,
46850    // segments from the previous timeline would be adjusted by the new timestamp offset.
46851    //
46852    // This requirement means that video will not cross a timeline until the audio is
46853    // about to cross to it, so that way audio and video will always cross the timeline
46854    // together.
46855    //
46856    // In addition to normal timeline changes, these rules also apply to the start of a
46857    // stream (going from a non-existent timeline, -1, to timeline 0). It's important
46858    // that these rules apply to the first timeline change because if they did not, it's
46859    // possible that the main loader will cross two timelines before the audio loader has
46860    // crossed one. Logic may be implemented to handle the startup as a special case, but
46861    // it's easier to simply treat all timeline changes the same.
46862
46863    if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
46864      return false;
46865    }
46866    return true;
46867  }
46868  return false;
46869};
46870const shouldFixBadTimelineChanges = (timelineChangeController) => {
46871  if (!timelineChangeController) {
46872    return false;
46873  }
46874  const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
46875    type: 'audio',
46876  });
46877  const pendingMainTimelineChange = timelineChangeController.pendingTimelineChange({
46878    type: 'main',
46879  });
46880  const hasPendingTimelineChanges = pendingAudioTimelineChange && pendingMainTimelineChange;
46881  const differentPendingChanges =
46882    hasPendingTimelineChanges && pendingAudioTimelineChange.to !== pendingMainTimelineChange.to;
46883  const isNotInitialPendingTimelineChange =
46884    hasPendingTimelineChanges &&
46885    pendingAudioTimelineChange.from !== -1 &&
46886    pendingMainTimelineChange.from !== -1;
46887  if (isNotInitialPendingTimelineChange && differentPendingChanges) {
46888    return true;
46889  }
46890  return false;
46891};
46892/**
46893 * Fixes certain bad timeline scenarios by resetting the loader.
46894 *
46895 * @param {SegmentLoader} segmentLoader
46896 */
46897
46898const fixBadTimelineChange = (segmentLoader) => {
46899  if (!segmentLoader) {
46900    return;
46901  }
46902  segmentLoader.pause();
46903  segmentLoader.resetEverything();
46904  segmentLoader.load();
46905};
46906/**
46907 * Check if the pending audio timeline change is behind the
46908 * pending main timeline change.
46909 *
46910 * @param {SegmentLoader} segmentLoader
46911 * @return {boolean}
46912 */
46913
46914const isAudioTimelineBehind = (segmentLoader) => {
46915  const pendingAudioTimelineChange = segmentLoader.timelineChangeController_.pendingTimelineChange({
46916    type: 'audio',
46917  });
46918  const pendingMainTimelineChange = segmentLoader.timelineChangeController_.pendingTimelineChange({
46919    type: 'main',
46920  });
46921  const hasPendingTimelineChanges = pendingAudioTimelineChange && pendingMainTimelineChange;
46922  return hasPendingTimelineChanges && pendingAudioTimelineChange.to < pendingMainTimelineChange.to;
46923};
46924/**
46925 * A method to check if the player is waiting for a timeline change, and fixes
46926 * certain scenarios where the timelines need to be updated.
46927 *
46928 * @param {SegmentLoader} segmentLoader
46929 */
46930
46931const checkAndFixTimelines = (segmentLoader) => {
46932  const segmentInfo = segmentLoader.pendingSegment_;
46933  if (!segmentInfo) {
46934    return;
46935  }
46936  const waitingForTimelineChange = shouldWaitForTimelineChange({
46937    timelineChangeController: segmentLoader.timelineChangeController_,
46938    currentTimeline: segmentLoader.currentTimeline_,
46939    segmentTimeline: segmentInfo.timeline,
46940    loaderType: segmentLoader.loaderType_,
46941    audioDisabled: segmentLoader.audioDisabled_,
46942  });
46943  if (
46944    waitingForTimelineChange &&
46945    shouldFixBadTimelineChanges(segmentLoader.timelineChangeController_)
46946  ) {
46947    if (isAudioTimelineBehind(segmentLoader)) {
46948      segmentLoader.timelineChangeController_.trigger('audioTimelineBehind');
46949      return;
46950    }
46951    fixBadTimelineChange(segmentLoader);
46952  }
46953};
46954const mediaDuration = (timingInfos) => {
46955  let maxDuration = 0;
46956  ['video', 'audio'].forEach(function (type) {
46957    const typeTimingInfo = timingInfos[`${type}TimingInfo`];
46958    if (!typeTimingInfo) {
46959      return;
46960    }
46961    const { start, end } = typeTimingInfo;
46962    let duration;
46963    if (typeof start === 'bigint' || typeof end === 'bigint') {
46964      duration = window__default['default'].BigInt(end) - window__default['default'].BigInt(start);
46965    } else if (typeof start === 'number' && typeof end === 'number') {
46966      duration = end - start;
46967    }
46968    if (typeof duration !== 'undefined' && duration > maxDuration) {
46969      maxDuration = duration;
46970    }
46971  }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
46972  // as we only need BigInt when we are above that.
46973
46974  if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
46975    maxDuration = Number(maxDuration);
46976  }
46977  return maxDuration;
46978};
46979const segmentTooLong = ({ segmentDuration, maxDuration }) => {
46980  // 0 duration segments are most likely due to metadata only segments or a lack of
46981  // information.
46982  if (!segmentDuration) {
46983    return false;
46984  } // For HLS:
46985  //
46986  // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
46987  // The EXTINF duration of each Media Segment in the Playlist
46988  // file, when rounded to the nearest integer, MUST be less than or equal
46989  // to the target duration; longer segments can trigger playback stalls
46990  // or other errors.
46991  //
46992  // For DASH, the mpd-parser uses the largest reported segment duration as the target
46993  // duration. Although that reported duration is occasionally approximate (i.e., not
46994  // exact), a strict check may report that a segment is too long more often in DASH.
46995
46996  return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
46997};
46998const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => {
46999  // Right now we aren't following DASH's timing model exactly, so only perform
47000  // this check for HLS content.
47001  if (sourceType !== 'hls') {
47002    return null;
47003  }
47004  const segmentDuration = mediaDuration({
47005    audioTimingInfo: segmentInfo.audioTimingInfo,
47006    videoTimingInfo: segmentInfo.videoTimingInfo,
47007  }); // Don't report if we lack information.
47008  //
47009  // If the segment has a duration of 0 it is either a lack of information or a
47010  // metadata only segment and shouldn't be reported here.
47011
47012  if (!segmentDuration) {
47013    return null;
47014  }
47015  const targetDuration = segmentInfo.playlist.targetDuration;
47016  const isSegmentWayTooLong = segmentTooLong({
47017    segmentDuration,
47018    maxDuration: targetDuration * 2,
47019  });
47020  const isSegmentSlightlyTooLong = segmentTooLong({
47021    segmentDuration,
47022    maxDuration: targetDuration,
47023  });
47024  const segmentTooLongMessage =
47025    `Segment with index ${segmentInfo.mediaIndex} ` +
47026    `from playlist ${segmentInfo.playlist.id} ` +
47027    `has a duration of ${segmentDuration} ` +
47028    `when the reported duration is ${segmentInfo.duration} ` +
47029    `and the target duration is ${targetDuration}. ` +
47030    'For HLS content, a duration in excess of the target duration may result in ' +
47031    'playback issues. See the HLS specification section on EXT-X-TARGETDURATION for ' +
47032    'more details: ' +
47033    'https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1';
47034  if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
47035    return {
47036      severity: isSegmentWayTooLong ? 'warn' : 'info',
47037      message: segmentTooLongMessage,
47038    };
47039  }
47040  return null;
47041};
47042/**
47043 *
47044 * @param {Object} options type of segment loader and segment either segmentInfo or simple segment
47045 * @return a segmentInfo payload for events or errors.
47046 */
47047
47048const segmentInfoPayload = ({ type, segment }) => {
47049  if (!segment) {
47050    return;
47051  }
47052  const isEncrypted = Boolean(segment.key || (segment.map && segment.map.ke));
47053  const isMediaInitialization = Boolean(segment.map && !segment.map.bytes);
47054  const start = segment.startOfSegment === undefined ? segment.start : segment.startOfSegment;
47055  return {
47056    type: type || segment.type,
47057    uri: segment.resolvedUri || segment.uri,
47058    start,
47059    duration: segment.duration,
47060    isEncrypted,
47061    isMediaInitialization,
47062  };
47063};
47064/**
47065 * An object that manages segment loading and appending.
47066 *
47067 * @class SegmentLoader
47068 * @param {Object} options required and optional options
47069 * @extends videojs.EventTarget
47070 */
47071
47072class SegmentLoader extends videojs.EventTarget {
47073  constructor(settings, options = {}) {
47074    super(); // check pre-conditions
47075
47076    if (!settings) {
47077      throw new TypeError('Initialization settings are required');
47078    }
47079    if (typeof settings.currentTime !== 'function') {
47080      throw new TypeError('No currentTime getter specified');
47081    }
47082    if (!settings.mediaSource) {
47083      throw new TypeError('No MediaSource specified');
47084    } // public properties
47085
47086    this.bandwidth = settings.bandwidth;
47087    this.throughput = {
47088      rate: 0,
47089      count: 0,
47090    };
47091    this.roundTrip = NaN;
47092    this.resetStats_();
47093    this.mediaIndex = null;
47094    this.partIndex = null; // private settings
47095
47096    this.hasPlayed_ = settings.hasPlayed;
47097    this.currentTime_ = settings.currentTime;
47098    this.seekable_ = settings.seekable;
47099    this.seeking_ = settings.seeking;
47100    this.duration_ = settings.duration;
47101    this.mediaSource_ = settings.mediaSource;
47102    this.vhs_ = settings.vhs;
47103    this.loaderType_ = settings.loaderType;
47104    this.currentMediaInfo_ = void 0;
47105    this.startingMediaInfo_ = void 0;
47106    this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
47107    this.goalBufferLength_ = settings.goalBufferLength;
47108    this.sourceType_ = settings.sourceType;
47109    this.sourceUpdater_ = settings.sourceUpdater;
47110    this.inbandTextTracks_ = settings.inbandTextTracks;
47111    this.state_ = 'INIT';
47112    this.timelineChangeController_ = settings.timelineChangeController;
47113    this.shouldSaveSegmentTimingInfo_ = true;
47114    this.parse708captions_ = settings.parse708captions;
47115    this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
47116    this.captionServices_ = settings.captionServices;
47117    this.exactManifestTimings = settings.exactManifestTimings;
47118    this.addMetadataToTextTrack = settings.addMetadataToTextTrack; // private instance variables
47119
47120    this.checkBufferTimeout_ = null;
47121    this.error_ = void 0;
47122    this.currentTimeline_ = -1;
47123    this.shouldForceTimestampOffsetAfterResync_ = false;
47124    this.pendingSegment_ = null;
47125    this.xhrOptions_ = null;
47126    this.pendingSegments_ = [];
47127    this.audioDisabled_ = false;
47128    this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
47129
47130    this.gopBuffer_ = [];
47131    this.timeMapping_ = 0;
47132    this.safeAppend_ = false;
47133    this.appendInitSegment_ = {
47134      audio: true,
47135      video: true,
47136    };
47137    this.playlistOfLastInitSegment_ = {
47138      audio: null,
47139      video: null,
47140    };
47141    this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
47142    // information yet to start the loading process (e.g., if the audio loader wants to
47143    // load a segment from the next timeline but the main loader hasn't yet crossed that
47144    // timeline), then the load call will be added to the queue until it is ready to be
47145    // processed.
47146
47147    this.loadQueue_ = [];
47148    this.metadataQueue_ = {
47149      id3: [],
47150      caption: [],
47151    };
47152    this.waitingOnRemove_ = false;
47153    this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
47154
47155    this.activeInitSegmentId_ = null;
47156    this.initSegments_ = {}; // HLSe playback
47157
47158    this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
47159    this.keyCache_ = {};
47160    this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
47161    // between a time in the display time and a segment index within
47162    // a playlist
47163
47164    this.syncController_ = settings.syncController;
47165    this.syncPoint_ = {
47166      segmentIndex: 0,
47167      time: 0,
47168    };
47169    this.transmuxer_ = this.createTransmuxer_();
47170    this.triggerSyncInfoUpdate_ = () => this.trigger('syncinfoupdate');
47171    this.syncController_.on('syncinfoupdate', this.triggerSyncInfoUpdate_);
47172    this.mediaSource_.addEventListener('sourceopen', () => {
47173      if (!this.isEndOfStream_()) {
47174        this.ended_ = false;
47175      }
47176    }); // ...for determining the fetch location
47177
47178    this.fetchAtBuffer_ = false;
47179    this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
47180    Object.defineProperty(this, 'state', {
47181      get() {
47182        return this.state_;
47183      },
47184      set(newState) {
47185        if (newState !== this.state_) {
47186          this.logger_(`${this.state_} -> ${newState}`);
47187          this.state_ = newState;
47188          this.trigger('statechange');
47189        }
47190      },
47191    });
47192    this.sourceUpdater_.on('ready', () => {
47193      if (this.hasEnoughInfoToAppend_()) {
47194        this.processCallQueue_();
47195      } else {
47196        checkAndFixTimelines(this);
47197      }
47198    });
47199    this.sourceUpdater_.on('codecschange', (metadata) => {
47200      this.trigger(
47201        _extends__default['default'](
47202          {
47203            type: 'codecschange',
47204          },
47205          metadata
47206        )
47207      );
47208    }); // Only the main loader needs to listen for pending timeline changes, as the main
47209    // loader should wait for audio to be ready to change its timeline so that both main
47210    // and audio timelines change together. For more details, see the
47211    // shouldWaitForTimelineChange function.
47212
47213    if (this.loaderType_ === 'main') {
47214      this.timelineChangeController_.on('pendingtimelinechange', () => {
47215        if (this.hasEnoughInfoToAppend_()) {
47216          this.processCallQueue_();
47217        } else {
47218          checkAndFixTimelines(this);
47219        }
47220      });
47221    } // The main loader only listens on pending timeline changes, but the audio loader,
47222    // since its loads follow main, needs to listen on timeline changes. For more details,
47223    // see the shouldWaitForTimelineChange function.
47224
47225    if (this.loaderType_ === 'audio') {
47226      this.timelineChangeController_.on('timelinechange', (metadata) => {
47227        this.trigger(
47228          _extends__default['default'](
47229            {
47230              type: 'timelinechange',
47231            },
47232            metadata
47233          )
47234        );
47235        if (this.hasEnoughInfoToLoad_()) {
47236          this.processLoadQueue_();
47237        } else {
47238          checkAndFixTimelines(this);
47239        }
47240        if (this.hasEnoughInfoToAppend_()) {
47241          this.processCallQueue_();
47242        } else {
47243          checkAndFixTimelines(this);
47244        }
47245      });
47246    }
47247  }
47248  /**
47249   * TODO: Current sync controller consists of many hls-specific strategies
47250   * media sequence sync is also hls-specific, and we would like to be protocol-agnostic on this level
47251   * this should be a part of the sync-controller and sync controller should expect different strategy list based on the protocol.
47252   *
47253   * @return {MediaSequenceSync|null}
47254   * @private
47255   */
47256
47257  get mediaSequenceSync_() {
47258    return this.syncController_.getMediaSequenceSync(this.loaderType_);
47259  }
47260  createTransmuxer_() {
47261    return segmentTransmuxer.createTransmuxer({
47262      remux: false,
47263      alignGopsAtEnd: this.safeAppend_,
47264      keepOriginalTimestamps: true,
47265      parse708captions: this.parse708captions_,
47266      captionServices: this.captionServices_,
47267    });
47268  }
47269  /**
47270   * reset all of our media stats
47271   *
47272   * @private
47273   */
47274
47275  resetStats_() {
47276    this.mediaBytesTransferred = 0;
47277    this.mediaRequests = 0;
47278    this.mediaRequestsAborted = 0;
47279    this.mediaRequestsTimedout = 0;
47280    this.mediaRequestsErrored = 0;
47281    this.mediaTransferDuration = 0;
47282    this.mediaSecondsLoaded = 0;
47283    this.mediaAppends = 0;
47284  }
47285  /**
47286   * dispose of the SegmentLoader and reset to the default state
47287   */
47288
47289  dispose() {
47290    this.trigger('dispose');
47291    this.state = 'DISPOSED';
47292    this.pause();
47293    this.abort_();
47294    if (this.transmuxer_) {
47295      this.transmuxer_.terminate();
47296    }
47297    this.resetStats_();
47298    if (this.checkBufferTimeout_) {
47299      window__default['default'].clearTimeout(this.checkBufferTimeout_);
47300    }
47301    if (this.syncController_ && this.triggerSyncInfoUpdate_) {
47302      this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
47303    }
47304    this.off();
47305  }
47306  setAudio(enable) {
47307    this.audioDisabled_ = !enable;
47308    if (enable) {
47309      this.appendInitSegment_.audio = true;
47310    } else {
47311      // remove current track audio if it gets disabled
47312      this.sourceUpdater_.removeAudio(0, this.duration_());
47313    }
47314  }
47315  /**
47316   * abort anything that is currently doing on with the SegmentLoader
47317   * and reset to a default state
47318   */
47319
47320  abort() {
47321    if (this.state !== 'WAITING') {
47322      if (this.pendingSegment_) {
47323        this.pendingSegment_ = null;
47324      }
47325      return;
47326    }
47327    this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
47328    // since we are no longer "waiting" on any requests. XHR callback is not always run
47329    // when the request is aborted. This will prevent the loader from being stuck in the
47330    // WAITING state indefinitely.
47331
47332    this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
47333    // next segment
47334
47335    if (!this.paused()) {
47336      this.monitorBuffer_();
47337    }
47338  }
47339  /**
vendor: 13,095 bytes, lines 47340-47704
47340   * abort all pending xhr requests and null any pending segements
47341   *
47342   * @private
47343   */
47344
47345  abort_() {
47346    if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
47347      this.pendingSegment_.abortRequests();
47348    } // clear out the segment being processed
47349
47350    this.pendingSegment_ = null;
47351    this.callQueue_ = [];
47352    this.loadQueue_ = [];
47353    this.metadataQueue_.id3 = [];
47354    this.metadataQueue_.caption = [];
47355    this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
47356    this.waitingOnRemove_ = false;
47357    window__default['default'].clearTimeout(this.quotaExceededErrorRetryTimeout_);
47358    this.quotaExceededErrorRetryTimeout_ = null;
47359  }
47360  checkForAbort_(requestId) {
47361    // If the state is APPENDING, then aborts will not modify the state, meaning the first
47362    // callback that happens should reset the state to READY so that loading can continue.
47363    if (this.state === 'APPENDING' && !this.pendingSegment_) {
47364      this.state = 'READY';
47365      return true;
47366    }
47367    if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
47368      return true;
47369    }
47370    return false;
47371  }
47372  /**
47373   * set an error on the segment loader and null out any pending segements
47374   *
47375   * @param {Error} error the error to set on the SegmentLoader
47376   * @return {Error} the error that was set or that is currently set
47377   */
47378
47379  error(error) {
47380    if (typeof error !== 'undefined') {
47381      this.logger_('error occurred:', error);
47382      this.error_ = error;
47383    }
47384    this.pendingSegment_ = null;
47385    return this.error_;
47386  }
47387  endOfStream() {
47388    this.ended_ = true;
47389    if (this.transmuxer_) {
47390      // need to clear out any cached data to prepare for the new segment
47391      segmentTransmuxer.reset(this.transmuxer_);
47392    }
47393    this.gopBuffer_.length = 0;
47394    this.pause();
47395    this.trigger('ended');
47396  }
47397  /**
47398   * Indicates which time ranges are buffered
47399   *
47400   * @return {TimeRange}
47401   *         TimeRange object representing the current buffered ranges
47402   */
47403
47404  buffered_() {
47405    const trackInfo = this.getMediaInfo_();
47406    if (!this.sourceUpdater_ || !trackInfo) {
47407      return createTimeRanges();
47408    }
47409    if (this.loaderType_ === 'main') {
47410      const { hasAudio, hasVideo, isMuxed } = trackInfo;
47411      if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
47412        return this.sourceUpdater_.buffered();
47413      }
47414      if (hasVideo) {
47415        return this.sourceUpdater_.videoBuffered();
47416      }
47417    } // One case that can be ignored for now is audio only with alt audio,
47418    // as we don't yet have proper support for that.
47419
47420    return this.sourceUpdater_.audioBuffered();
47421  }
47422  /**
47423   * Gets and sets init segment for the provided map
47424   *
47425   * @param {Object} map
47426   *        The map object representing the init segment to get or set
47427   * @param {boolean=} set
47428   *        If true, the init segment for the provided map should be saved
47429   * @return {Object}
47430   *         map object for desired init segment
47431   */
47432
47433  initSegmentForMap(map, set = false) {
47434    if (!map) {
47435      return null;
47436    }
47437    const id = initSegmentId(map);
47438    let storedMap = this.initSegments_[id];
47439    if (set && !storedMap && map.bytes) {
47440      this.initSegments_[id] = storedMap = {
47441        resolvedUri: map.resolvedUri,
47442        byterange: map.byterange,
47443        bytes: map.bytes,
47444        tracks: map.tracks,
47445        timescales: map.timescales,
47446      };
47447    }
47448    return storedMap || map;
47449  }
47450  /**
47451   * Gets and sets key for the provided key
47452   *
47453   * @param {Object} key
47454   *        The key object representing the key to get or set
47455   * @param {boolean=} set
47456   *        If true, the key for the provided key should be saved
47457   * @return {Object}
47458   *         Key object for desired key
47459   */
47460
47461  segmentKey(key, set = false) {
47462    if (!key) {
47463      return null;
47464    }
47465    const id = segmentKeyId(key);
47466    let storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
47467    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
47468
47469    if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
47470      this.keyCache_[id] = storedKey = {
47471        resolvedUri: key.resolvedUri,
47472        bytes: key.bytes,
47473      };
47474    }
47475    const result = {
47476      resolvedUri: (storedKey || key).resolvedUri,
47477    };
47478    if (storedKey) {
47479      result.bytes = storedKey.bytes;
47480    }
47481    return result;
47482  }
47483  /**
47484   * Returns true if all configuration required for loading is present, otherwise false.
47485   *
47486   * @return {boolean} True if the all configuration is ready for loading
47487   * @private
47488   */
47489
47490  couldBeginLoading_() {
47491    return this.playlist_ && !this.paused();
47492  }
47493  /**
47494   * load a playlist and start to fill the buffer
47495   */
47496
47497  load() {
47498    // un-pause
47499    this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
47500    // specified
47501
47502    if (!this.playlist_) {
47503      return;
47504    } // if all the configuration is ready, initialize and begin loading
47505
47506    if (this.state === 'INIT' && this.couldBeginLoading_()) {
47507      return this.init_();
47508    } // if we're in the middle of processing a segment already, don't
47509    // kick off an additional segment request
47510
47511    if (!this.couldBeginLoading_() || (this.state !== 'READY' && this.state !== 'INIT')) {
47512      return;
47513    }
47514    this.state = 'READY';
47515  }
47516  /**
47517   * Once all the starting parameters have been specified, begin
47518   * operation. This method should only be invoked from the INIT
47519   * state.
47520   *
47521   * @private
47522   */
47523
47524  init_() {
47525    this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
47526    // audio data from the muxed content should be removed
47527
47528    this.resetEverything();
47529    return this.monitorBuffer_();
47530  }
47531  /**
47532   * set a playlist on the segment loader
47533   *
47534   * @param {PlaylistLoader} media the playlist to set on the segment loader
47535   */
47536
47537  playlist(newPlaylist, options = {}) {
47538    if (!newPlaylist) {
47539      return;
47540    }
47541    const oldPlaylist = this.playlist_;
47542    const segmentInfo = this.pendingSegment_;
47543    this.playlist_ = newPlaylist;
47544    this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
47545    // is always our zero-time so force a sync update each time the playlist
47546    // is refreshed from the server
47547    //
47548    // Use the INIT state to determine if playback has started, as the playlist sync info
47549    // should be fixed once requests begin (as sync points are generated based on sync
47550    // info), but not before then.
47551
47552    if (this.state === 'INIT') {
47553      newPlaylist.syncInfo = {
47554        mediaSequence: newPlaylist.mediaSequence,
47555        time: 0,
47556      }; // Setting the date time mapping means mapping the program date time (if available)
47557      // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
47558      // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
47559      // be updated as the playlist is refreshed before the loader starts loading, the
47560      // program date time mapping needs to be updated as well.
47561      //
47562      // This mapping is only done for the main loader because a program date time should
47563      // map equivalently between playlists.
47564
47565      if (this.loaderType_ === 'main') {
47566        this.syncController_.setDateTimeMappingForStart(newPlaylist);
47567      }
47568    }
47569    let oldId = null;
47570    if (oldPlaylist) {
47571      if (oldPlaylist.id) {
47572        oldId = oldPlaylist.id;
47573      } else if (oldPlaylist.uri) {
47574        oldId = oldPlaylist.uri;
47575      }
47576    }
47577    this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`);
47578    if (this.mediaSequenceSync_) {
47579      this.mediaSequenceSync_.update(newPlaylist, this.currentTime_());
47580      this.logger_(
47581        `Playlist update:
47582currentTime: ${this.currentTime_()}
47583bufferedEnd: ${lastBufferedEnd(this.buffered_())}
47584`,
47585        this.mediaSequenceSync_.diagnostics
47586      );
47587    } // in VOD, this is always a rendition switch (or we updated our syncInfo above)
47588    // in LIVE, we always want to update with new playlists (including refreshes)
47589
47590    this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
47591    // buffering now
47592
47593    if (this.state === 'INIT' && this.couldBeginLoading_()) {
47594      return this.init_();
47595    }
47596    if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
47597      if (this.mediaIndex !== null) {
47598        // we must reset/resync the segment loader when we switch renditions and
47599        // the segment loader is already synced to the previous rendition
47600        // We only want to reset the loader here for LLHLS playback, as resetLoader sets fetchAtBuffer_
47601        // to false, resulting in fetching segments at currentTime and causing repeated
47602        // same-segment requests on playlist change. This erroneously drives up the playback watcher
47603        // stalled segment count, as re-requesting segments at the currentTime or browser cached segments
47604        // will not change the buffer.
47605        // Reference for LLHLS fixes: https://github.com/videojs/http-streaming/pull/1201
47606        const isLLHLS = !newPlaylist.endList && typeof newPlaylist.partTargetDuration === 'number';
47607        if (isLLHLS) {
47608          this.resetLoader();
47609        } else {
47610          this.resyncLoader();
47611        }
47612      }
47613      this.currentMediaInfo_ = void 0;
47614      this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
47615
47616      return;
47617    } // we reloaded the same playlist so we are in a live scenario
47618    // and we will likely need to adjust the mediaIndex
47619
47620    const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
47621    this.logger_(`live window shift [${mediaSequenceDiff}]`); // update the mediaIndex on the SegmentLoader
47622    // this is important because we can abort a request and this value must be
47623    // equal to the last appended mediaIndex
47624
47625    if (this.mediaIndex !== null) {
47626      this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
47627      // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
47628      // new playlist was incremented by 1.
47629
47630      if (this.mediaIndex < 0) {
47631        this.mediaIndex = null;
47632        this.partIndex = null;
47633      } else {
47634        const segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
47635        // unless parts fell off of the playlist for this segment.
47636        // In that case we need to reset partIndex and resync
47637
47638        if (
47639          this.partIndex &&
47640          (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])
47641        ) {
47642          const mediaIndex = this.mediaIndex;
47643          this.logger_(`currently processing part (index ${this.partIndex}) no longer exists.`);
47644          this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
47645          // as the part was dropped from our current playlists segment.
47646          // The mediaIndex will still be valid so keep that around.
47647
47648          this.mediaIndex = mediaIndex;
47649        }
47650      }
47651    } // update the mediaIndex on the SegmentInfo object
47652    // this is important because we will update this.mediaIndex with this value
47653    // in `handleAppendsDone_` after the segment has been successfully appended
47654
47655    if (segmentInfo) {
47656      segmentInfo.mediaIndex -= mediaSequenceDiff;
47657      if (segmentInfo.mediaIndex < 0) {
47658        segmentInfo.mediaIndex = null;
47659        segmentInfo.partIndex = null;
47660      } else {
47661        // we need to update the referenced segment so that timing information is
47662        // saved for the new playlist's segment, however, if the segment fell off the
47663        // playlist, we can leave the old reference and just lose the timing info
47664        if (segmentInfo.mediaIndex >= 0) {
47665          segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
47666        }
47667        if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
47668          segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
47669        }
47670      }
47671    }
47672    this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
47673  }
47674  /**
47675   * Prevent the loader from fetching additional segments. If there
47676   * is a segment request outstanding, it will finish processing
47677   * before the loader halts. A segment loader can be unpaused by
47678   * calling load().
47679   */
47680
47681  pause() {
47682    if (this.checkBufferTimeout_) {
47683      window__default['default'].clearTimeout(this.checkBufferTimeout_);
47684      this.checkBufferTimeout_ = null;
47685    }
47686  }
47687  /**
47688   * Returns whether the segment loader is fetching additional
47689   * segments when given the opportunity. This property can be
47690   * modified through calls to pause() and load().
47691   */
47692
47693  paused() {
47694    return this.checkBufferTimeout_ === null;
47695  }
47696  /**
47697   * Delete all the buffered data and reset the SegmentLoader
47698   *
47699   * @param {Function} [done] an optional callback to be executed when the remove
47700   * operation is complete
47701   */
47702
47703  resetEverything(done) {
47704    this.ended_ = false;
vendor: 4,091 bytes, lines 47705-47817
47705    this.activeInitSegmentId_ = null;
47706    this.appendInitSegment_ = {
47707      audio: true,
47708      video: true,
47709    };
47710    this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
47711    // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
47712    // we then clamp the value to duration if necessary.
47713
47714    this.remove(0, Infinity, done); // clears fmp4 captions
47715
47716    if (this.transmuxer_) {
47717      this.transmuxer_.postMessage({
47718        action: 'clearAllMp4Captions',
47719      }); // reset the cache in the transmuxer
47720
47721      this.transmuxer_.postMessage({
47722        action: 'reset',
47723      });
47724    }
47725  }
47726  /**
47727   * Force the SegmentLoader to resync and start loading around the currentTime instead
47728   * of starting at the end of the buffer
47729   *
47730   * Useful for fast quality changes
47731   */
47732
47733  resetLoader() {
47734    this.fetchAtBuffer_ = false;
47735    if (this.mediaSequenceSync_) {
47736      this.mediaSequenceSync_.resetAppendedStatus();
47737    }
47738    this.resyncLoader();
47739  }
47740  /**
47741   * Force the SegmentLoader to restart synchronization and make a conservative guess
47742   * before returning to the simple walk-forward method
47743   */
47744
47745  resyncLoader() {
47746    if (this.transmuxer_) {
47747      // need to clear out any cached data to prepare for the new segment
47748      segmentTransmuxer.reset(this.transmuxer_);
47749    }
47750    this.mediaIndex = null;
47751    this.partIndex = null;
47752    this.syncPoint_ = null;
47753    this.isPendingTimestampOffset_ = false; // this is mainly to sync timing-info when switching between renditions with and without timestamp-rollover,
47754    // so we don't want it for DASH or fragmented mp4 segments.
47755
47756    const isFmp4 = this.currentMediaInfo_ && this.currentMediaInfo_.isFmp4;
47757    const isHlsTs = this.sourceType_ === 'hls' && !isFmp4;
47758    if (isHlsTs) {
47759      this.shouldForceTimestampOffsetAfterResync_ = true;
47760    }
47761    this.callQueue_ = [];
47762    this.loadQueue_ = [];
47763    this.metadataQueue_.id3 = [];
47764    this.metadataQueue_.caption = [];
47765    this.abort();
47766    if (this.transmuxer_) {
47767      this.transmuxer_.postMessage({
47768        action: 'clearParsedMp4Captions',
47769      });
47770    }
47771  }
47772  /**
47773   * Remove any data in the source buffer between start and end times
47774   *
47775   * @param {number} start - the start time of the region to remove from the buffer
47776   * @param {number} end - the end time of the region to remove from the buffer
47777   * @param {Function} [done] - an optional callback to be executed when the remove
47778   * @param {boolean} force - force all remove operations to happen
47779   * operation is complete
47780   */
47781
47782  remove(start, end, done = () => {}, force = false) {
47783    // clamp end to duration if we need to remove everything.
47784    // This is due to a browser bug that causes issues if we remove to Infinity.
47785    // videojs/videojs-contrib-hls#1225
47786    if (end === Infinity) {
47787      end = this.duration_();
47788    } // skip removes that would throw an error
47789    // commonly happens during a rendition switch at the start of a video
47790    // from start 0 to end 0
47791
47792    if (end <= start) {
47793      this.logger_('skipping remove because end ${end} is <= start ${start}');
47794      return;
47795    }
47796    if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
47797      this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
47798
47799      return;
47800    } // set it to one to complete this function's removes
47801
47802    let removesRemaining = 1;
47803    const removeFinished = () => {
47804      removesRemaining--;
47805      if (removesRemaining === 0) {
47806        done();
47807      }
47808    };
47809    if (force || !this.audioDisabled_) {
47810      removesRemaining++;
47811      this.sourceUpdater_.removeAudio(start, end, removeFinished);
47812    } // While it would be better to only remove video if the main loader has video, this
47813    // should be safe with audio only as removeVideo will call back even if there's no
47814    // video buffer.
47815    //
47816    // In theory we can check to see if there's video before calling the remove, but in
47817    // the event that we're switching between renditions and from video to audio only
vendor: 6,466 bytes, lines 47818-48009
47818    // (when we add support for that), we may need to clear the video contents despite
47819    // what the new media will contain.
47820
47821    if (force || this.loaderType_ === 'main') {
47822      this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
47823      removesRemaining++;
47824      this.sourceUpdater_.removeVideo(start, end, removeFinished);
47825    } // remove any captions and ID3 tags
47826
47827    for (const track in this.inbandTextTracks_) {
47828      removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
47829    }
47830    removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
47831
47832    removeFinished();
47833  }
47834  /**
47835   * (re-)schedule monitorBufferTick_ to run as soon as possible
47836   *
47837   * @private
47838   */
47839
47840  monitorBuffer_() {
47841    if (this.checkBufferTimeout_) {
47842      window__default['default'].clearTimeout(this.checkBufferTimeout_);
47843    }
47844    this.checkBufferTimeout_ = window__default['default'].setTimeout(
47845      this.monitorBufferTick_.bind(this),
47846      1
47847    );
47848  }
47849  /**
47850   * As long as the SegmentLoader is in the READY state, periodically
47851   * invoke fillBuffer_().
47852   *
47853   * @private
47854   */
47855
47856  monitorBufferTick_() {
47857    if (this.state === 'READY') {
47858      this.fillBuffer_();
47859    }
47860    if (this.checkBufferTimeout_) {
47861      window__default['default'].clearTimeout(this.checkBufferTimeout_);
47862    }
47863    this.checkBufferTimeout_ = window__default['default'].setTimeout(
47864      this.monitorBufferTick_.bind(this),
47865      CHECK_BUFFER_DELAY
47866    );
47867  }
47868  /**
47869   * fill the buffer with segements unless the sourceBuffers are
47870   * currently updating
47871   *
47872   * Note: this function should only ever be called by monitorBuffer_
47873   * and never directly
47874   *
47875   * @private
47876   */
47877
47878  fillBuffer_() {
47879    // TODO since the source buffer maintains a queue, and we shouldn't call this function
47880    // except when we're ready for the next segment, this check can most likely be removed
47881    if (this.sourceUpdater_.updating()) {
47882      return;
47883    } // see if we need to begin loading immediately
47884
47885    const segmentInfo = this.chooseNextRequest_();
47886    if (!segmentInfo) {
47887      return;
47888    }
47889    const metadata = {
47890      segmentInfo: segmentInfoPayload({
47891        type: this.loaderType_,
47892        segment: segmentInfo,
47893      }),
47894    };
47895    this.trigger({
47896      type: 'segmentselected',
47897      metadata,
47898    });
47899    if (typeof segmentInfo.timestampOffset === 'number') {
47900      this.isPendingTimestampOffset_ = false;
47901      this.timelineChangeController_.pendingTimelineChange({
47902        type: this.loaderType_,
47903        from: this.currentTimeline_,
47904        to: segmentInfo.timeline,
47905      });
47906    }
47907    this.loadSegment_(segmentInfo);
47908  }
47909  /**
47910   * Determines if we should call endOfStream on the media source based
47911   * on the state of the buffer or if appened segment was the final
47912   * segment in the playlist.
47913   *
47914   * @param {number} [mediaIndex] the media index of segment we last appended
47915   * @param {Object} [playlist] a media playlist object
47916   * @return {boolean} do we need to call endOfStream on the MediaSource
47917   */
47918
47919  isEndOfStream_(
47920    mediaIndex = this.mediaIndex,
47921    playlist = this.playlist_,
47922    partIndex = this.partIndex
47923  ) {
47924    if (!playlist || !this.mediaSource_) {
47925      return false;
47926    }
47927    const segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
47928
47929    const appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
47930
47931    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
47932    // so that MediaSources can trigger the `ended` event when it runs out of
47933    // buffered data instead of waiting for me
47934
47935    return (
47936      playlist.endList &&
47937      this.mediaSource_.readyState === 'open' &&
47938      appendedLastSegment &&
47939      appendedLastPart
47940    );
47941  }
47942  /**
47943   * Determines what request should be made given current segment loader state.
47944   *
47945   * @return {Object} a request object that describes the segment/part to load
47946   */
47947
47948  chooseNextRequest_() {
47949    const buffered = this.buffered_();
47950    const bufferedEnd = lastBufferedEnd(buffered) || 0;
47951    const bufferedTime = timeAheadOf(buffered, this.currentTime_());
47952    const preloaded = !this.hasPlayed_() && bufferedTime >= 1;
47953    const haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
47954    const segments = this.playlist_.segments; // return no segment if:
47955    // 1. we don't have segments
47956    // 2. The video has not yet played and we already downloaded a segment
47957    // 3. we already have enough buffered time
47958
47959    if (!segments.length || preloaded || haveEnoughBuffer) {
47960      return null;
47961    }
47962    this.syncPoint_ =
47963      this.syncPoint_ ||
47964      this.syncController_.getSyncPoint(
47965        this.playlist_,
47966        this.duration_(),
47967        this.currentTimeline_,
47968        this.currentTime_(),
47969        this.loaderType_
47970      );
47971    const next = {
47972      partIndex: null,
47973      mediaIndex: null,
47974      startOfSegment: null,
47975      playlist: this.playlist_,
47976      isSyncRequest: Boolean(!this.syncPoint_),
47977    };
47978    if (next.isSyncRequest) {
47979      next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
47980      this.logger_(
47981        `choose next request. Can not find sync point. Fallback to media Index: ${next.mediaIndex}`
47982      );
47983    } else if (this.mediaIndex !== null) {
47984      const segment = segments[this.mediaIndex];
47985      const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
47986      next.startOfSegment = segment.end ? segment.end : bufferedEnd;
47987      if (segment.parts && segment.parts[partIndex + 1]) {
47988        next.mediaIndex = this.mediaIndex;
47989        next.partIndex = partIndex + 1;
47990      } else {
47991        next.mediaIndex = this.mediaIndex + 1;
47992      }
47993    } else {
47994      let segmentIndex;
47995      let partIndex;
47996      let startTime;
47997      const targetTime = this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_();
47998      if (this.mediaSequenceSync_) {
47999        this.logger_(
48000          `chooseNextRequest_ request after Quality Switch:
48001For TargetTime: ${targetTime}.
48002CurrentTime: ${this.currentTime_()}
48003BufferedEnd: ${bufferedEnd}
48004Fetch At Buffer: ${this.fetchAtBuffer_}
48005`,
48006          this.mediaSequenceSync_.diagnostics
48007        );
48008      }
48009      if (this.mediaSequenceSync_ && this.mediaSequenceSync_.isReliable) {
vendor: 4,420 bytes, lines 48010-48112
48010        const syncInfo = this.getSyncInfoFromMediaSequenceSync_(targetTime);
48011        if (!syncInfo) {
48012          const message = 'No sync info found while using media sequence sync';
48013          this.error({
48014            message,
48015            metadata: {
48016              errorType: videojs.Error.StreamingFailedToSelectNextSegment,
48017              error: new Error(message),
48018            },
48019          });
48020          this.logger_('chooseNextRequest_ - no sync info found using media sequence sync'); // no match
48021
48022          return null;
48023        }
48024        this.logger_(
48025          `chooseNextRequest_ mediaSequence syncInfo (${syncInfo.start} --> ${syncInfo.end})`
48026        );
48027        segmentIndex = syncInfo.segmentIndex;
48028        partIndex = syncInfo.partIndex;
48029        startTime = syncInfo.start;
48030      } else {
48031        this.logger_(
48032          'chooseNextRequest_ - fallback to a regular segment selection algorithm, based on a syncPoint.'
48033        ); // fallback
48034
48035        const mediaInfoForTime = Playlist.getMediaInfoForTime({
48036          exactManifestTimings: this.exactManifestTimings,
48037          playlist: this.playlist_,
48038          currentTime: targetTime,
48039          startingPartIndex: this.syncPoint_.partIndex,
48040          startingSegmentIndex: this.syncPoint_.segmentIndex,
48041          startTime: this.syncPoint_.time,
48042        });
48043        segmentIndex = mediaInfoForTime.segmentIndex;
48044        partIndex = mediaInfoForTime.partIndex;
48045        startTime = mediaInfoForTime.startTime;
48046      }
48047      next.getMediaInfoForTime = this.fetchAtBuffer_
48048        ? `bufferedEnd ${targetTime}`
48049        : `currentTime ${targetTime}`;
48050      next.mediaIndex = segmentIndex;
48051      next.startOfSegment = startTime;
48052      next.partIndex = partIndex;
48053      this.logger_(
48054        `choose next request. Playlist switched and we have a sync point. Media Index: ${next.mediaIndex} `
48055      );
48056    }
48057    const nextSegment = segments[next.mediaIndex];
48058    let nextPart =
48059      nextSegment &&
48060      typeof next.partIndex === 'number' &&
48061      nextSegment.parts &&
48062      nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
48063    // the next partIndex is invalid do not choose a next segment.
48064
48065    if (!nextSegment || (typeof next.partIndex === 'number' && !nextPart)) {
48066      return null;
48067    } // if the next segment has parts, and we don't have a partIndex.
48068    // Set partIndex to 0
48069
48070    if (typeof next.partIndex !== 'number' && nextSegment.parts) {
48071      next.partIndex = 0;
48072      nextPart = nextSegment.parts[0];
48073    } // independentSegments applies to every segment in a playlist. If independentSegments appears in a main playlist,
48074    // it applies to each segment in each media playlist.
48075    // https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.5.1
48076
48077    const hasIndependentSegments =
48078      (this.vhs_.playlists &&
48079        this.vhs_.playlists.main &&
48080        this.vhs_.playlists.main.independentSegments) ||
48081      this.playlist_.independentSegments; // if we have no buffered data then we need to make sure
48082    // that the next part we append is "independent" if possible.
48083    // So we check if the previous part is independent, and request
48084    // it if it is.
48085
48086    if (!bufferedTime && nextPart && !hasIndependentSegments && !nextPart.independent) {
48087      if (next.partIndex === 0) {
48088        const lastSegment = segments[next.mediaIndex - 1];
48089        const lastSegmentLastPart =
48090          lastSegment.parts &&
48091          lastSegment.parts.length &&
48092          lastSegment.parts[lastSegment.parts.length - 1];
48093        if (lastSegmentLastPart && lastSegmentLastPart.independent) {
48094          next.mediaIndex -= 1;
48095          next.partIndex = lastSegment.parts.length - 1;
48096          next.independent = 'previous segment';
48097        }
48098      } else if (nextSegment.parts[next.partIndex - 1].independent) {
48099        next.partIndex -= 1;
48100        next.independent = 'previous part';
48101      }
48102    }
48103    const ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
48104    // 1. this is the last segment in the playlist
48105    // 2. end of stream has been called on the media source already
48106    // 3. the player is not seeking
48107
48108    if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
48109      return null;
48110    }
48111    if (this.shouldForceTimestampOffsetAfterResync_) {
48112      this.shouldForceTimestampOffsetAfterResync_ = false;
48113      next.forceTimestampOffset = true;
48114      this.logger_('choose next request. Force timestamp offset after loader resync');
48115    }
48116    return this.generateSegmentInfo_(next);
48117  }
48118  getSyncInfoFromMediaSequenceSync_(targetTime) {
48119    if (!this.mediaSequenceSync_) {
48120      return null;
48121    } // we should pull the target time to the least available time if we drop out of sync for any reason
48122
48123    const finalTargetTime = Math.max(targetTime, this.mediaSequenceSync_.start);
48124    if (targetTime !== finalTargetTime) {
48125      this.logger_(
48126        `getSyncInfoFromMediaSequenceSync_. Pulled target time from ${targetTime} to ${finalTargetTime}`
48127      );
48128    }
48129    const mediaSequenceSyncInfo = this.mediaSequenceSync_.getSyncInfoForTime(finalTargetTime);
48130    if (!mediaSequenceSyncInfo) {
48131      // no match at all
48132      return null;
48133    }
48134    if (!mediaSequenceSyncInfo.isAppended) {
48135      // has a perfect match
48136      return mediaSequenceSyncInfo;
48137    } // has match, but segment was already appended.
48138    // attempt to auto-advance to the nearest next segment:
48139
48140    const nextMediaSequenceSyncInfo = this.mediaSequenceSync_.getSyncInfoForTime(
48141      mediaSequenceSyncInfo.end
48142    );
48143    if (!nextMediaSequenceSyncInfo) {
48144      // no match at all
48145      return null;
48146    }
48147    if (nextMediaSequenceSyncInfo.isAppended) {
48148      this.logger_(
48149        'getSyncInfoFromMediaSequenceSync_: We encounter unexpected scenario where next media sequence sync info is also appended!'
48150      );
48151    } // got match with the nearest next segment
48152
48153    return nextMediaSequenceSyncInfo;
48154  }
48155  generateSegmentInfo_(options) {
48156    const {
48157      independent,
48158      playlist,
48159      mediaIndex,
48160      startOfSegment,
48161      isSyncRequest,
48162      partIndex,
48163      forceTimestampOffset,
48164      getMediaInfoForTime,
48165    } = options;
48166    const segment = playlist.segments[mediaIndex];
48167    const part = typeof partIndex === 'number' && segment.parts[partIndex];
48168    const segmentInfo = {
48169      requestId: 'segment-loader-' + Math.random(),
48170      // resolve the segment URL relative to the playlist
48171      uri: (part && part.resolvedUri) || segment.resolvedUri,
48172      // the segment's mediaIndex at the time it was requested
48173      mediaIndex,
48174      partIndex: part ? partIndex : null,
48175      // whether or not to update the SegmentLoader's state with this
48176      // segment's mediaIndex
48177      isSyncRequest,
48178      startOfSegment,
48179      // the segment's playlist
48180      playlist,
48181      // unencrypted bytes of the segment
48182      bytes: null,
48183      // when a key is defined for this segment, the encrypted bytes
48184      encryptedBytes: null,
48185      // The target timestampOffset for this segment when we append it
48186      // to the source buffer
48187      timestampOffset: null,
48188      // The timeline that the segment is in
48189      timeline: segment.timeline,
48190      // The expected duration of the segment in seconds
48191      duration: (part && part.duration) || segment.duration,
48192      // retain the segment in case the playlist updates while doing an async process
48193      segment,
48194      part,
48195      byteLength: 0,
48196      transmuxer: this.transmuxer_,
48197      // type of getMediaInfoForTime that was used to get this segment
48198      getMediaInfoForTime,
48199      independent,
48200    };
48201    const overrideCheck =
48202      typeof forceTimestampOffset !== 'undefined'
48203        ? forceTimestampOffset
48204        : this.isPendingTimestampOffset_;
48205    segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
48206      segmentTimeline: segment.timeline,
48207      currentTimeline: this.currentTimeline_,
48208      startOfSegment,
48209      buffered: this.buffered_(),
48210      overrideCheck,
48211    });
48212    const audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
48213    if (typeof audioBufferedEnd === 'number') {
48214      // since the transmuxer is using the actual timing values, but the buffer is
48215      // adjusted by the timestamp offset, we must adjust the value here
48216      segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
48217    }
48218    if (this.sourceUpdater_.videoBuffered().length) {
48219      segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(
48220        this.gopBuffer_,
48221        // since the transmuxer is using the actual timing values, but the time is
vendor: 4,209 bytes, lines 48222-48326
48222        // adjusted by the timestmap offset, we must adjust the value here
48223        this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(),
48224        this.timeMapping_
48225      );
48226    }
48227    return segmentInfo;
48228  } // get the timestampoffset for a segment,
48229  // added so that vtt segment loader can override and prevent
48230  // adding timestamp offsets.
48231
48232  timestampOffsetForSegment_(options) {
48233    return timestampOffsetForSegment(options);
48234  }
48235  /**
48236   * Determines if the network has enough bandwidth to complete the current segment
48237   * request in a timely manner. If not, the request will be aborted early and bandwidth
48238   * updated to trigger a playlist switch.
48239   *
48240   * @param {Object} stats
48241   *        Object containing stats about the request timing and size
48242   * @private
48243   */
48244
48245  earlyAbortWhenNeeded_(stats) {
48246    if (
48247      this.vhs_.tech_.paused() ||
48248      // Don't abort if the current playlist is on the lowestEnabledRendition
48249      // TODO: Replace using timeout with a boolean indicating whether this playlist is
48250      //       the lowestEnabledRendition.
48251      !this.xhrOptions_.timeout ||
48252      // Don't abort if we have no bandwidth information to estimate segment sizes
48253      !this.playlist_.attributes.BANDWIDTH
48254    ) {
48255      return;
48256    } // Wait at least 1 second since the first byte of data has been received before
48257    // using the calculated bandwidth from the progress event to allow the bitrate
48258    // to stabilize
48259
48260    if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
48261      return;
48262    }
48263    const currentTime = this.currentTime_();
48264    const measuredBandwidth = stats.bandwidth;
48265    const segmentDuration = this.pendingSegment_.duration;
48266    const requestTimeRemaining = Playlist.estimateSegmentRequestTime(
48267      segmentDuration,
48268      measuredBandwidth,
48269      this.playlist_,
48270      stats.bytesReceived
48271    ); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
48272    // if we are only left with less than 1 second when the request completes.
48273    // A negative timeUntilRebuffering indicates we are already rebuffering
48274
48275    const timeUntilRebuffer$1 =
48276      timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
48277    // is larger than the estimated time until the player runs out of forward buffer
48278
48279    if (requestTimeRemaining <= timeUntilRebuffer$1) {
48280      return;
48281    }
48282    const switchCandidate = minRebufferMaxBandwidthSelector({
48283      main: this.vhs_.playlists.main,
48284      currentTime,
48285      bandwidth: measuredBandwidth,
48286      duration: this.duration_(),
48287      segmentDuration,
48288      timeUntilRebuffer: timeUntilRebuffer$1,
48289      currentTimeline: this.currentTimeline_,
48290      syncController: this.syncController_,
48291    });
48292    if (!switchCandidate) {
48293      return;
48294    }
48295    const rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
48296    const timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
48297    let minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
48298    // potential round trip time of the new request so that we are not too aggressive
48299    // with switching to a playlist that might save us a fraction of a second.
48300
48301    if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
48302      minimumTimeSaving = 1;
48303    }
48304    if (
48305      !switchCandidate.playlist ||
48306      switchCandidate.playlist.uri === this.playlist_.uri ||
48307      timeSavedBySwitching < minimumTimeSaving
48308    ) {
48309      return;
48310    } // set the bandwidth to that of the desired playlist being sure to scale by
48311    // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
48312    // don't trigger a bandwidthupdate as the bandwidth is artifial
48313
48314    this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
48315    this.trigger('earlyabort');
48316  }
48317  handleAbort_(segmentInfo) {
48318    this.logger_(`Aborting ${segmentInfoString(segmentInfo)}`);
48319    this.mediaRequestsAborted += 1;
48320  }
48321  /**
48322   * XHR `progress` event handler
48323   *
48324   * @param {Event}
48325   *        The XHR `progress` event
48326   * @param {Object} simpleSegment
48327   *        A simplified segment object copy
48328   * @private
48329   */
48330
48331  handleProgress_(event, simpleSegment) {
48332    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48333    if (this.checkForAbort_(simpleSegment.requestId)) {
48334      return;
48335    }
48336    this.trigger('progress');
48337  }
48338  handleTrackInfo_(simpleSegment, trackInfo) {
48339    const { hasAudio, hasVideo } = trackInfo;
48340    const metadata = {
48341      segmentInfo: segmentInfoPayload({
48342        type: this.loaderType_,
48343        segment: simpleSegment,
48344      }),
48345      trackInfo: {
48346        hasAudio,
48347        hasVideo,
48348      },
48349    };
48350    this.trigger({
48351      type: 'segmenttransmuxingtrackinfoavailable',
48352      metadata,
48353    });
48354    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48355    if (this.checkForAbort_(simpleSegment.requestId)) {
48356      return;
48357    }
48358    if (this.checkForIllegalMediaSwitch(trackInfo)) {
48359      return;
48360    }
48361    trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
48362    // Guard against cases where we're not getting track info at all until we are
48363    // certain that all streams will provide it.
48364
48365    if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
48366      this.appendInitSegment_ = {
48367        audio: true,
48368        video: true,
48369      };
48370      this.startingMediaInfo_ = trackInfo;
48371      this.currentMediaInfo_ = trackInfo;
48372      this.logger_('trackinfo update', trackInfo);
48373      this.trigger('trackinfo');
48374    } // trackinfo may cause an abort if the trackinfo
48375    // causes a codec change to an unsupported codec.
48376
48377    if (this.checkForAbort_(simpleSegment.requestId)) {
48378      return;
48379    } // set trackinfo on the pending segment so that
48380    // it can append.
48381
48382    this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
48383
48384    if (this.hasEnoughInfoToAppend_()) {
48385      this.processCallQueue_();
48386    } else {
48387      checkAndFixTimelines(this);
48388    }
48389  }
48390  handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
48391    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48392    if (this.checkForAbort_(simpleSegment.requestId)) {
48393      return;
48394    }
48395    const segmentInfo = this.pendingSegment_;
48396    const timingInfoProperty = timingInfoPropertyForMedia(mediaType);
48397    segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
48398    segmentInfo[timingInfoProperty][timeType] = time;
48399    this.logger_(`timinginfo: ${mediaType} - ${timeType} - ${time}`); // check if any calls were waiting on the timing info
48400
48401    if (this.hasEnoughInfoToAppend_()) {
48402      this.processCallQueue_();
48403    } else {
48404      checkAndFixTimelines(this);
48405    }
48406  }
48407  handleCaptions_(simpleSegment, captionData) {
48408    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48409    if (this.checkForAbort_(simpleSegment.requestId)) {
48410      return;
48411    } // This could only happen with fmp4 segments, but
48412    // should still not happen in general
48413
48414    if (captionData.length === 0) {
48415      this.logger_('SegmentLoader received no captions from a caption event');
48416      return;
48417    }
48418    const segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
48419    // can be adjusted by the timestamp offset
48420
48421    if (!segmentInfo.hasAppendedData_) {
48422      this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
48423      return;
48424    }
48425    const timestampOffset =
48426      this.sourceUpdater_.videoTimestampOffset() === null
48427        ? this.sourceUpdater_.audioTimestampOffset()
48428        : this.sourceUpdater_.videoTimestampOffset();
48429    const captionTracks = {}; // get total start/end and captions for each track/stream
48430
48431    captionData.forEach((caption) => {
48432      // caption.stream is actually a track name...
48433      // set to the existing values in tracks or default values
48434      captionTracks[caption.stream] = captionTracks[caption.stream] || {
48435        // Infinity, as any other value will be less than this
48436        startTime: Infinity,
48437        captions: [],
48438        // 0 as an other value will be more than this
48439        endTime: 0,
48440      };
48441      const captionTrack = captionTracks[caption.stream];
48442      captionTrack.startTime = Math.min(
48443        captionTrack.startTime,
48444        caption.startTime + timestampOffset
48445      );
48446      captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
48447      captionTrack.captions.push(caption);
48448    });
48449    Object.keys(captionTracks).forEach((trackName) => {
48450      const { startTime, endTime, captions } = captionTracks[trackName];
48451      const inbandTextTracks = this.inbandTextTracks_;
48452      this.logger_(`adding cues from ${startTime} -> ${endTime} for ${trackName}`);
48453      createCaptionsTrackIfNotExists(inbandTextTracks, this.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
48454      // We do this because a rendition change that also changes the timescale for captions
48455      // will result in captions being re-parsed for certain segments. If we add them again
48456      // without clearing we will have two of the same captions visible.
48457
48458      removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
48459      addCaptionData({
48460        captionArray: captions,
48461        inbandTextTracks,
48462        timestampOffset,
48463      });
48464    }); // Reset stored captions since we added parsed
48465    // captions to a text track at this point
48466
48467    if (this.transmuxer_) {
48468      this.transmuxer_.postMessage({
48469        action: 'clearParsedMp4Captions',
48470      });
48471    }
48472  }
48473  handleId3_(simpleSegment, id3Frames, dispatchType) {
48474    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48475    if (this.checkForAbort_(simpleSegment.requestId)) {
48476      return;
48477    }
48478    const segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
48479
48480    if (!segmentInfo.hasAppendedData_) {
48481      this.metadataQueue_.id3.push(
48482        this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType)
48483      );
48484      return;
48485    }
48486    this.addMetadataToTextTrack(dispatchType, id3Frames, this.duration_());
48487  }
48488  processMetadataQueue_() {
48489    this.metadataQueue_.id3.forEach((fn) => fn());
48490    this.metadataQueue_.caption.forEach((fn) => fn());
48491    this.metadataQueue_.id3 = [];
48492    this.metadataQueue_.caption = [];
48493  }
48494  processCallQueue_() {
48495    const callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
48496    // functions may check the length of the load queue and default to pushing themselves
48497    // back onto the queue.
48498
48499    this.callQueue_ = [];
48500    callQueue.forEach((fun) => fun());
48501  }
48502  processLoadQueue_() {
48503    const loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
48504    // functions may check the length of the load queue and default to pushing themselves
48505    // back onto the queue.
48506
48507    this.loadQueue_ = [];
48508    loadQueue.forEach((fun) => fun());
48509  }
48510  /**
48511   * Determines whether the loader has enough info to load the next segment.
48512   *
48513   * @return {boolean}
48514   *         Whether or not the loader has enough info to load the next segment
48515   */
48516
48517  hasEnoughInfoToLoad_() {
48518    // Since primary timing goes by video, only the audio loader potentially needs to wait
48519    // to load.
48520    if (this.loaderType_ !== 'audio') {
48521      return true;
48522    }
48523    const segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
48524    // enough info to load.
48525
48526    if (!segmentInfo) {
48527      return false;
48528    } // The first segment can and should be loaded immediately so that source buffers are
48529    // created together (before appending). Source buffer creation uses the presence of
48530    // audio and video data to determine whether to create audio/video source buffers, and
48531    // uses processed (transmuxed or parsed) media to determine the types required.
48532
48533    if (!this.getCurrentMediaInfo_()) {
48534      return true;
48535    }
48536    if (
48537      // Technically, instead of waiting to load a segment on timeline changes, a segment
48538      // can be requested and downloaded and only wait before it is transmuxed or parsed.
48539      // But in practice, there are a few reasons why it is better to wait until a loader
48540      // is ready to append that segment before requesting and downloading:
48541      //
48542      // 1. Because audio and main loaders cross discontinuities together, if this loader
48543      //    is waiting for the other to catch up, then instead of requesting another
48544      //    segment and using up more bandwidth, by not yet loading, more bandwidth is
48545      //    allotted to the loader currently behind.
48546      // 2. media-segment-request doesn't have to have logic to consider whether a segment
48547      // is ready to be processed or not, isolating the queueing behavior to the loader.
48548      // 3. The audio loader bases some of its segment properties on timing information
48549      //    provided by the main loader, meaning that, if the logic for waiting on
48550      //    processing was in media-segment-request, then it would also need to know how
48551      //    to re-generate the segment information after the main loader caught up.
48552      shouldWaitForTimelineChange({
48553        timelineChangeController: this.timelineChangeController_,
48554        currentTimeline: this.currentTimeline_,
48555        segmentTimeline: segmentInfo.timeline,
48556        loaderType: this.loaderType_,
48557        audioDisabled: this.audioDisabled_,
48558      })
48559    ) {
48560      return false;
vendor: 8,605 bytes, lines 48561-48752
48561    }
48562    return true;
48563  }
48564  getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) {
48565    return (segmentInfo && segmentInfo.trackInfo) || this.currentMediaInfo_;
48566  }
48567  getMediaInfo_(segmentInfo = this.pendingSegment_) {
48568    return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
48569  }
48570  getPendingSegmentPlaylist() {
48571    return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
48572  }
48573  hasEnoughInfoToAppend_() {
48574    if (!this.sourceUpdater_.ready()) {
48575      return false;
48576    } // If content needs to be removed or the loader is waiting on an append reattempt,
48577    // then no additional content should be appended until the prior append is resolved.
48578
48579    if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
48580      return false;
48581    }
48582    const segmentInfo = this.pendingSegment_;
48583    const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
48584    // we do not have information on this specific
48585    // segment yet
48586
48587    if (!segmentInfo || !trackInfo) {
48588      return false;
48589    }
48590    const { hasAudio, hasVideo, isMuxed } = trackInfo;
48591    if (hasVideo && !segmentInfo.videoTimingInfo) {
48592      return false;
48593    } // muxed content only relies on video timing information for now.
48594
48595    if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
48596      return false;
48597    } // we need to allow an append here even if we're moving to different timelines.
48598
48599    if (
48600      shouldWaitForTimelineChange({
48601        timelineChangeController: this.timelineChangeController_,
48602        currentTimeline: this.currentTimeline_,
48603        segmentTimeline: segmentInfo.timeline,
48604        loaderType: this.loaderType_,
48605        audioDisabled: this.audioDisabled_,
48606      })
48607    ) {
48608      return false;
48609    }
48610    return true;
48611  }
48612  handleData_(simpleSegment, result) {
48613    this.earlyAbortWhenNeeded_(simpleSegment.stats);
48614    if (this.checkForAbort_(simpleSegment.requestId)) {
48615      return;
48616    } // If there's anything in the call queue, then this data came later and should be
48617    // executed after the calls currently queued.
48618
48619    if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
48620      checkAndFixTimelines(this);
48621      this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
48622      return;
48623    }
48624    const segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
48625
48626    this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
48627
48628    this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
48629    // logic may change behavior depending on the state, and changing state too early may
48630    // inflate our estimates of bandwidth. In the future this should be re-examined to
48631    // note more granular states.
48632    // don't process and append data if the mediaSource is closed
48633
48634    if (this.mediaSource_.readyState === 'closed') {
48635      return;
48636    } // if this request included an initialization segment, save that data
48637    // to the initSegment cache
48638
48639    if (simpleSegment.map) {
48640      simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
48641
48642      segmentInfo.segment.map = simpleSegment.map;
48643    } // if this request included a segment key, save that data in the cache
48644
48645    if (simpleSegment.key) {
48646      this.segmentKey(simpleSegment.key, true);
48647    }
48648    segmentInfo.isFmp4 = simpleSegment.isFmp4;
48649    segmentInfo.timingInfo = segmentInfo.timingInfo || {};
48650    if (segmentInfo.isFmp4) {
48651      this.trigger('fmp4');
48652      segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
48653    } else {
48654      const trackInfo = this.getCurrentMediaInfo_();
48655      const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
48656      let firstVideoFrameTimeForData;
48657      if (useVideoTimingInfo) {
48658        firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
48659      } // Segment loader knows more about segment timing than the transmuxer (in certain
48660      // aspects), so make any changes required for a more accurate start time.
48661      // Don't set the end time yet, as the segment may not be finished processing.
48662
48663      segmentInfo.timingInfo.start = this.trueSegmentStart_({
48664        currentStart: segmentInfo.timingInfo.start,
48665        playlist: segmentInfo.playlist,
48666        mediaIndex: segmentInfo.mediaIndex,
48667        currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
48668        useVideoTimingInfo,
48669        firstVideoFrameTimeForData,
48670        videoTimingInfo: segmentInfo.videoTimingInfo,
48671        audioTimingInfo: segmentInfo.audioTimingInfo,
48672      });
48673    } // Init segments for audio and video only need to be appended in certain cases. Now
48674    // that data is about to be appended, we can check the final cases to determine
48675    // whether we should append an init segment.
48676
48677    this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
48678    // as we use the start of the segment to offset the best guess (playlist provided)
48679    // timestamp offset.
48680
48681    this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
48682    // be appended or not.
48683
48684    if (segmentInfo.isSyncRequest) {
48685      // first save/update our timing info for this segment.
48686      // this is what allows us to choose an accurate segment
48687      // and the main reason we make a sync request.
48688      this.updateTimingInfoEnd_(segmentInfo);
48689      this.syncController_.saveSegmentTimingInfo({
48690        segmentInfo,
48691        shouldSaveTimelineMapping: this.loaderType_ === 'main',
48692      });
48693      const next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
48694      // after taking into account its timing info, do not append it.
48695
48696      if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
48697        this.logger_('sync segment was incorrect, not appending');
48698        return;
48699      } // otherwise append it like any other segment as our guess was correct.
48700
48701      this.logger_('sync segment was correct, appending');
48702    } // Save some state so that in the future anything waiting on first append (and/or
48703    // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
48704    // we need some notion of whether the timestamp offset or other relevant information
48705    // has had a chance to be set.
48706
48707    segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
48708
48709    this.processMetadataQueue_();
48710    this.appendData_(segmentInfo, result);
48711  }
48712  updateAppendInitSegmentStatus(segmentInfo, type) {
48713    // alt audio doesn't manage timestamp offset
48714    if (
48715      this.loaderType_ === 'main' &&
48716      typeof segmentInfo.timestampOffset === 'number' &&
48717      // in the case that we're handling partial data, we don't want to append an init
48718      // segment for each chunk
48719      !segmentInfo.changedTimestampOffset
48720    ) {
48721      // if the timestamp offset changed, the timeline may have changed, so we have to re-
48722      // append init segments
48723      this.appendInitSegment_ = {
48724        audio: true,
48725        video: true,
48726      };
48727    }
48728    if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
48729      // make sure we append init segment on playlist changes, in case the media config
48730      // changed
48731      this.appendInitSegment_[type] = true;
48732    }
48733  }
48734  getInitSegmentAndUpdateState_({ type, initSegment, map, playlist }) {
48735    // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
48736    // (Section 3) required to parse the applicable Media Segments.  It applies to every
48737    // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
48738    // or until the end of the playlist."
48739    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
48740    if (map) {
48741      const id = initSegmentId(map);
48742      if (this.activeInitSegmentId_ === id) {
48743        // don't need to re-append the init segment if the ID matches
48744        return null;
48745      } // a map-specified init segment takes priority over any transmuxed (or otherwise
48746      // obtained) init segment
48747      //
48748      // this also caches the init segment for later use
48749
48750      initSegment = this.initSegmentForMap(map, true).bytes;
48751      this.activeInitSegmentId_ = id;
48752    }
vendor: 6,867 bytes, lines 48752-48899
48752 // We used to always prepend init segments for video, however, that shouldn't be
48753    // necessary. Instead, we should only append on changes, similar to what we've always
48754    // done for audio. This is more important (though may not be that important) for
48755    // frame-by-frame appending for LHLS, simply because of the increased quantity of
48756    // appends.
48757
48758    if (initSegment && this.appendInitSegment_[type]) {
48759      // Make sure we track the playlist that we last used for the init segment, so that
48760      // we can re-append the init segment in the event that we get data from a new
48761      // playlist. Discontinuities and track changes are handled in other sections.
48762      this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
48763
48764      this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
48765      // we are appending the muxer init segment
48766
48767      this.activeInitSegmentId_ = null;
48768      return initSegment;
48769    }
48770    return null;
48771  }
48772  handleQuotaExceededError_({ segmentInfo, type, bytes }, error) {
48773    const audioBuffered = this.sourceUpdater_.audioBuffered();
48774    const videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
48775    // should be cleared out during the buffer removals. However, log in case it helps
48776    // debug.
48777
48778    if (audioBuffered.length > 1) {
48779      this.logger_(
48780        'On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' +
48781          timeRangesToArray(audioBuffered).join(', ')
48782      );
48783    }
48784    if (videoBuffered.length > 1) {
48785      this.logger_(
48786        'On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' +
48787          timeRangesToArray(videoBuffered).join(', ')
48788      );
48789    }
48790    const audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
48791    const audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
48792    const videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
48793    const videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
48794    if (
48795      audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER &&
48796      videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER
48797    ) {
48798      // Can't remove enough buffer to make room for new segment (or the browser doesn't
48799      // allow for appends of segments this size). In the future, it may be possible to
48800      // split up the segment and append in pieces, but for now, error out this playlist
48801      // in an attempt to switch to a more manageable rendition.
48802      this.logger_(
48803        'On QUOTA_EXCEEDED_ERR, single segment too large to append to ' +
48804          'buffer, triggering an error. ' +
48805          `Appended byte length: ${bytes.byteLength}, ` +
48806          `audio buffer: ${timeRangesToArray(audioBuffered).join(', ')}, ` +
48807          `video buffer: ${timeRangesToArray(videoBuffered).join(', ')}, `
48808      );
48809      this.error({
48810        message: 'Quota exceeded error with append of a single segment of content',
48811        excludeUntil: Infinity,
48812      });
48813      this.trigger('error');
48814      return;
48815    } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
48816    // that the segment-loader should block on future events until this one is handled, so
48817    // that it doesn't keep moving onto further segments. Adding the call to the call
48818    // queue will prevent further appends until waitingOnRemove_ and
48819    // quotaExceededErrorRetryTimeout_ are cleared.
48820    //
48821    // Note that this will only block the current loader. In the case of demuxed content,
48822    // the other load may keep filling as fast as possible. In practice, this should be
48823    // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
48824    // source buffer, or video fills without enough room for audio to append (and without
48825    // the availability of clearing out seconds of back buffer to make room for audio).
48826    // But it might still be good to handle this case in the future as a TODO.
48827
48828    this.waitingOnRemove_ = true;
48829    this.callQueue_.push(
48830      this.appendToSourceBuffer_.bind(this, {
48831        segmentInfo,
48832        type,
48833        bytes,
48834      })
48835    );
48836    const currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
48837    // before retrying.
48838
48839    const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
48840    this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`);
48841    this.remove(
48842      0,
48843      timeToRemoveUntil,
48844      () => {
48845        this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`);
48846        this.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
48847        // attempts (since we can't clear less than the minimum)
48848
48849        this.quotaExceededErrorRetryTimeout_ = window__default['default'].setTimeout(() => {
48850          this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
48851          this.quotaExceededErrorRetryTimeout_ = null;
48852          this.processCallQueue_();
48853        }, MIN_BACK_BUFFER * 1000);
48854      },
48855      true
48856    );
48857  }
48858  handleAppendError_({ segmentInfo, type, bytes }, error) {
48859    // if there's no error, nothing to do
48860    if (!error) {
48861      return;
48862    }
48863    if (error.code === QUOTA_EXCEEDED_ERR) {
48864      this.handleQuotaExceededError_({
48865        segmentInfo,
48866        type,
48867        bytes,
48868      }); // A quota exceeded error should be recoverable with a future re-append, so no need
48869      // to trigger an append error.
48870
48871      return;
48872    }
48873    this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error); // If an append errors, we often can't recover.
48874    // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
48875    //
48876    // Trigger a special error so that it can be handled separately from normal,
48877    // recoverable errors.
48878
48879    this.error({
48880      message:
48881        `${type} append of ${bytes.length}b failed for segment ` +
48882        `#${segmentInfo.mediaIndex} in playlist ${segmentInfo.playlist.id}`,
48883      metadata: {
48884        errorType: videojs.Error.StreamingFailedToAppendSegment,
48885      },
48886    });
48887    this.trigger('appenderror');
48888  }
48889  appendToSourceBuffer_({ segmentInfo, type, initSegment, data, bytes }) {
48890    // If this is a re-append, bytes were already created and don't need to be recreated
48891    if (!bytes) {
48892      const segments = [data];
48893      let byteLength = data.byteLength;
48894      if (initSegment) {
48895        // if the media initialization segment is changing, append it before the content
48896        // segment
48897        segments.unshift(initSegment);
48898        byteLength += initSegment.byteLength;
48899      } // Technically we should be OK appending the init segment separately, however, we
48900      // haven't yet tested that, and prepending is how we have always done things.
48901
48902      bytes = concatSegments({
48903        bytes: byteLength,
48904        segments,
48905      });
48906    }
48907    const metadata = {
48908      segmentInfo: segmentInfoPayload({
48909        type: this.loaderType_,
48910        segment: segmentInfo,
48911      }),
48912    };
48913    this.trigger({
48914      type: 'segmentappendstart',
48915      metadata,
48916    });
48917    this.sourceUpdater_.appendBuffer(
48918      {
48919        segmentInfo,
48920        type,
48921        bytes,
48922      },
48923      this.handleAppendError_.bind(this, {
48924        segmentInfo,
48925        type,
48926        bytes,
48927      })
48928    );
48929  }
48930  handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
48931    if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
48932      return;
48933    }
48934    const segment = this.pendingSegment_.segment;
48935    const timingInfoProperty = `${type}TimingInfo`;
48936    if (!segment[timingInfoProperty]) {
48937      segment[timingInfoProperty] = {};
48938    }
48939    segment[timingInfoProperty].transmuxerPrependedSeconds =
48940      segmentTimingInfo.prependedContentDuration || 0;
48941    segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
48942    segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
48943    segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
48944    segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
48945
48946    segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
48947  }
48948  appendData_(segmentInfo, result) {
48949    const { type, data } = result;
48950    if (!data || !data.byteLength) {
48951      return;
48952    }
48953    if (type === 'audio' && this.audioDisabled_) {
48954      return;
48955    }
48956    const initSegment = this.getInitSegmentAndUpdateState_({
48957      type,
48958      initSegment: result.initSegment,
48959      playlist: segmentInfo.playlist,
48960      map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null,
48961    });
48962    this.appendToSourceBuffer_({
48963      segmentInfo,
48964      type,
48965      initSegment,
48966      data,
48967    });
48968  }
48969  /**
48970   * load a specific segment from a request into the buffer
48971   *
48972   * @private
48973   */
48974
48975  loadSegment_(segmentInfo) {
48976    this.state = 'WAITING';
48977    this.pendingSegment_ = segmentInfo;
48978    this.trimBackBuffer_(segmentInfo);
48979    if (typeof segmentInfo.timestampOffset === 'number') {
48980      if (this.transmuxer_) {
48981        this.transmuxer_.postMessage({
48982          action: 'clearAllMp4Captions',
48983        });
48984      }
48985    }
48986    if (!this.hasEnoughInfoToLoad_()) {
48987      checkAndFixTimelines(this);
48988      this.loadQueue_.push(() => {
48989        // regenerate the audioAppendStart, timestampOffset, etc as they
48990        // may have changed since this function was added to the queue.
48991        const options = _extends__default['default']({}, segmentInfo, {
48992          forceTimestampOffset: true,
48993        });
48994        _extends__default['default'](segmentInfo, this.generateSegmentInfo_(options));
48995        this.isPendingTimestampOffset_ = false;
48996        this.updateTransmuxerAndRequestSegment_(segmentInfo);
48997      });
48998      return;
48999    }
49000    this.updateTransmuxerAndRequestSegment_(segmentInfo);
49001  }
49002  updateTransmuxerAndRequestSegment_(segmentInfo) {
49003    // We'll update the source buffer's timestamp offset once we have transmuxed data, but
49004    // the transmuxer still needs to be updated before then.
49005    //
49006    // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
49007    // offset must be passed to the transmuxer for stream correcting adjustments.
49008    if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
49009      this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
49010
49011      segmentInfo.gopsToAlignWith = [];
49012      this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
49013
49014      this.transmuxer_.postMessage({
49015        action: 'reset',
49016      });
49017      this.transmuxer_.postMessage({
49018        action: 'setTimestampOffset',
49019        timestampOffset: segmentInfo.timestampOffset,
49020      });
49021    }
49022    const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
49023    const isEndOfStream = this.isEndOfStream_(
49024      segmentInfo.mediaIndex,
49025      segmentInfo.playlist,
49026      segmentInfo.partIndex
49027    );
49028    const isWalkingForward = this.mediaIndex !== null;
49029    const isDiscontinuity =
49030      segmentInfo.timeline !== this.currentTimeline_ &&
49031      // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
49032      // the first timeline
49033      segmentInfo.timeline > 0;
49034    const isEndOfTimeline = isEndOfStream || (isWalkingForward && isDiscontinuity);
49035    this.logger_(`Requesting
49036${compactSegmentUrlDescription(segmentInfo.uri)}
49037${segmentInfoString(segmentInfo)}`); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
49038    // then this init segment has never been seen before and should be appended.
49039    //
49040    // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
49041    // both to true and leave the decision of whether to append the init segment to append time.
49042
49043    if (simpleSegment.map && !simpleSegment.map.bytes) {
49044      this.logger_('going to request init segment.');
49045      this.appendInitSegment_ = {
49046        video: true,
49047        audio: true,
49048      };
49049    }
49050    segmentInfo.abortRequests = mediaSegmentRequest({
49051      xhr: this.vhs_.xhr,
49052      xhrOptions: this.xhrOptions_,
49053      decryptionWorker: this.decrypter_,
49054      segment: simpleSegment,
49055      abortFn: this.handleAbort_.bind(this, segmentInfo),
49056      progressFn: this.handleProgress_.bind(this),
49057      trackInfoFn: this.handleTrackInfo_.bind(this),
49058      timingInfoFn: this.handleTimingInfo_.bind(this),
49059      videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(
49060        this,
49061        'video',
49062        segmentInfo.requestId
49063      ),
49064      audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(
49065        this,
49066        'audio',
49067        segmentInfo.requestId
49068      ),
49069      captionsFn: this.handleCaptions_.bind(this),
49070      isEndOfTimeline,
49071      endedTimelineFn: () => {
49072        this.logger_('received endedtimeline callback');
49073      },
49074      id3Fn: this.handleId3_.bind(this),
49075      dataFn: this.handleData_.bind(this),
49076      doneFn: this.segmentRequestFinished_.bind(this),
49077      onTransmuxerLog: ({ message, level, stream }) => {
49078        this.logger_(
49079          `${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`
49080        );
49081      },
49082      triggerSegmentEventFn: ({ type, segment, keyInfo, trackInfo, timingInfo }) => {
49083        const segInfo = segmentInfoPayload({
49084          segment,
49085        });
49086        const metadata = {
49087          segmentInfo: segInfo,
49088        }; // add other properties if necessary.
49089
49090        if (keyInfo) {
49091          metadata.keyInfo = keyInfo;
49092        }
49093        if (trackInfo) {
49094          metadata.trackInfo = trackInfo;
49095        }
49096        if (timingInfo) {
49097          metadata.timingInfo = timingInfo;
49098        }
49099        this.trigger({
49100          type,
49101          metadata,
49102        });
49103      },
49104    });
49105  }
49106  /**
49107   * trim the back buffer so that we don't have too much data
49108   * in the source buffer
49109   *
49110   * @private
49111   *
49112   * @param {Object} segmentInfo - the current segment
49113   */
49114
49115  trimBackBuffer_(segmentInfo) {
49116    const removeToTime = safeBackBufferTrimTime(
49117      this.seekable_(),
49118      this.currentTime_(),
49119      this.playlist_.targetDuration || 10
49120    ); // Chrome has a hard limit of 150MB of
49121    // buffer and a very conservative "garbage collector"
49122    // We manually clear out the old buffer to ensure
49123    // we don't trigger the QuotaExceeded error
49124    // on the source buffer during subsequent appends
49125
49126    if (removeToTime > 0) {
49127      this.remove(0, removeToTime);
49128    }
49129  }
49130  /**
49131   * created a simplified copy of the segment object with just the
49132   * information necessary to perform the XHR and decryption
49133   *
49134   * @private
49135   *
49136   * @param {Object} segmentInfo - the current segment
49137   * @return {Object} a simplified segment object copy
49138   */
49139
49140  createSimplifiedSegmentObj_(segmentInfo) {
49141    const segment = segmentInfo.segment;
49142    const part = segmentInfo.part;
49143    const isEncrypted =
49144      segmentInfo.segment.key || (segmentInfo.segment.map && segmentInfo.segment.map.key);
49145    const isMediaInitialization = segmentInfo.segment.map && !segmentInfo.segment.map.bytes;
49146    const simpleSegment = {
49147      resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
49148      byterange: part ? part.byterange : segment.byterange,
49149      requestId: segmentInfo.requestId,
49150      transmuxer: segmentInfo.transmuxer,
49151      audioAppendStart: segmentInfo.audioAppendStart,
49152      gopsToAlignWith: segmentInfo.gopsToAlignWith,
49153      part: segmentInfo.part,
49154      type: this.loaderType_,
49155      start: segmentInfo.startOfSegment,
49156      duration: segmentInfo.duration,
49157      isEncrypted,
49158      isMediaInitialization,
49159    };
49160    const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
49161    if (previousSegment && previousSegment.timeline === segment.timeline) {
49162      // The baseStartTime of a segment is used to handle rollover when probing the TS
49163      // segment to retrieve timing information. Since the probe only looks at the media's
49164      // times (e.g., PTS and DTS values of the segment), and doesn't consider the
49165      // player's time (e.g., player.currentTime()), baseStartTime should reflect the
49166      // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
49167      // seconds of media time, so should be used here. The previous segment is used since
49168      // the end of the previous segment should represent the beginning of the current
49169      // segment, so long as they are on the same timeline.
49170      if (previousSegment.videoTimingInfo) {
49171        simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
49172      } else if (previousSegment.audioTimingInfo) {
49173        simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
49174      }
49175    }
49176    if (segment.key) {
49177      // if the media sequence is greater than 2^32, the IV will be incorrect
49178      // assuming 10s segments, that would be about 1300 years
49179      const iv =
49180        segment.key.iv ||
49181        new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
49182      simpleSegment.key = this.segmentKey(segment.key);
49183      simpleSegment.key.iv = iv;
49184    }
49185    if (segment.map) {
49186      simpleSegment.map = this.initSegmentForMap(segment.map);
49187    }
49188    return simpleSegment;
49189  }
49190  saveTransferStats_(stats) {
49191    // every request counts as a media request even if it has been aborted
49192    // or canceled due to a timeout
49193    this.mediaRequests += 1;
49194    if (stats) {
49195      this.mediaBytesTransferred += stats.bytesReceived;
49196      this.mediaTransferDuration += stats.roundTripTime;
49197    }
49198  }
49199  saveBandwidthRelatedStats_(duration, stats) {
49200    // byteLength will be used for throughput, and should be based on bytes receieved,
49201    // which we only know at the end of the request and should reflect total bytes
49202    // downloaded rather than just bytes processed from components of the segment
49203    this.pendingSegment_.byteLength = stats.bytesReceived;
49204    if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
49205      this.logger_(
49206        `Ignoring segment's bandwidth because its duration of ${duration}` +
49207          ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`
49208      );
49209      return;
49210    }
49211    const metadata = {
49212      bandwidthInfo: {
49213        from: this.bandwidth,
vendor: 17,992 bytes, lines 49214-49669
49214        to: stats.bandwidth,
49215      },
49216    }; // player event with payload
49217
49218    this.trigger({
49219      type: 'bandwidthupdated',
49220      metadata,
49221    });
49222    this.bandwidth = stats.bandwidth;
49223    this.roundTrip = stats.roundTripTime;
49224  }
49225  handleTimeout_() {
49226    // although the VTT segment loader bandwidth isn't really used, it's good to
49227    // maintain functinality between segment loaders
49228    this.mediaRequestsTimedout += 1;
49229    this.bandwidth = 1;
49230    this.roundTrip = NaN;
49231    this.trigger('bandwidthupdate');
49232    this.trigger('timeout');
49233  }
49234  /**
49235   * Handle the callback from the segmentRequest function and set the
49236   * associated SegmentLoader state and errors if necessary
49237   *
49238   * @private
49239   */
49240
49241  segmentRequestFinished_(error, simpleSegment, result) {
49242    // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
49243    // check the call queue directly since this function doesn't need to deal with any
49244    // data, and can continue even if the source buffers are not set up and we didn't get
49245    // any data from the segment
49246    if (this.callQueue_.length) {
49247      this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
49248      return;
49249    }
49250    this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
49251
49252    if (!this.pendingSegment_) {
49253      return;
49254    } // the request was aborted and the SegmentLoader has already started
49255    // another request. this can happen when the timeout for an aborted
49256    // request triggers due to a limitation in the XHR library
49257    // do not count this as any sort of request or we risk double-counting
49258
49259    if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
49260      return;
49261    } // an error occurred from the active pendingSegment_ so reset everything
49262
49263    if (error) {
49264      this.pendingSegment_ = null;
49265      this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
49266
49267      if (error.code === REQUEST_ERRORS.ABORTED) {
49268        return;
49269      }
49270      this.pause(); // the error is really just that at least one of the requests timed-out
49271      // set the bandwidth to a very low value and trigger an ABR switch to
49272      // take emergency action
49273
49274      if (error.code === REQUEST_ERRORS.TIMEOUT) {
49275        this.handleTimeout_();
49276        return;
49277      } // if control-flow has arrived here, then the error is real
49278      // emit an error event to exclude the current playlist
49279
49280      this.mediaRequestsErrored += 1;
49281      this.error(error);
49282      this.trigger('error');
49283      return;
49284    }
49285    const segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
49286    // generated for ABR purposes
49287
49288    this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
49289    segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
49290    if (result.gopInfo) {
49291      this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
49292    } // Although we may have already started appending on progress, we shouldn't switch the
49293    // state away from loading until we are officially done loading the segment data.
49294
49295    this.state = 'APPENDING'; // used for testing
49296
49297    this.trigger('appending');
49298    this.waitForAppendsToComplete_(segmentInfo);
49299  }
49300  setTimeMapping_(timeline) {
49301    const timelineMapping = this.syncController_.mappingForTimeline(timeline);
49302    if (timelineMapping !== null) {
49303      this.timeMapping_ = timelineMapping;
49304    }
49305  }
49306  updateMediaSecondsLoaded_(segment) {
49307    if (typeof segment.start === 'number' && typeof segment.end === 'number') {
49308      this.mediaSecondsLoaded += segment.end - segment.start;
49309    } else {
49310      this.mediaSecondsLoaded += segment.duration;
49311    }
49312  }
49313  shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
49314    if (timestampOffset === null) {
49315      return false;
49316    } // note that we're potentially using the same timestamp offset for both video and
49317    // audio
49318
49319    if (
49320      this.loaderType_ === 'main' &&
49321      timestampOffset !== this.sourceUpdater_.videoTimestampOffset()
49322    ) {
49323      return true;
49324    }
49325    if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
49326      return true;
49327    }
49328    return false;
49329  }
49330  trueSegmentStart_({
49331    currentStart,
49332    playlist,
49333    mediaIndex,
49334    firstVideoFrameTimeForData,
49335    currentVideoTimestampOffset,
49336    useVideoTimingInfo,
49337    videoTimingInfo,
49338    audioTimingInfo,
49339  }) {
49340    if (typeof currentStart !== 'undefined') {
49341      // if start was set once, keep using it
49342      return currentStart;
49343    }
49344    if (!useVideoTimingInfo) {
49345      return audioTimingInfo.start;
49346    }
49347    const previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
49348    // within that segment. Since the transmuxer maintains a cache of incomplete data
49349    // from and/or the last frame seen, the start time may reflect a frame that starts
49350    // in the previous segment. Check for that case and ensure the start time is
49351    // accurate for the segment.
49352
49353    if (
49354      mediaIndex === 0 ||
49355      !previousSegment ||
49356      typeof previousSegment.start === 'undefined' ||
49357      previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset
49358    ) {
49359      return firstVideoFrameTimeForData;
49360    }
49361    return videoTimingInfo.start;
49362  }
49363  waitForAppendsToComplete_(segmentInfo) {
49364    const trackInfo = this.getCurrentMediaInfo_(segmentInfo);
49365    if (!trackInfo) {
49366      this.error({
49367        message: 'No starting media returned, likely due to an unsupported media format.',
49368        playlistExclusionDuration: Infinity,
49369      });
49370      this.trigger('error');
49371      return;
49372    } // Although transmuxing is done, appends may not yet be finished. Throw a marker
49373    // on each queue this loader is responsible for to ensure that the appends are
49374    // complete.
49375
49376    const { hasAudio, hasVideo, isMuxed } = trackInfo;
49377    const waitForVideo = this.loaderType_ === 'main' && hasVideo;
49378    const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
49379    segmentInfo.waitingOnAppends = 0; // segments with no data
49380
49381    if (!segmentInfo.hasAppendedData_) {
49382      if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
49383        // When there's no audio or video data in the segment, there's no audio or video
49384        // timing information.
49385        //
49386        // If there's no audio or video timing information, then the timestamp offset
49387        // can't be adjusted to the appropriate value for the transmuxer and source
49388        // buffers.
49389        //
49390        // Therefore, the next segment should be used to set the timestamp offset.
49391        this.isPendingTimestampOffset_ = true;
49392      } // override settings for metadata only segments
49393
49394      segmentInfo.timingInfo = {
49395        start: 0,
49396      };
49397      segmentInfo.waitingOnAppends++;
49398      if (!this.isPendingTimestampOffset_) {
49399        // update the timestampoffset
49400        this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
49401        // no video/audio data.
49402
49403        this.processMetadataQueue_();
49404      } // append is "done" instantly with no data.
49405
49406      this.checkAppendsDone_(segmentInfo);
49407      return;
49408    } // Since source updater could call back synchronously, do the increments first.
49409
49410    if (waitForVideo) {
49411      segmentInfo.waitingOnAppends++;
49412    }
49413    if (waitForAudio) {
49414      segmentInfo.waitingOnAppends++;
49415    }
49416    if (waitForVideo) {
49417      this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
49418    }
49419    if (waitForAudio) {
49420      this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
49421    }
49422  }
49423  checkAppendsDone_(segmentInfo) {
49424    if (this.checkForAbort_(segmentInfo.requestId)) {
49425      return;
49426    }
49427    segmentInfo.waitingOnAppends--;
49428    if (segmentInfo.waitingOnAppends === 0) {
49429      this.handleAppendsDone_();
49430    }
49431  }
49432  checkForIllegalMediaSwitch(trackInfo) {
49433    const illegalMediaSwitchError = illegalMediaSwitch(
49434      this.loaderType_,
49435      this.getCurrentMediaInfo_(),
49436      trackInfo
49437    );
49438    if (illegalMediaSwitchError) {
49439      this.error({
49440        message: illegalMediaSwitchError,
49441        playlistExclusionDuration: Infinity,
49442      });
49443      this.trigger('error');
49444      return true;
49445    }
49446    return false;
49447  }
49448  updateSourceBufferTimestampOffset_(segmentInfo) {
49449    if (
49450      segmentInfo.timestampOffset === null ||
49451      // we don't yet have the start for whatever media type (video or audio) has
49452      // priority, timing-wise, so we must wait
49453      typeof segmentInfo.timingInfo.start !== 'number' ||
49454      // already updated the timestamp offset for this segment
49455      segmentInfo.changedTimestampOffset ||
49456      // the alt audio loader should not be responsible for setting the timestamp offset
49457      this.loaderType_ !== 'main'
49458    ) {
49459      return;
49460    }
49461    let didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
49462    // the timing info here comes from video. In the event that the audio is longer than
49463    // the video, this will trim the start of the audio.
49464    // This also trims any offset from 0 at the beginning of the media
49465
49466    segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
49467      videoTimingInfo: segmentInfo.segment.videoTimingInfo,
49468      audioTimingInfo: segmentInfo.segment.audioTimingInfo,
49469      timingInfo: segmentInfo.timingInfo,
49470    }); // In the event that there are part segment downloads, each will try to update the
49471    // timestamp offset. Retaining this bit of state prevents us from updating in the
49472    // future (within the same segment), however, there may be a better way to handle it.
49473
49474    segmentInfo.changedTimestampOffset = true;
49475    if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
49476      this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
49477      didChange = true;
49478    }
49479    if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
49480      this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
49481      didChange = true;
49482    }
49483    if (didChange) {
49484      this.trigger('timestampoffset');
49485    }
49486  }
49487  getSegmentStartTimeForTimestampOffsetCalculation_({
49488    videoTimingInfo,
49489    audioTimingInfo,
49490    timingInfo,
49491  }) {
49492    if (!this.useDtsForTimestampOffset_) {
49493      return timingInfo.start;
49494    }
49495    if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
49496      return videoTimingInfo.transmuxedDecodeStart;
49497    } // handle audio only
49498
49499    if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
49500      return audioTimingInfo.transmuxedDecodeStart;
49501    } // handle content not transmuxed (e.g., MP4)
49502
49503    return timingInfo.start;
49504  }
49505  updateTimingInfoEnd_(segmentInfo) {
49506    segmentInfo.timingInfo = segmentInfo.timingInfo || {};
49507    const trackInfo = this.getMediaInfo_();
49508    const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
49509    const prioritizedTimingInfo =
49510      useVideoTimingInfo && segmentInfo.videoTimingInfo
49511        ? segmentInfo.videoTimingInfo
49512        : segmentInfo.audioTimingInfo;
49513    if (!prioritizedTimingInfo) {
49514      return;
49515    }
49516    segmentInfo.timingInfo.end =
49517      typeof prioritizedTimingInfo.end === 'number'
49518        ? // End time may not exist in a case where we aren't parsing the full segment (one
49519          // current example is the case of fmp4), so use the rough duration to calculate an
49520          // end time.
49521          prioritizedTimingInfo.end
49522        : prioritizedTimingInfo.start + segmentInfo.duration;
49523  }
49524  /**
49525   * callback to run when appendBuffer is finished. detects if we are
49526   * in a good state to do things with the data we got, or if we need
49527   * to wait for more
49528   *
49529   * @private
49530   */
49531
49532  handleAppendsDone_() {
49533    // appendsdone can cause an abort
49534    if (this.pendingSegment_) {
49535      const metadata = {
49536        segmentInfo: segmentInfoPayload({
49537          type: this.loaderType_,
49538          segment: this.pendingSegment_,
49539        }),
49540      };
49541      this.trigger({
49542        type: 'appendsdone',
49543        metadata,
49544      });
49545    }
49546    if (!this.pendingSegment_) {
49547      this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
49548      // all appending cases?
49549
49550      if (!this.paused()) {
49551        this.monitorBuffer_();
49552      }
49553      return;
49554    }
49555    const segmentInfo = this.pendingSegment_;
49556    if (segmentInfo.part && segmentInfo.part.syncInfo) {
49557      // low-latency flow
49558      segmentInfo.part.syncInfo.markAppended();
49559    } else if (segmentInfo.segment.syncInfo) {
49560      // normal flow
49561      segmentInfo.segment.syncInfo.markAppended();
49562    } // Now that the end of the segment has been reached, we can set the end time. It's
49563    // best to wait until all appends are done so we're sure that the primary media is
49564    // finished (and we have its end time).
49565
49566    this.updateTimingInfoEnd_(segmentInfo);
49567    if (this.shouldSaveSegmentTimingInfo_) {
49568      // Timeline mappings should only be saved for the main loader. This is for multiple
49569      // reasons:
49570      //
49571      // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
49572      //    and the main loader try to save the timeline mapping, whichever comes later
49573      //    will overwrite the first. In theory this is OK, as the mappings should be the
49574      //    same, however, it breaks for (2)
49575      // 2) In the event of a live stream, the initial live point will make for a somewhat
49576      //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
49577      //    the mapping will be off for one of the streams, dependent on which one was
49578      //    first saved (see (1)).
49579      // 3) Primary timing goes by video in VHS, so the mapping should be video.
49580      //
49581      // Since the audio loader will wait for the main loader to load the first segment,
49582      // the main loader will save the first timeline mapping, and ensure that there won't
49583      // be a case where audio loads two segments without saving a mapping (thus leading
49584      // to missing segment timing info).
49585      this.syncController_.saveSegmentTimingInfo({
49586        segmentInfo,
49587        shouldSaveTimelineMapping: this.loaderType_ === 'main',
49588      });
49589    }
49590    const segmentDurationMessage = getTroublesomeSegmentDurationMessage(
49591      segmentInfo,
49592      this.sourceType_
49593    );
49594    if (segmentDurationMessage) {
49595      if (segmentDurationMessage.severity === 'warn') {
49596        videojs.log.warn(segmentDurationMessage.message);
49597      } else {
49598        this.logger_(segmentDurationMessage.message);
49599      }
49600    }
49601    this.recordThroughput_(segmentInfo);
49602    this.pendingSegment_ = null;
49603    this.state = 'READY';
49604    if (segmentInfo.isSyncRequest) {
49605      this.trigger('syncinfoupdate'); // if the sync request was not appended
49606      // then it was not the correct segment.
49607      // throw it away and use the data it gave us
49608      // to get the correct one.
49609
49610      if (!segmentInfo.hasAppendedData_) {
49611        this.logger_(`Throwing away un-appended sync request ${segmentInfoString(segmentInfo)}`);
49612        return;
49613      }
49614    }
49615    this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
49616    this.addSegmentMetadataCue_(segmentInfo);
49617    this.fetchAtBuffer_ = true;
49618    if (this.currentTimeline_ !== segmentInfo.timeline) {
49619      this.timelineChangeController_.lastTimelineChange({
49620        type: this.loaderType_,
49621        from: this.currentTimeline_,
49622        to: segmentInfo.timeline,
49623      }); // If audio is not disabled, the main segment loader is responsible for updating
49624      // the audio timeline as well. If the content is video only, this won't have any
49625      // impact.
49626
49627      if (this.loaderType_ === 'main' && !this.audioDisabled_) {
49628        this.timelineChangeController_.lastTimelineChange({
49629          type: 'audio',
49630          from: this.currentTimeline_,
49631          to: segmentInfo.timeline,
49632        });
49633      }
49634    }
49635    this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
49636    // the following conditional otherwise it may consider this a bad "guess"
49637    // and attempt to resync when the post-update seekable window and live
49638    // point would mean that this was the perfect segment to fetch
49639
49640    this.trigger('syncinfoupdate');
49641    const segment = segmentInfo.segment;
49642    const part = segmentInfo.part;
49643    const badSegmentGuess =
49644      segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
49645    const badPartGuess =
49646      part &&
49647      part.end &&
49648      this.currentTime_() - part.end > segmentInfo.playlist.partTargetDuration * 3; // If we previously appended a segment/part that ends more than 3 part/targetDurations before
49649    // the currentTime_ that means that our conservative guess was too conservative.
49650    // In that case, reset the loader state so that we try to use any information gained
49651    // from the previous request to create a new, more accurate, sync-point.
49652
49653    if (badSegmentGuess || badPartGuess) {
49654      this.logger_(`bad ${badSegmentGuess ? 'segment' : 'part'} ${segmentInfoString(segmentInfo)}`);
49655      this.resetEverything();
49656      return;
49657    }
49658    const isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
49659    // and conservatively guess
49660
49661    if (isWalkingForward) {
49662      this.trigger('bandwidthupdate');
49663    }
49664    this.trigger('progress');
49665    this.mediaIndex = segmentInfo.mediaIndex;
49666    this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
49667    // buffer, end the stream. this ensures the "ended" event will
49668    // fire if playback reaches that point.
49669
vendor: 4,772 bytes, lines 49670-49801
49670    if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
49671      this.endOfStream();
49672    } // used for testing
49673
49674    this.trigger('appended');
49675    if (segmentInfo.hasAppendedData_) {
49676      this.mediaAppends++;
49677    }
49678    if (!this.paused()) {
49679      this.monitorBuffer_();
49680    }
49681  }
49682  /**
49683   * Records the current throughput of the decrypt, transmux, and append
49684   * portion of the semgment pipeline. `throughput.rate` is a the cumulative
49685   * moving average of the throughput. `throughput.count` is the number of
49686   * data points in the average.
49687   *
49688   * @private
49689   * @param {Object} segmentInfo the object returned by loadSegment
49690   */
49691
49692  recordThroughput_(segmentInfo) {
49693    if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
49694      this.logger_(
49695        `Ignoring segment's throughput because its duration of ${segmentInfo.duration}` +
49696          ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`
49697      );
49698      return;
49699    }
49700    const rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
49701    // by zero in the case where the throughput is ridiculously high
49702
49703    const segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
49704
49705    const segmentProcessingThroughput = Math.floor(
49706      (segmentInfo.byteLength / segmentProcessingTime) * 8 * 1000
49707    ); // This is just a cumulative moving average calculation:
49708    //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
49709
49710    this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
49711  }
49712  /**
49713   * Adds a cue to the segment-metadata track with some metadata information about the
49714   * segment
49715   *
49716   * @private
49717   * @param {Object} segmentInfo
49718   *        the object returned by loadSegment
49719   * @method addSegmentMetadataCue_
49720   */
49721
49722  addSegmentMetadataCue_(segmentInfo) {
49723    if (!this.segmentMetadataTrack_) {
49724      return;
49725    }
49726    const segment = segmentInfo.segment;
49727    const start = segment.start;
49728    const end = segment.end; // Do not try adding the cue if the start and end times are invalid.
49729
49730    if (!finite(start) || !finite(end)) {
49731      return;
49732    }
49733    removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
49734    const Cue = window__default['default'].WebKitDataCue || window__default['default'].VTTCue;
49735    const value = {
49736      custom: segment.custom,
49737      dateTimeObject: segment.dateTimeObject,
49738      dateTimeString: segment.dateTimeString,
49739      programDateTime: segment.programDateTime,
49740      bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
49741      resolution: segmentInfo.playlist.attributes.RESOLUTION,
49742      codecs: segmentInfo.playlist.attributes.CODECS,
49743      byteLength: segmentInfo.byteLength,
49744      uri: segmentInfo.uri,
49745      timeline: segmentInfo.timeline,
49746      playlist: segmentInfo.playlist.id,
49747      start,
49748      end,
49749    };
49750    const data = JSON.stringify(value);
49751    const cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
49752    // the differences of WebKitDataCue in safari and VTTCue in other browsers
49753
49754    cue.value = value;
49755    this.segmentMetadataTrack_.addCue(cue);
49756  }
49757}
49758function noop() {}
49759const toTitleCase = function (string) {
49760  if (typeof string !== 'string') {
49761    return string;
49762  }
49763  return string.replace(/./, (w) => w.toUpperCase());
49764};
49765
49766/**
49767 * @file source-updater.js
49768 */
49769const bufferTypes = ['video', 'audio'];
49770const updating = (type, sourceUpdater) => {
49771  const sourceBuffer = sourceUpdater[`${type}Buffer`];
49772  return (sourceBuffer && sourceBuffer.updating) || sourceUpdater.queuePending[type];
49773};
49774const nextQueueIndexOfType = (type, queue) => {
49775  for (let i = 0; i < queue.length; i++) {
49776    const queueEntry = queue[i];
49777    if (queueEntry.type === 'mediaSource') {
49778      // If the next entry is a media source entry (uses multiple source buffers), block
49779      // processing to allow it to go through first.
49780      return null;
49781    }
49782    if (queueEntry.type === type) {
49783      return i;
49784    }
49785  }
49786  return null;
49787};
49788const shiftQueue = (type, sourceUpdater) => {
49789  if (sourceUpdater.queue.length === 0) {
49790    return;
49791  }
49792  let queueIndex = 0;
49793  let queueEntry = sourceUpdater.queue[queueIndex];
49794  if (queueEntry.type === 'mediaSource') {
49795    if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
49796      sourceUpdater.queue.shift();
49797      queueEntry.action(sourceUpdater);
49798      if (queueEntry.doneFn) {
49799        queueEntry.doneFn();
49800      } // Only specific source buffer actions must wait for async updateend events. Media
49801      // Source actions process synchronously. Therefore, both audio and video source
vendor: 4,943 bytes, lines 49802-49920
49802      // buffers are now clear to process the next queue entries.
49803
49804      shiftQueue('audio', sourceUpdater);
49805      shiftQueue('video', sourceUpdater);
49806    } // Media Source actions require both source buffers, so if the media source action
49807    // couldn't process yet (because one or both source buffers are busy), block other
49808    // queue actions until both are available and the media source action can process.
49809
49810    return;
49811  }
49812  if (type === 'mediaSource') {
49813    // If the queue was shifted by a media source action (this happens when pushing a
49814    // media source action onto the queue), then it wasn't from an updateend event from an
49815    // audio or video source buffer, so there's no change from previous state, and no
49816    // processing should be done.
49817    return;
49818  } // Media source queue entries don't need to consider whether the source updater is
49819  // started (i.e., source buffers are created) as they don't need the source buffers, but
49820  // source buffer queue entries do.
49821
49822  if (
49823    !sourceUpdater.ready() ||
49824    sourceUpdater.mediaSource.readyState === 'closed' ||
49825    updating(type, sourceUpdater)
49826  ) {
49827    return;
49828  }
49829  if (queueEntry.type !== type) {
49830    queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
49831    if (queueIndex === null) {
49832      // Either there's no queue entry that uses this source buffer type in the queue, or
49833      // there's a media source queue entry before the next entry of this type, in which
49834      // case wait for that action to process first.
49835      return;
49836    }
49837    queueEntry = sourceUpdater.queue[queueIndex];
49838  }
49839  sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
49840  //
49841  // The queue pending operation must be set before the action is performed in the event
49842  // that the action results in a synchronous event that is acted upon. For instance, if
49843  // an exception is thrown that can be handled, it's possible that new actions will be
49844  // appended to an empty queue and immediately executed, but would not have the correct
49845  // pending information if this property was set after the action was performed.
49846
49847  sourceUpdater.queuePending[type] = queueEntry;
49848  queueEntry.action(type, sourceUpdater);
49849  if (!queueEntry.doneFn) {
49850    // synchronous operation, process next entry
49851    sourceUpdater.queuePending[type] = null;
49852    shiftQueue(type, sourceUpdater);
49853    return;
49854  }
49855};
49856const cleanupBuffer = (type, sourceUpdater) => {
49857  const buffer = sourceUpdater[`${type}Buffer`];
49858  const titleType = toTitleCase(type);
49859  if (!buffer) {
49860    return;
49861  }
49862  buffer.removeEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
49863  buffer.removeEventListener('error', sourceUpdater[`on${titleType}Error_`]);
49864  sourceUpdater.codecs[type] = null;
49865  sourceUpdater[`${type}Buffer`] = null;
49866};
49867const inSourceBuffers = (mediaSource, sourceBuffer) =>
49868  mediaSource &&
49869  sourceBuffer &&
49870  Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
49871const actions = {
49872  appendBuffer: (bytes, segmentInfo, onError) => (type, sourceUpdater) => {
49873    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
49874    // or the media source does not contain this source buffer.
49875
49876    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49877      return;
49878    }
49879    sourceUpdater.logger_(
49880      `Appending segment ${segmentInfo.mediaIndex}'s ${bytes.length} bytes to ${type}Buffer`
49881    );
49882    try {
49883      sourceBuffer.appendBuffer(bytes);
49884    } catch (e) {
49885      sourceUpdater.logger_(
49886        `Error with code ${e.code} ` +
49887          (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') +
49888          `when appending segment ${segmentInfo.mediaIndex} to ${type}Buffer`
49889      );
49890      sourceUpdater.queuePending[type] = null;
49891      onError(e);
49892    }
49893  },
49894  remove: (start, end) => (type, sourceUpdater) => {
49895    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
49896    // or the media source does not contain this source buffer.
49897
49898    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49899      return;
49900    }
49901    sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`);
49902    try {
49903      sourceBuffer.remove(start, end);
49904    } catch (e) {
49905      sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`);
49906    }
49907  },
49908  timestampOffset: (offset) => (type, sourceUpdater) => {
49909    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
49910    // or the media source does not contain this source buffer.
49911
49912    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49913      return;
49914    }
49915    sourceUpdater.logger_(`Setting ${type}timestampOffset to ${offset}`);
49916    sourceBuffer.timestampOffset = offset;
49917  },
49918  callback: (callback) => (type, sourceUpdater) => {
49919    callback();
49920  },
49921  endOfStream: (error) => (sourceUpdater) => {
49922    if (sourceUpdater.mediaSource.readyState !== 'open') {
49923      return;
49924    }
49925    sourceUpdater.logger_(`Calling mediaSource endOfStream(${error || ''})`);
49926    try {
49927      sourceUpdater.mediaSource.endOfStream(error);
49928    } catch (e) {
49929      videojs.log.warn('Failed to call media source endOfStream', e);
49930    }
49931  },
49932  duration: (duration) => (sourceUpdater) => {
49933    sourceUpdater.logger_(`Setting mediaSource duration to ${duration}`);
49934    try {
49935      sourceUpdater.mediaSource.duration = duration;
49936    } catch (e) {
49937      videojs.log.warn('Failed to set media source duration', e);
49938    }
49939  },
49940  abort: () => (type, sourceUpdater) => {
49941    if (sourceUpdater.mediaSource.readyState !== 'open') {
49942      return;
49943    }
49944    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
49945    // or the media source does not contain this source buffer.
49946
49947    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49948      return;
49949    }
49950    sourceUpdater.logger_(`calling abort on ${type}Buffer`);
49951    try {
49952      sourceBuffer.abort();
49953    } catch (e) {
49954      videojs.log.warn(`Failed to abort on ${type}Buffer`, e);
49955    }
49956  },
49957  addSourceBuffer: (type, codec) => (sourceUpdater) => {
49958    const titleType = toTitleCase(type);
49959    const mime = codecs_js.getMimeForCodec(codec);
49960    sourceUpdater.logger_(`Adding ${type}Buffer with codec ${codec} to mediaSource`);
49961    const sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
49962    sourceBuffer.addEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
49963    sourceBuffer.addEventListener('error', sourceUpdater[`on${titleType}Error_`]);
49964    sourceUpdater.codecs[type] = codec;
49965    sourceUpdater[`${type}Buffer`] = sourceBuffer;
49966  },
49967  removeSourceBuffer: (type) => (sourceUpdater) => {
49968    const sourceBuffer = sourceUpdater[`${type}Buffer`];
49969    cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
49970    // or the media source does not contain this source buffer.
49971
49972    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49973      return;
49974    }
49975    sourceUpdater.logger_(
49976      `Removing ${type}Buffer with codec ${sourceUpdater.codecs[type]} from mediaSource`
49977    );
49978    try {
49979      sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
49980    } catch (e) {
49981      videojs.log.warn(`Failed to removeSourceBuffer ${type}Buffer`, e);
49982    }
49983  },
49984  changeType: (codec) => (type, sourceUpdater) => {
49985    const sourceBuffer = sourceUpdater[`${type}Buffer`];
49986    const mime = codecs_js.getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
49987    // or the media source does not contain this source buffer.
49988
49989    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
49990      return;
49991    } // do not update codec if we don't need to.
49992    // Only update if we change the codec base.
49993    // For example, going from avc1.640028 to avc1.64001f does not require a changeType call.
49994
49995    const newCodecBase = codec.substring(0, codec.indexOf('.'));
49996    const oldCodec = sourceUpdater.codecs[type];
49997    const oldCodecBase = oldCodec.substring(0, oldCodec.indexOf('.'));
49998    if (oldCodecBase === newCodecBase) {
49999      return;
50000    }
50001    const metadata = {
50002      codecsChangeInfo: {
50003        from: oldCodec,
50004        to: codec,
50005      },
50006    };
50007    sourceUpdater.trigger({
50008      type: 'codecschange',
50009      metadata,
50010    });
50011    sourceUpdater.logger_(`changing ${type}Buffer codec from ${oldCodec} to ${codec}`); // check if change to the provided type is supported
50012
50013    try {
50014      sourceBuffer.changeType(mime);
50015      sourceUpdater.codecs[type] = codec;
50016    } catch (e) {
50017      metadata.errorType = videojs.Error.StreamingCodecsChangeError;
50018      metadata.error = e;
50019      e.metadata = metadata;
50020      sourceUpdater.error_ = e;
50021      sourceUpdater.trigger('error');
50022      videojs.log.warn(`Failed to changeType on ${type}Buffer`, e);
50023    }
50024  },
50025};
50026const pushQueue = ({ type, sourceUpdater, action, doneFn, name }) => {
50027  sourceUpdater.queue.push({
50028    type,
50029    action,
50030    doneFn,
50031    name,
50032  });
50033  shiftQueue(type, sourceUpdater);
50034};
50035const onUpdateend = (type, sourceUpdater) => (e) => {
50036  // Although there should, in theory, be a pending action for any updateend receieved,
50037  // there are some actions that may trigger updateend events without set definitions in
50038  // the w3c spec. For instance, setting the duration on the media source may trigger
50039  // updateend events on source buffers. This does not appear to be in the spec. As such,
50040  // if we encounter an updateend without a corresponding pending action from our queue
50041  // for that source buffer type, process the next action.
50042  const bufferedRangesForType = sourceUpdater[`${type}Buffered`]();
50043  const descriptiveString = bufferedRangesToString(bufferedRangesForType);
50044  sourceUpdater.logger_(
50045    `received "updateend" event for ${type} Source Buffer: `,
50046    descriptiveString
50047  );
50048  if (sourceUpdater.queuePending[type]) {
50049    const doneFn = sourceUpdater.queuePending[type].doneFn;
50050    sourceUpdater.queuePending[type] = null;
50051    if (doneFn) {
50052      // if there's an error, report it
50053      doneFn(sourceUpdater[`${type}Error_`]);
50054    }
50055  }
50056  shiftQueue(type, sourceUpdater);
50057};
50058/**
50059 * A queue of callbacks to be serialized and applied when a
50060 * MediaSource and its associated SourceBuffers are not in the
50061 * updating state. It is used by the segment loader to update the
50062 * underlying SourceBuffers when new data is loaded, for instance.
50063 *
50064 * @class SourceUpdater
50065 * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
50066 * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
50067 */
50068
50069class SourceUpdater extends videojs.EventTarget {
50070  constructor(mediaSource) {
50071    super();
50072    this.mediaSource = mediaSource;
50073    this.sourceopenListener_ = () => shiftQueue('mediaSource', this);
50074    this.mediaSource.addEventListener('sourceopen', this.sourceopenListener_);
50075    this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
50076
50077    this.audioTimestampOffset_ = 0;
50078    this.videoTimestampOffset_ = 0;
50079    this.queue = [];
50080    this.queuePending = {
50081      audio: null,
50082      video: null,
50083    };
50084    this.delayedAudioAppendQueue_ = [];
50085    this.videoAppendQueued_ = false;
50086    this.codecs = {};
50087    this.onVideoUpdateEnd_ = onUpdateend('video', this);
50088    this.onAudioUpdateEnd_ = onUpdateend('audio', this);
50089    this.onVideoError_ = (e) => {
50090      // used for debugging
50091      this.videoError_ = e;
50092    };
50093    this.onAudioError_ = (e) => {
50094      // used for debugging
50095      this.audioError_ = e;
50096    };
50097    this.createdSourceBuffers_ = false;
50098    this.initializedEme_ = false;
50099    this.triggeredReady_ = false;
50100  }
50101  initializedEme() {
50102    this.initializedEme_ = true;
50103    this.triggerReady();
50104  }
50105  hasCreatedSourceBuffers() {
50106    // if false, likely waiting on one of the segment loaders to get enough data to create
50107    // source buffers
50108    return this.createdSourceBuffers_;
50109  }
50110  hasInitializedAnyEme() {
50111    return this.initializedEme_;
50112  }
50113  ready() {
50114    return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
50115  }
50116  createSourceBuffers(codecs) {
50117    if (this.hasCreatedSourceBuffers()) {
50118      // already created them before
50119      return;
50120    } // the intial addOrChangeSourceBuffers will always be
50121    // two add buffers.
50122
50123    this.addOrChangeSourceBuffers(codecs);
50124    this.createdSourceBuffers_ = true;
50125    this.trigger('createdsourcebuffers');
50126    this.triggerReady();
50127  }
50128  triggerReady() {
50129    // only allow ready to be triggered once, this prevents the case
50130    // where:
50131    // 1. we trigger createdsourcebuffers
50132    // 2. ie 11 synchronously initializates eme
50133    // 3. the synchronous initialization causes us to trigger ready
50134    // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
50135    if (this.ready() && !this.triggeredReady_) {
50136      this.triggeredReady_ = true;
50137      this.trigger('ready');
50138    }
50139  }
50140  /**
50141   * Add a type of source buffer to the media source.
50142   *
50143   * @param {string} type
50144   *        The type of source buffer to add.
50145   *
50146   * @param {string} codec
50147   *        The codec to add the source buffer with.
50148   */
50149
50150  addSourceBuffer(type, codec) {
50151    pushQueue({
50152      type: 'mediaSource',
50153      sourceUpdater: this,
50154      action: actions.addSourceBuffer(type, codec),
50155      name: 'addSourceBuffer',
50156    });
50157  }
50158  /**
50159   * call abort on a source buffer.
50160   *
50161   * @param {string} type
50162   *        The type of source buffer to call abort on.
50163   */
50164
50165  abort(type) {
50166    pushQueue({
50167      type,
50168      sourceUpdater: this,
50169      action: actions.abort(type),
50170      name: 'abort',
50171    });
50172  }
50173  /**
50174   * Call removeSourceBuffer and remove a specific type
50175   * of source buffer on the mediaSource.
50176   *
50177   * @param {string} type
50178   *        The type of source buffer to remove.
50179   */
50180
50181  removeSourceBuffer(type) {
50182    if (!this.canRemoveSourceBuffer()) {
50183      videojs.log.error('removeSourceBuffer is not supported!');
50184      return;
50185    }
50186    pushQueue({
50187      type: 'mediaSource',
50188      sourceUpdater: this,
50189      action: actions.removeSourceBuffer(type),
50190      name: 'removeSourceBuffer',
50191    });
50192  }
50193  /**
50194   * Whether or not the removeSourceBuffer function is supported
50195   * on the mediaSource.
50196   *
50197   * @return {boolean}
50198   *          if removeSourceBuffer can be called.
50199   */
50200
50201  canRemoveSourceBuffer() {
50202    // As of Firefox 83 removeSourceBuffer
50203    // throws errors, so we report that it does not support this.
50204    return (
50205      !videojs.browser.IS_FIREFOX &&
50206      window__default['default'].MediaSource &&
50207      window__default['default'].MediaSource.prototype &&
50208      typeof window__default['default'].MediaSource.prototype.removeSourceBuffer === 'function'
50209    );
50210  }
50211  /**
50212   * Whether or not the changeType function is supported
50213   * on our SourceBuffers.
50214   *
50215   * @return {boolean}
50216   *         if changeType can be called.
50217   */
50218
50219  static canChangeType() {
50220    return (
50221      window__default['default'].SourceBuffer &&
50222      window__default['default'].SourceBuffer.prototype &&
50223      typeof window__default['default'].SourceBuffer.prototype.changeType === 'function'
50224    );
50225  }
50226  /**
50227   * Whether or not the changeType function is supported
50228   * on our SourceBuffers.
50229   *
50230   * @return {boolean}
50231   *         if changeType can be called.
50232   */
50233
50234  canChangeType() {
50235    return this.constructor.canChangeType();
50236  }
50237  /**
50238   * Call the changeType function on a source buffer, given the code and type.
50239   *
50240   * @param {string} type
50241   *        The type of source buffer to call changeType on.
50242   *
50243   * @param {string} codec
50244   *        The codec string to change type with on the source buffer.
50245   */
50246
vendor: 18,343 bytes, lines 50247-50843
50247  changeType(type, codec) {
50248    if (!this.canChangeType()) {
50249      videojs.log.error('changeType is not supported!');
50250      return;
50251    }
50252    pushQueue({
50253      type,
50254      sourceUpdater: this,
50255      action: actions.changeType(codec),
50256      name: 'changeType',
50257    });
50258  }
50259  /**
50260   * Add source buffers with a codec or, if they are already created,
50261   * call changeType on source buffers using changeType.
50262   *
50263   * @param {Object} codecs
50264   *        Codecs to switch to
50265   */
50266
50267  addOrChangeSourceBuffers(codecs) {
50268    if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
50269      throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
50270    }
50271    Object.keys(codecs).forEach((type) => {
50272      const codec = codecs[type];
50273      if (!this.hasCreatedSourceBuffers()) {
50274        return this.addSourceBuffer(type, codec);
50275      }
50276      if (this.canChangeType()) {
50277        this.changeType(type, codec);
50278      }
50279    });
50280  }
50281  /**
50282   * Queue an update to append an ArrayBuffer.
50283   *
50284   * @param {MediaObject} object containing audioBytes and/or videoBytes
50285   * @param {Function} done the function to call when done
50286   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
50287   */
50288
50289  appendBuffer(options, doneFn) {
50290    const { segmentInfo, type, bytes } = options;
50291    this.processedAppend_ = true;
50292    if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
50293      this.delayedAudioAppendQueue_.push([options, doneFn]);
50294      this.logger_(`delayed audio append of ${bytes.length} until video append`);
50295      return;
50296    } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
50297    // not be fired. This means that the queue will be blocked until the next action
50298    // taken by the segment-loader. Provide a mechanism for segment-loader to handle
50299    // these errors by calling the doneFn with the specific error.
50300
50301    const onError = doneFn;
50302    pushQueue({
50303      type,
50304      sourceUpdater: this,
50305      action: actions.appendBuffer(
50306        bytes,
50307        segmentInfo || {
50308          mediaIndex: -1,
50309        },
50310        onError
50311      ),
50312      doneFn,
50313      name: 'appendBuffer',
50314    });
50315    if (type === 'video') {
50316      this.videoAppendQueued_ = true;
50317      if (!this.delayedAudioAppendQueue_.length) {
50318        return;
50319      }
50320      const queue = this.delayedAudioAppendQueue_.slice();
50321      this.logger_(`queuing delayed audio ${queue.length} appendBuffers`);
50322      this.delayedAudioAppendQueue_.length = 0;
50323      queue.forEach((que) => {
50324        this.appendBuffer.apply(this, que);
50325      });
50326    }
50327  }
50328  /**
50329   * Get the audio buffer's buffered timerange.
50330   *
50331   * @return {TimeRange}
50332   *         The audio buffer's buffered time range
50333   */
50334
50335  audioBuffered() {
50336    // no media source/source buffer or it isn't in the media sources
50337    // source buffer list
50338    if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
50339      return createTimeRanges();
50340    }
50341    return this.audioBuffer.buffered ? this.audioBuffer.buffered : createTimeRanges();
50342  }
50343  /**
50344   * Get the video buffer's buffered timerange.
50345   *
50346   * @return {TimeRange}
50347   *         The video buffer's buffered time range
50348   */
50349
50350  videoBuffered() {
50351    // no media source/source buffer or it isn't in the media sources
50352    // source buffer list
50353    if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
50354      return createTimeRanges();
50355    }
50356    return this.videoBuffer.buffered ? this.videoBuffer.buffered : createTimeRanges();
50357  }
50358  /**
50359   * Get a combined video/audio buffer's buffered timerange.
50360   *
50361   * @return {TimeRange}
50362   *         the combined time range
50363   */
50364
50365  buffered() {
50366    const video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
50367    const audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
50368    if (audio && !video) {
50369      return this.audioBuffered();
50370    }
50371    if (video && !audio) {
50372      return this.videoBuffered();
50373    }
50374    return bufferIntersection(this.audioBuffered(), this.videoBuffered());
50375  }
50376  /**
50377   * Add a callback to the queue that will set duration on the mediaSource.
50378   *
50379   * @param {number} duration
50380   *        The duration to set
50381   *
50382   * @param {Function} [doneFn]
50383   *        function to run after duration has been set.
50384   */
50385
50386  setDuration(duration, doneFn = noop) {
50387    // In order to set the duration on the media source, it's necessary to wait for all
50388    // source buffers to no longer be updating. "If the updating attribute equals true on
50389    // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
50390    // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
50391    pushQueue({
50392      type: 'mediaSource',
50393      sourceUpdater: this,
50394      action: actions.duration(duration),
50395      name: 'duration',
50396      doneFn,
50397    });
50398  }
50399  /**
50400   * Add a mediaSource endOfStream call to the queue
50401   *
50402   * @param {Error} [error]
50403   *        Call endOfStream with an error
50404   *
50405   * @param {Function} [doneFn]
50406   *        A function that should be called when the
50407   *        endOfStream call has finished.
50408   */
50409
50410  endOfStream(error = null, doneFn = noop) {
50411    if (typeof error !== 'string') {
50412      error = undefined;
50413    } // In order to set the duration on the media source, it's necessary to wait for all
50414    // source buffers to no longer be updating. "If the updating attribute equals true on
50415    // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
50416    // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
50417
50418    pushQueue({
50419      type: 'mediaSource',
50420      sourceUpdater: this,
50421      action: actions.endOfStream(error),
50422      name: 'endOfStream',
50423      doneFn,
50424    });
50425  }
50426  /**
50427   * Queue an update to remove a time range from the buffer.
50428   *
50429   * @param {number} start where to start the removal
50430   * @param {number} end where to end the removal
50431   * @param {Function} [done=noop] optional callback to be executed when the remove
50432   * operation is complete
50433   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
50434   */
50435
50436  removeAudio(start, end, done = noop) {
50437    if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
50438      done();
50439      return;
50440    }
50441    pushQueue({
50442      type: 'audio',
50443      sourceUpdater: this,
50444      action: actions.remove(start, end),
50445      doneFn: done,
50446      name: 'remove',
50447    });
50448  }
50449  /**
50450   * Queue an update to remove a time range from the buffer.
50451   *
50452   * @param {number} start where to start the removal
50453   * @param {number} end where to end the removal
50454   * @param {Function} [done=noop] optional callback to be executed when the remove
50455   * operation is complete
50456   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
50457   */
50458
50459  removeVideo(start, end, done = noop) {
50460    if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
50461      done();
50462      return;
50463    }
50464    pushQueue({
50465      type: 'video',
50466      sourceUpdater: this,
50467      action: actions.remove(start, end),
50468      doneFn: done,
50469      name: 'remove',
50470    });
50471  }
50472  /**
50473   * Whether the underlying sourceBuffer is updating or not
50474   *
50475   * @return {boolean} the updating status of the SourceBuffer
50476   */
50477
50478  updating() {
50479    // the audio/video source buffer is updating
50480    if (updating('audio', this) || updating('video', this)) {
50481      return true;
50482    }
50483    return false;
50484  }
50485  /**
50486   * Set/get the timestampoffset on the audio SourceBuffer
50487   *
50488   * @return {number} the timestamp offset
50489   */
50490
50491  audioTimestampOffset(offset) {
50492    if (
50493      typeof offset !== 'undefined' &&
50494      this.audioBuffer &&
50495      // no point in updating if it's the same
50496      this.audioTimestampOffset_ !== offset
50497    ) {
50498      pushQueue({
50499        type: 'audio',
50500        sourceUpdater: this,
50501        action: actions.timestampOffset(offset),
50502        name: 'timestampOffset',
50503      });
50504      this.audioTimestampOffset_ = offset;
50505    }
50506    return this.audioTimestampOffset_;
50507  }
50508  /**
50509   * Set/get the timestampoffset on the video SourceBuffer
50510   *
50511   * @return {number} the timestamp offset
50512   */
50513
50514  videoTimestampOffset(offset) {
50515    if (
50516      typeof offset !== 'undefined' &&
50517      this.videoBuffer &&
50518      // no point in updating if it's the same
50519      this.videoTimestampOffset_ !== offset
50520    ) {
50521      pushQueue({
50522        type: 'video',
50523        sourceUpdater: this,
50524        action: actions.timestampOffset(offset),
50525        name: 'timestampOffset',
50526      });
50527      this.videoTimestampOffset_ = offset;
50528    }
50529    return this.videoTimestampOffset_;
50530  }
50531  /**
50532   * Add a function to the queue that will be called
50533   * when it is its turn to run in the audio queue.
50534   *
50535   * @param {Function} callback
50536   *        The callback to queue.
50537   */
50538
50539  audioQueueCallback(callback) {
50540    if (!this.audioBuffer) {
50541      return;
50542    }
50543    pushQueue({
50544      type: 'audio',
50545      sourceUpdater: this,
50546      action: actions.callback(callback),
50547      name: 'callback',
50548    });
50549  }
50550  /**
50551   * Add a function to the queue that will be called
50552   * when it is its turn to run in the video queue.
50553   *
50554   * @param {Function} callback
50555   *        The callback to queue.
50556   */
50557
50558  videoQueueCallback(callback) {
50559    if (!this.videoBuffer) {
50560      return;
50561    }
50562    pushQueue({
50563      type: 'video',
50564      sourceUpdater: this,
50565      action: actions.callback(callback),
50566      name: 'callback',
50567    });
50568  }
50569  /**
50570   * dispose of the source updater and the underlying sourceBuffer
50571   */
50572
50573  dispose() {
50574    this.trigger('dispose');
50575    bufferTypes.forEach((type) => {
50576      this.abort(type);
50577      if (this.canRemoveSourceBuffer()) {
50578        this.removeSourceBuffer(type);
50579      } else {
50580        this[`${type}QueueCallback`](() => cleanupBuffer(type, this));
50581      }
50582    });
50583    this.videoAppendQueued_ = false;
50584    this.delayedAudioAppendQueue_.length = 0;
50585    if (this.sourceopenListener_) {
50586      this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
50587    }
50588    this.off();
50589  }
50590}
50591const uint8ToUtf8 = (uintArray) =>
50592  decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
50593const bufferToHexString = (buffer) => {
50594  const uInt8Buffer = new Uint8Array(buffer);
50595  return Array.from(uInt8Buffer)
50596    .map((byte) => byte.toString(16).padStart(2, '0'))
50597    .join('');
50598};
50599
50600/**
50601 * @file vtt-segment-loader.js
50602 */
50603const VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map((char) => char.charCodeAt(0)));
50604class NoVttJsError extends Error {
50605  constructor() {
50606    super('Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.');
50607  }
50608}
50609/**
50610 * An object that manages segment loading and appending.
50611 *
50612 * @class VTTSegmentLoader
50613 * @param {Object} options required and optional options
50614 * @extends videojs.EventTarget
50615 */
50616
50617class VTTSegmentLoader extends SegmentLoader {
50618  constructor(settings, options = {}) {
50619    super(settings, options); // SegmentLoader requires a MediaSource be specified or it will throw an error;
50620    // however, VTTSegmentLoader has no need of a media source, so delete the reference
50621
50622    this.mediaSource_ = null;
50623    this.subtitlesTrack_ = null;
50624    this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
50625    this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
50626    // the sync controller leads to improper behavior.
50627
50628    this.shouldSaveSegmentTimingInfo_ = false;
50629  }
50630  /**
50631   * Indicates which time ranges are buffered
50632   *
50633   * @return {TimeRange}
50634   *         TimeRange object representing the current buffered ranges
50635   */
50636
50637  buffered_() {
50638    if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
50639      return createTimeRanges();
50640    }
50641    const cues = this.subtitlesTrack_.cues;
50642    const start = cues[0].startTime;
50643    const end = cues[cues.length - 1].startTime;
50644    return createTimeRanges([[start, end]]);
50645  }
50646  /**
50647   * Gets and sets init segment for the provided map
50648   *
50649   * @param {Object} map
50650   *        The map object representing the init segment to get or set
50651   * @param {boolean=} set
50652   *        If true, the init segment for the provided map should be saved
50653   * @return {Object}
50654   *         map object for desired init segment
50655   */
50656
50657  initSegmentForMap(map, set = false) {
50658    if (!map) {
50659      return null;
50660    }
50661    const id = initSegmentId(map);
50662    let storedMap = this.initSegments_[id];
50663    if (set && !storedMap && map.bytes) {
50664      // append WebVTT line terminators to the media initialization segment if it exists
50665      // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
50666      // requires two or more WebVTT line terminators between the WebVTT header and the
50667      // rest of the file
50668      const combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
50669      const combinedSegment = new Uint8Array(combinedByteLength);
50670      combinedSegment.set(map.bytes);
50671      combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
50672      this.initSegments_[id] = storedMap = {
50673        resolvedUri: map.resolvedUri,
50674        byterange: map.byterange,
50675        bytes: combinedSegment,
50676      };
50677    }
50678    return storedMap || map;
50679  }
50680  /**
50681   * Returns true if all configuration required for loading is present, otherwise false.
50682   *
50683   * @return {boolean} True if the all configuration is ready for loading
50684   * @private
50685   */
50686
50687  couldBeginLoading_() {
50688    return this.playlist_ && this.subtitlesTrack_ && !this.paused();
50689  }
50690  /**
50691   * Once all the starting parameters have been specified, begin
50692   * operation. This method should only be invoked from the INIT
50693   * state.
50694   *
50695   * @private
50696   */
50697
50698  init_() {
50699    this.state = 'READY';
50700    this.resetEverything();
50701    return this.monitorBuffer_();
50702  }
50703  /**
50704   * Set a subtitle track on the segment loader to add subtitles to
50705   *
50706   * @param {TextTrack=} track
50707   *        The text track to add loaded subtitles to
50708   * @return {TextTrack}
50709   *        Returns the subtitles track
50710   */
50711
50712  track(track) {
50713    if (typeof track === 'undefined') {
50714      return this.subtitlesTrack_;
50715    }
50716    this.subtitlesTrack_ = track; // if we were unpaused but waiting for a sourceUpdater, start
50717    // buffering now
50718
50719    if (this.state === 'INIT' && this.couldBeginLoading_()) {
50720      this.init_();
50721    }
50722    return this.subtitlesTrack_;
50723  }
50724  /**
50725   * Remove any data in the source buffer between start and end times
50726   *
50727   * @param {number} start - the start time of the region to remove from the buffer
50728   * @param {number} end - the end time of the region to remove from the buffer
50729   */
50730
50731  remove(start, end) {
50732    removeCuesFromTrack(start, end, this.subtitlesTrack_);
50733  }
50734  /**
50735   * fill the buffer with segements unless the sourceBuffers are
50736   * currently updating
50737   *
50738   * Note: this function should only ever be called by monitorBuffer_
50739   * and never directly
50740   *
50741   * @private
50742   */
50743
50744  fillBuffer_() {
50745    // see if we need to begin loading immediately
50746    const segmentInfo = this.chooseNextRequest_();
50747    if (!segmentInfo) {
50748      return;
50749    }
50750    if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
50751      // We don't have the timestamp offset that we need to sync subtitles.
50752      // Rerun on a timestamp offset or user interaction.
50753      const checkTimestampOffset = () => {
50754        this.state = 'READY';
50755        if (!this.paused()) {
50756          // if not paused, queue a buffer check as soon as possible
50757          this.monitorBuffer_();
50758        }
50759      };
50760      this.syncController_.one('timestampoffset', checkTimestampOffset);
50761      this.state = 'WAITING_ON_TIMELINE';
50762      return;
50763    }
50764    this.loadSegment_(segmentInfo);
50765  } // never set a timestamp offset for vtt segments.
50766
50767  timestampOffsetForSegment_() {
50768    return null;
50769  }
50770  chooseNextRequest_() {
50771    return this.skipEmptySegments_(super.chooseNextRequest_());
50772  }
50773  /**
50774   * Prevents the segment loader from requesting segments we know contain no subtitles
50775   * by walking forward until we find the next segment that we don't know whether it is
50776   * empty or not.
50777   *
50778   * @param {Object} segmentInfo
50779   *        a segment info object that describes the current segment
50780   * @return {Object}
50781   *         a segment info object that describes the current segment
50782   */
50783
50784  skipEmptySegments_(segmentInfo) {
50785    while (segmentInfo && segmentInfo.segment.empty) {
50786      // stop at the last possible segmentInfo
50787      if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
50788        segmentInfo = null;
50789        break;
50790      }
50791      segmentInfo = this.generateSegmentInfo_({
50792        playlist: segmentInfo.playlist,
50793        mediaIndex: segmentInfo.mediaIndex + 1,
50794        startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
50795        isSyncRequest: segmentInfo.isSyncRequest,
50796      });
50797    }
50798    return segmentInfo;
50799  }
50800  stopForError(error) {
50801    this.error(error);
50802    this.state = 'READY';
50803    this.pause();
50804    this.trigger('error');
50805  }
50806  /**
50807   * append a decrypted segement to the SourceBuffer through a SourceUpdater
50808   *
50809   * @private
50810   */
50811
50812  segmentRequestFinished_(error, simpleSegment, result) {
50813    if (!this.subtitlesTrack_) {
50814      this.state = 'READY';
50815      return;
50816    }
50817    this.saveTransferStats_(simpleSegment.stats); // the request was aborted
50818
50819    if (!this.pendingSegment_) {
50820      this.state = 'READY';
50821      this.mediaRequestsAborted += 1;
50822      return;
50823    }
50824    if (error) {
50825      if (error.code === REQUEST_ERRORS.TIMEOUT) {
50826        this.handleTimeout_();
50827      }
50828      if (error.code === REQUEST_ERRORS.ABORTED) {
50829        this.mediaRequestsAborted += 1;
50830      } else {
50831        this.mediaRequestsErrored += 1;
50832      }
50833      this.stopForError(error);
50834      return;
50835    }
50836    const segmentInfo = this.pendingSegment_;
50837    const isMp4WebVttSegmentWithCues = result.mp4VttCues && result.mp4VttCues.length;
50838    if (isMp4WebVttSegmentWithCues) {
50839      segmentInfo.mp4VttCues = result.mp4VttCues;
50840    } // although the VTT segment loader bandwidth isn't really used, it's good to
50841    // maintain functionality between segment loaders
50842
50843    this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
vendor: 12,870 bytes, lines 50843-51225
50843 // if this request included a segment key, save that data in the cache
50844
50845    if (simpleSegment.key) {
50846      this.segmentKey(simpleSegment.key, true);
50847    }
50848    this.state = 'APPENDING'; // used for tests
50849
50850    this.trigger('appending');
50851    const segment = segmentInfo.segment;
50852    if (segment.map) {
50853      segment.map.bytes = simpleSegment.map.bytes;
50854    }
50855    segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
50856
50857    if (
50858      typeof window__default['default'].WebVTT !== 'function' &&
50859      typeof this.loadVttJs === 'function'
50860    ) {
50861      this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
50862      // script will be loaded once but multiple listeners will be added to the queue, which is expected.
50863
50864      this.loadVttJs().then(
50865        () => this.segmentRequestFinished_(error, simpleSegment, result),
50866        () =>
50867          this.stopForError({
50868            message: 'Error loading vtt.js',
50869          })
50870      );
50871      return;
50872    }
50873    segment.requested = true;
50874    try {
50875      this.parseVTTCues_(segmentInfo);
50876    } catch (e) {
50877      this.stopForError({
50878        message: e.message,
50879        metadata: {
50880          errorType: videojs.Error.StreamingVttParserError,
50881          error: e,
50882        },
50883      });
50884      return;
50885    }
50886    if (!isMp4WebVttSegmentWithCues) {
50887      this.updateTimeMapping_(
50888        segmentInfo,
50889        this.syncController_.timelines[segmentInfo.timeline],
50890        this.playlist_
50891      );
50892    }
50893    if (segmentInfo.cues.length) {
50894      segmentInfo.timingInfo = {
50895        start: segmentInfo.cues[0].startTime,
50896        end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime,
50897      };
50898    } else {
50899      segmentInfo.timingInfo = {
50900        start: segmentInfo.startOfSegment,
50901        end: segmentInfo.startOfSegment + segmentInfo.duration,
50902      };
50903    }
50904    if (segmentInfo.isSyncRequest) {
50905      this.trigger('syncinfoupdate');
50906      this.pendingSegment_ = null;
50907      this.state = 'READY';
50908      return;
50909    }
50910    segmentInfo.byteLength = segmentInfo.bytes.byteLength;
50911    this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
50912    // the subtitle track
50913
50914    segmentInfo.cues.forEach((cue) => {
50915      this.subtitlesTrack_.addCue(
50916        this.featuresNativeTextTracks_
50917          ? new window__default['default'].VTTCue(cue.startTime, cue.endTime, cue.text)
50918          : cue
50919      );
50920    }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
50921    // cues to have identical time-intervals, but if the text is also identical
50922    // we can safely assume it is a duplicate that can be removed (ex. when a cue
50923    // "overlaps" VTT segments)
50924
50925    removeDuplicateCuesFromTrack(this.subtitlesTrack_);
50926    this.handleAppendsDone_();
50927  }
50928  handleData_(simpleSegment, result) {
50929    const isVttType = simpleSegment && simpleSegment.type === 'vtt';
50930    const isTextResult = result && result.type === 'text';
50931    const isFmp4VttSegment = isVttType && isTextResult; // handle segment data for fmp4 encapsulated webvtt
50932
50933    if (isFmp4VttSegment) {
50934      super.handleData_(simpleSegment, result);
50935    }
50936  }
50937  updateTimingInfoEnd_() {
50938    // noop
50939  }
50940  /**
50941   * Utility function for converting mp4 webvtt cue objects into VTTCues.
50942   *
50943   * @param {Object} segmentInfo with mp4 webvtt cues for parsing into VTTCue objecs
50944   */
50945
50946  parseMp4VttCues_(segmentInfo) {
50947    const timestampOffset =
50948      this.sourceUpdater_.videoTimestampOffset() === null
50949        ? this.sourceUpdater_.audioTimestampOffset()
50950        : this.sourceUpdater_.videoTimestampOffset();
50951    segmentInfo.mp4VttCues.forEach((cue) => {
50952      const start = cue.start + timestampOffset;
50953      const end = cue.end + timestampOffset;
50954      const vttCue = new window__default['default'].VTTCue(start, end, cue.cueText);
50955      if (cue.settings) {
50956        cue.settings.split(' ').forEach((cueSetting) => {
50957          const keyValString = cueSetting.split(':');
50958          const key = keyValString[0];
50959          const value = keyValString[1];
50960          vttCue[key] = isNaN(value) ? value : Number(value);
50961        });
50962      }
50963      segmentInfo.cues.push(vttCue);
50964    });
50965  }
50966  /**
50967   * Uses the WebVTT parser to parse the segment response
50968   *
50969   * @throws NoVttJsError
50970   *
50971   * @param {Object} segmentInfo
50972   *        a segment info object that describes the current segment
50973   * @private
50974   */
50975
50976  parseVTTCues_(segmentInfo) {
50977    let decoder;
50978    let decodeBytesToString = false;
50979    if (typeof window__default['default'].WebVTT !== 'function') {
50980      // caller is responsible for exception handling.
50981      throw new NoVttJsError();
50982    }
50983    segmentInfo.cues = [];
50984    segmentInfo.timestampmap = {
50985      MPEGTS: 0,
50986      LOCAL: 0,
50987    };
50988    if (segmentInfo.mp4VttCues) {
50989      this.parseMp4VttCues_(segmentInfo);
50990      return;
50991    }
50992    if (typeof window__default['default'].TextDecoder === 'function') {
50993      decoder = new window__default['default'].TextDecoder('utf8');
50994    } else {
50995      decoder = window__default['default'].WebVTT.StringDecoder();
50996      decodeBytesToString = true;
50997    }
50998    const parser = new window__default['default'].WebVTT.Parser(
50999      window__default['default'],
51000      window__default['default'].vttjs,
51001      decoder
51002    );
51003    parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
51004    parser.ontimestampmap = (map) => {
51005      segmentInfo.timestampmap = map;
51006    };
51007    parser.onparsingerror = (error) => {
51008      videojs.log.warn('Error encountered when parsing cues: ' + error.message);
51009    };
51010    if (segmentInfo.segment.map) {
51011      let mapData = segmentInfo.segment.map.bytes;
51012      if (decodeBytesToString) {
51013        mapData = uint8ToUtf8(mapData);
51014      }
51015      parser.parse(mapData);
51016    }
51017    let segmentData = segmentInfo.bytes;
51018    if (decodeBytesToString) {
51019      segmentData = uint8ToUtf8(segmentData);
51020    }
51021    parser.parse(segmentData);
51022    parser.flush();
51023  }
51024  /**
51025   * Updates the start and end times of any cues parsed by the WebVTT parser using
51026   * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
51027   * from the SyncController
51028   *
51029   * @param {Object} segmentInfo
51030   *        a segment info object that describes the current segment
51031   * @param {Object} mappingObj
51032   *        object containing a mapping from TS to media time
51033   * @param {Object} playlist
51034   *        the playlist object containing the segment
51035   * @private
51036   */
51037
51038  updateTimeMapping_(segmentInfo, mappingObj, playlist) {
51039    const segment = segmentInfo.segment;
51040    if (!mappingObj) {
51041      // If the sync controller does not have a mapping of TS to Media Time for the
51042      // timeline, then we don't have enough information to update the cue
51043      // start/end times
51044      return;
51045    }
51046    if (!segmentInfo.cues.length) {
51047      // If there are no cues, we also do not have enough information to figure out
51048      // segment timing. Mark that the segment contains no cues so we don't re-request
51049      // an empty segment.
51050      segment.empty = true;
51051      return;
51052    }
51053    const { MPEGTS, LOCAL } = segmentInfo.timestampmap;
51054    /**
51055     * From the spec:
51056     * The MPEGTS media timestamp MUST use a 90KHz timescale,
51057     * even when non-WebVTT Media Segments use a different timescale.
51058     */
51059
51060    const mpegTsInSeconds = MPEGTS / clock.ONE_SECOND_IN_TS;
51061    const diff = mpegTsInSeconds - LOCAL + mappingObj.mapping;
51062    segmentInfo.cues.forEach((cue) => {
51063      const duration = cue.endTime - cue.startTime;
51064      const startTime = this.handleRollover_(cue.startTime + diff, mappingObj.time);
51065      cue.startTime = Math.max(startTime, 0);
51066      cue.endTime = Math.max(startTime + duration, 0);
51067    });
51068    if (!playlist.syncInfo) {
51069      const firstStart = segmentInfo.cues[0].startTime;
51070      const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
51071      playlist.syncInfo = {
51072        mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
51073        time: Math.min(firstStart, lastStart - segment.duration),
51074      };
51075    }
51076  }
51077  /**
51078   * MPEG-TS PES timestamps are limited to 2^33.
51079   * Once they reach 2^33, they roll over to 0.
51080   * mux.js handles PES timestamp rollover for the following scenarios:
51081   * [forward rollover(right)] ->
51082   *    PES timestamps monotonically increase, and once they reach 2^33, they roll over to 0
51083   * [backward rollover(left)] -->
51084   *    we seek back to position before rollover.
51085   *
51086   * According to the HLS SPEC:
51087   * When synchronizing WebVTT with PES timestamps, clients SHOULD account
51088   * for cases where the 33-bit PES timestamps have wrapped and the WebVTT
51089   * cue times have not.  When the PES timestamp wraps, the WebVTT Segment
51090   * SHOULD have a X-TIMESTAMP-MAP header that maps the current WebVTT
51091   * time to the new (low valued) PES timestamp.
51092   *
51093   * So we want to handle rollover here and align VTT Cue start/end time to the player's time.
51094   */
51095
51096  handleRollover_(value, reference) {
51097    if (reference === null) {
51098      return value;
51099    }
51100    let valueIn90khz = value * clock.ONE_SECOND_IN_TS;
51101    const referenceIn90khz = reference * clock.ONE_SECOND_IN_TS;
51102    let offset;
51103    if (referenceIn90khz < valueIn90khz) {
51104      // - 2^33
51105      offset = -8589934592;
51106    } else {
51107      // + 2^33
51108      offset = 8589934592;
51109    } // distance(value - reference) > 2^32
51110
51111    while (Math.abs(valueIn90khz - referenceIn90khz) > 4294967296) {
51112      valueIn90khz += offset;
51113    }
51114    return valueIn90khz / clock.ONE_SECOND_IN_TS;
51115  }
51116}
51117
51118/**
51119 * @file ad-cue-tags.js
51120 */
51121/**
51122 * Searches for an ad cue that overlaps with the given mediaTime
51123 *
51124 * @param {Object} track
51125 *        the track to find the cue for
51126 *
51127 * @param {number} mediaTime
51128 *        the time to find the cue at
51129 *
51130 * @return {Object|null}
51131 *         the found cue or null
51132 */
51133
51134const findAdCue = function (track, mediaTime) {
51135  const cues = track.cues;
51136  for (let i = 0; i < cues.length; i++) {
51137    const cue = cues[i];
51138    if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
51139      return cue;
51140    }
51141  }
51142  return null;
51143};
51144const updateAdCues = function (media, track, offset = 0) {
51145  if (!media.segments) {
51146    return;
51147  }
51148  let mediaTime = offset;
51149  let cue;
51150  for (let i = 0; i < media.segments.length; i++) {
51151    const segment = media.segments[i];
51152    if (!cue) {
51153      // Since the cues will span for at least the segment duration, adding a fudge
51154      // factor of half segment duration will prevent duplicate cues from being
51155      // created when timing info is not exact (e.g. cue start time initialized
51156      // at 10.006677, but next call mediaTime is 10.003332 )
51157      cue = findAdCue(track, mediaTime + segment.duration / 2);
51158    }
51159    if (cue) {
51160      if ('cueIn' in segment) {
51161        // Found a CUE-IN so end the cue
51162        cue.endTime = mediaTime;
51163        cue.adEndTime = mediaTime;
51164        mediaTime += segment.duration;
51165        cue = null;
51166        continue;
51167      }
51168      if (mediaTime < cue.endTime) {
51169        // Already processed this mediaTime for this cue
51170        mediaTime += segment.duration;
51171        continue;
51172      } // otherwise extend cue until a CUE-IN is found
51173
51174      cue.endTime += segment.duration;
51175    } else {
51176      if ('cueOut' in segment) {
51177        cue = new window__default['default'].VTTCue(
51178          mediaTime,
51179          mediaTime + segment.duration,
51180          segment.cueOut
51181        );
51182        cue.adStartTime = mediaTime; // Assumes tag format to be
51183        // #EXT-X-CUE-OUT:30
51184
51185        cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
51186        track.addCue(cue);
51187      }
51188      if ('cueOutCont' in segment) {
51189        // Entered into the middle of an ad cue
51190        // Assumes tag formate to be
51191        // #EXT-X-CUE-OUT-CONT:10/30
51192        const [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat);
51193        cue = new window__default['default'].VTTCue(mediaTime, mediaTime + segment.duration, '');
51194        cue.adStartTime = mediaTime - adOffset;
51195        cue.adEndTime = cue.adStartTime + adTotal;
51196        track.addCue(cue);
51197      }
51198    }
51199    mediaTime += segment.duration;
51200  }
51201};
51202class SyncInfo {
51203  /**
51204   * @param {number} start - media sequence start
51205   * @param {number} end - media sequence end
51206   * @param {number} segmentIndex - index for associated segment
51207   * @param {number|null} [partIndex] - index for associated part
51208   * @param {boolean} [appended] - appended indicator
51209   *
51210   */
51211  constructor({ start, end, segmentIndex, partIndex = null, appended = false }) {
51212    this.start_ = start;
51213    this.end_ = end;
51214    this.segmentIndex_ = segmentIndex;
51215    this.partIndex_ = partIndex;
51216    this.appended_ = appended;
51217  }
51218  isInRange(targetTime) {
51219    return targetTime >= this.start && targetTime < this.end;
51220  }
51221  markAppended() {
51222    this.appended_ = true;
51223  }
51224  resetAppendedStatus() {
51225    this.appended_ = false;
vendor: 27,055 bytes, lines 51226-51983
51226  }
51227  get isAppended() {
51228    return this.appended_;
51229  }
51230  get start() {
51231    return this.start_;
51232  }
51233  get end() {
51234    return this.end_;
51235  }
51236  get segmentIndex() {
51237    return this.segmentIndex_;
51238  }
51239  get partIndex() {
51240    return this.partIndex_;
51241  }
51242}
51243class SyncInfoData {
51244  /**
51245   *
51246   * @param {SyncInfo} segmentSyncInfo - sync info for a given segment
51247   * @param {Array<SyncInfo>} [partsSyncInfo] - sync infos for a list of parts for a given segment
51248   */
51249  constructor(segmentSyncInfo, partsSyncInfo = []) {
51250    this.segmentSyncInfo_ = segmentSyncInfo;
51251    this.partsSyncInfo_ = partsSyncInfo;
51252  }
51253  get segmentSyncInfo() {
51254    return this.segmentSyncInfo_;
51255  }
51256  get partsSyncInfo() {
51257    return this.partsSyncInfo_;
51258  }
51259  get hasPartsSyncInfo() {
51260    return this.partsSyncInfo_.length > 0;
51261  }
51262  resetAppendStatus() {
51263    this.segmentSyncInfo_.resetAppendedStatus();
51264    this.partsSyncInfo_.forEach((partSyncInfo) => partSyncInfo.resetAppendedStatus());
51265  }
51266}
51267class MediaSequenceSync {
51268  constructor() {
51269    /**
51270     * @type {Map<number, SyncInfoData>}
51271     * @protected
51272     */
51273    this.storage_ = new Map();
51274    this.diagnostics_ = '';
51275    this.isReliable_ = false;
51276    this.start_ = -Infinity;
51277    this.end_ = Infinity;
51278  }
51279  get start() {
51280    return this.start_;
51281  }
51282  get end() {
51283    return this.end_;
51284  }
51285  get diagnostics() {
51286    return this.diagnostics_;
51287  }
51288  get isReliable() {
51289    return this.isReliable_;
51290  }
51291  resetAppendedStatus() {
51292    this.storage_.forEach((syncInfoData) => syncInfoData.resetAppendStatus());
51293  }
51294  /**
51295   * update sync storage
51296   *
51297   * @param {Object} playlist
51298   * @param {number} currentTime
51299   *
51300   * @return {void}
51301   */
51302
51303  update(playlist, currentTime) {
51304    const { mediaSequence, segments } = playlist;
51305    this.isReliable_ = this.isReliablePlaylist_(mediaSequence, segments);
51306    if (!this.isReliable_) {
51307      return;
51308    }
51309    return this.updateStorage_(
51310      segments,
51311      mediaSequence,
51312      this.calculateBaseTime_(mediaSequence, segments, currentTime)
51313    );
51314  }
51315  /**
51316   * @param {number} targetTime
51317   * @return {SyncInfo|null}
51318   */
51319
51320  getSyncInfoForTime(targetTime) {
51321    for (const { segmentSyncInfo, partsSyncInfo } of this.storage_.values()) {
51322      // Normal segment flow:
51323      if (!partsSyncInfo.length) {
51324        if (segmentSyncInfo.isInRange(targetTime)) {
51325          return segmentSyncInfo;
51326        }
51327      } else {
51328        // Low latency flow:
51329        for (const partSyncInfo of partsSyncInfo) {
51330          if (partSyncInfo.isInRange(targetTime)) {
51331            return partSyncInfo;
51332          }
51333        }
51334      }
51335    }
51336    return null;
51337  }
51338  getSyncInfoForMediaSequence(mediaSequence) {
51339    return this.storage_.get(mediaSequence);
51340  }
51341  updateStorage_(segments, startingMediaSequence, startingTime) {
51342    const newStorage = new Map();
51343    let newDiagnostics = '\n';
51344    let currentStart = startingTime;
51345    let currentMediaSequence = startingMediaSequence;
51346    this.start_ = currentStart;
51347    segments.forEach((segment, segmentIndex) => {
51348      const prevSyncInfoData = this.storage_.get(currentMediaSequence);
51349      const segmentStart = currentStart;
51350      const segmentEnd = segmentStart + segment.duration;
51351      const segmentIsAppended = Boolean(
51352        prevSyncInfoData &&
51353        prevSyncInfoData.segmentSyncInfo &&
51354        prevSyncInfoData.segmentSyncInfo.isAppended
51355      );
51356      const segmentSyncInfo = new SyncInfo({
51357        start: segmentStart,
51358        end: segmentEnd,
51359        appended: segmentIsAppended,
51360        segmentIndex,
51361      });
51362      segment.syncInfo = segmentSyncInfo;
51363      let currentPartStart = currentStart;
51364      const partsSyncInfo = (segment.parts || []).map((part, partIndex) => {
51365        const partStart = currentPartStart;
51366        const partEnd = currentPartStart + part.duration;
51367        const partIsAppended = Boolean(
51368          prevSyncInfoData &&
51369          prevSyncInfoData.partsSyncInfo &&
51370          prevSyncInfoData.partsSyncInfo[partIndex] &&
51371          prevSyncInfoData.partsSyncInfo[partIndex].isAppended
51372        );
51373        const partSyncInfo = new SyncInfo({
51374          start: partStart,
51375          end: partEnd,
51376          appended: partIsAppended,
51377          segmentIndex,
51378          partIndex,
51379        });
51380        currentPartStart = partEnd;
51381        newDiagnostics += `Media Sequence: ${currentMediaSequence}.${partIndex} | Range: ${partStart} --> ${partEnd} | Appended: ${partIsAppended}\n`;
51382        part.syncInfo = partSyncInfo;
51383        return partSyncInfo;
51384      });
51385      newStorage.set(currentMediaSequence, new SyncInfoData(segmentSyncInfo, partsSyncInfo));
51386      newDiagnostics += `${compactSegmentUrlDescription(segment.resolvedUri)} | Media Sequence: ${currentMediaSequence} | Range: ${segmentStart} --> ${segmentEnd} | Appended: ${segmentIsAppended}\n`;
51387      currentMediaSequence++;
51388      currentStart = segmentEnd;
51389    });
51390    this.end_ = currentStart;
51391    this.storage_ = newStorage;
51392    this.diagnostics_ = newDiagnostics;
51393  }
51394  calculateBaseTime_(mediaSequence, segments, fallback) {
51395    if (!this.storage_.size) {
51396      // Initial setup flow.
51397      return 0;
51398    }
51399    if (this.storage_.has(mediaSequence)) {
51400      // Normal flow.
51401      return this.storage_.get(mediaSequence).segmentSyncInfo.start;
51402    }
51403    const minMediaSequenceFromStorage = Math.min(...this.storage_.keys()); // This case captures a race condition that can occur if we switch to a new media playlist that is out of date
51404    // and still has an older Media Sequence. If this occurs, we extrapolate backwards to get the base time.
51405
51406    if (mediaSequence < minMediaSequenceFromStorage) {
51407      const mediaSequenceDiff = minMediaSequenceFromStorage - mediaSequence;
51408      let baseTime = this.storage_.get(minMediaSequenceFromStorage).segmentSyncInfo.start;
51409      for (let i = 0; i < mediaSequenceDiff; i++) {
51410        const segment = segments[i];
51411        baseTime -= segment.duration;
51412      }
51413      return baseTime;
51414    } // Fallback flow.
51415    // There is a gap between last recorded playlist and a new one received.
51416
51417    return fallback;
51418  }
51419  isReliablePlaylist_(mediaSequence, segments) {
51420    return (
51421      mediaSequence !== undefined &&
51422      mediaSequence !== null &&
51423      Array.isArray(segments) &&
51424      segments.length
51425    );
51426  }
51427}
51428class DependantMediaSequenceSync extends MediaSequenceSync {
51429  constructor(parent) {
51430    super();
51431    this.parent_ = parent;
51432  }
51433  calculateBaseTime_(mediaSequence, segments, fallback) {
51434    if (!this.storage_.size) {
51435      const info = this.parent_.getSyncInfoForMediaSequence(mediaSequence);
51436      if (info) {
51437        return info.segmentSyncInfo.start;
51438      }
51439      return 0;
51440    }
51441    return super.calculateBaseTime_(mediaSequence, segments, fallback);
51442  }
51443}
51444
51445/**
51446 * @file sync-controller.js
51447 */
51448// synchronize expired playlist segments.
51449// the max media sequence diff is 48 hours of live stream
51450// content with two second segments. Anything larger than that
51451// will likely be invalid.
51452
51453const MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
51454const syncPointStrategies = [
51455  // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
51456  //                the equivalence display-time 0 === segment-index 0
51457  {
51458    name: 'VOD',
51459    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
51460      if (duration !== Infinity) {
51461        const syncPoint = {
51462          time: 0,
51463          segmentIndex: 0,
51464          partIndex: null,
51465        };
51466        return syncPoint;
51467      }
51468      return null;
51469    },
51470  },
51471  {
51472    name: 'MediaSequence',
51473    /**
51474     * run media sequence strategy
51475     *
51476     * @param {SyncController} syncController
51477     * @param {Object} playlist
51478     * @param {number} duration
51479     * @param {number} currentTimeline
51480     * @param {number} currentTime
51481     * @param {string} type
51482     */
51483    run: (syncController, playlist, duration, currentTimeline, currentTime, type) => {
51484      const mediaSequenceSync = syncController.getMediaSequenceSync(type);
51485      if (!mediaSequenceSync) {
51486        return null;
51487      }
51488      if (!mediaSequenceSync.isReliable) {
51489        return null;
51490      }
51491      const syncInfo = mediaSequenceSync.getSyncInfoForTime(currentTime);
51492      if (!syncInfo) {
51493        return null;
51494      }
51495      return {
51496        time: syncInfo.start,
51497        partIndex: syncInfo.partIndex,
51498        segmentIndex: syncInfo.segmentIndex,
51499      };
51500    },
51501  },
51502  // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
51503  {
51504    name: 'ProgramDateTime',
51505    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
51506      if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
51507        return null;
51508      }
51509      let syncPoint = null;
51510      let lastDistance = null;
51511      const partsAndSegments = getPartsAndSegments(playlist);
51512      currentTime = currentTime || 0;
51513      for (let i = 0; i < partsAndSegments.length; i++) {
51514        // start from the end and loop backwards for live
51515        // or start from the front and loop forwards for non-live
51516        const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
51517        const partAndSegment = partsAndSegments[index];
51518        const segment = partAndSegment.segment;
51519        const datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
51520        if (!datetimeMapping || !segment.dateTimeObject) {
51521          continue;
51522        }
51523        const segmentTime = segment.dateTimeObject.getTime() / 1000;
51524        let start = segmentTime + datetimeMapping; // take part duration into account.
51525
51526        if (segment.parts && typeof partAndSegment.partIndex === 'number') {
51527          for (let z = 0; z < partAndSegment.partIndex; z++) {
51528            start += segment.parts[z].duration;
51529          }
51530        }
51531        const distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
51532        // currentTime and can stop looking for better candidates
51533
51534        if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
51535          break;
51536        }
51537        lastDistance = distance;
51538        syncPoint = {
51539          time: start,
51540          segmentIndex: partAndSegment.segmentIndex,
51541          partIndex: partAndSegment.partIndex,
51542        };
51543      }
51544      return syncPoint;
51545    },
51546  },
51547  // Stategy "Segment": We have a known time mapping for a timeline and a
51548  //                    segment in the current timeline with timing data
51549  {
51550    name: 'Segment',
51551    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
51552      let syncPoint = null;
51553      let lastDistance = null;
51554      currentTime = currentTime || 0;
51555      const partsAndSegments = getPartsAndSegments(playlist);
51556      for (let i = 0; i < partsAndSegments.length; i++) {
51557        // start from the end and loop backwards for live
51558        // or start from the front and loop forwards for non-live
51559        const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
51560        const partAndSegment = partsAndSegments[index];
51561        const segment = partAndSegment.segment;
51562        const start =
51563          (partAndSegment.part && partAndSegment.part.start) || (segment && segment.start);
51564        if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
51565          const distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
51566          // currentTime and can stop looking for better candidates
51567
51568          if (lastDistance !== null && lastDistance < distance) {
51569            break;
51570          }
51571          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
51572            lastDistance = distance;
51573            syncPoint = {
51574              time: start,
51575              segmentIndex: partAndSegment.segmentIndex,
51576              partIndex: partAndSegment.partIndex,
51577            };
51578          }
51579        }
51580      }
51581      return syncPoint;
51582    },
51583  },
51584  // Stategy "Discontinuity": We have a discontinuity with a known
51585  //                          display-time
51586  {
51587    name: 'Discontinuity',
51588    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
51589      let syncPoint = null;
51590      currentTime = currentTime || 0;
51591      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
51592        let lastDistance = null;
51593        for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
51594          const segmentIndex = playlist.discontinuityStarts[i];
51595          const discontinuity = playlist.discontinuitySequence + i + 1;
51596          const discontinuitySync = syncController.discontinuities[discontinuity];
51597          if (discontinuitySync) {
51598            const distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
51599            // currentTime and can stop looking for better candidates
51600
51601            if (lastDistance !== null && lastDistance < distance) {
51602              break;
51603            }
51604            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
51605              lastDistance = distance;
51606              syncPoint = {
51607                time: discontinuitySync.time,
51608                segmentIndex,
51609                partIndex: null,
51610              };
51611            }
51612          }
51613        }
51614      }
51615      return syncPoint;
51616    },
51617  },
51618  // Stategy "Playlist": We have a playlist with a known mapping of
51619  //                     segment index to display time
51620  {
51621    name: 'Playlist',
51622    run: (syncController, playlist, duration, currentTimeline, currentTime) => {
51623      if (playlist.syncInfo) {
51624        const syncPoint = {
51625          time: playlist.syncInfo.time,
51626          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
51627          partIndex: null,
51628        };
51629        return syncPoint;
51630      }
51631      return null;
51632    },
51633  },
51634];
51635class SyncController extends videojs.EventTarget {
51636  constructor(options = {}) {
51637    super(); // ...for synching across variants
51638
51639    this.timelines = [];
51640    this.discontinuities = [];
51641    this.timelineToDatetimeMappings = {}; // TODO: this map should be only available for HLS. Since only HLS has MediaSequence.
51642    //  For some reason this map helps with syncing between quality switch for MPEG-DASH as well.
51643    //  Moreover if we disable this map for MPEG-DASH - quality switch will be broken.
51644    //  MPEG-DASH should have its own separate sync strategy
51645
51646    const main = new MediaSequenceSync();
51647    const audio = new DependantMediaSequenceSync(main);
51648    const vtt = new DependantMediaSequenceSync(main);
51649    this.mediaSequenceStorage_ = {
51650      main,
51651      audio,
51652      vtt,
51653    };
51654    this.logger_ = logger('SyncController');
51655  }
51656  /**
51657   *
51658   * @param {string} loaderType
51659   * @return {MediaSequenceSync|null}
51660   */
51661
51662  getMediaSequenceSync(loaderType) {
51663    return this.mediaSequenceStorage_[loaderType] || null;
51664  }
51665  /**
51666   * Find a sync-point for the playlist specified
51667   *
51668   * A sync-point is defined as a known mapping from display-time to
51669   * a segment-index in the current playlist.
51670   *
51671   * @param {Playlist} playlist
51672   *        The playlist that needs a sync-point
51673   * @param {number} duration
51674   *        Duration of the MediaSource (Infinite if playing a live source)
51675   * @param {number} currentTimeline
51676   *        The last timeline from which a segment was loaded
51677   * @param {number} currentTime
51678   *        Current player's time
51679   * @param {string} type
51680   *        Segment loader type
51681   * @return {Object}
51682   *          A sync-point object
51683   */
51684
51685  getSyncPoint(playlist, duration, currentTimeline, currentTime, type) {
51686    // Always use VOD sync point for VOD
51687    if (duration !== Infinity) {
51688      const vodSyncPointStrategy = syncPointStrategies.find(({ name }) => name === 'VOD');
51689      return vodSyncPointStrategy.run(this, playlist, duration);
51690    }
51691    const syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime, type);
51692    if (!syncPoints.length) {
51693      // Signal that we need to attempt to get a sync-point manually
51694      // by fetching a segment in the playlist and constructing
51695      // a sync-point from that information
51696      return null;
51697    } // If we have exact match just return it instead of finding the nearest distance
51698
51699    for (const syncPointInfo of syncPoints) {
51700      const { syncPoint, strategy } = syncPointInfo;
51701      const { segmentIndex, time } = syncPoint;
51702      if (segmentIndex < 0) {
51703        continue;
51704      }
51705      const selectedSegment = playlist.segments[segmentIndex];
51706      const start = time;
51707      const end = start + selectedSegment.duration;
51708      this.logger_(
51709        `Strategy: ${strategy}. Current time: ${currentTime}. selected segment: ${segmentIndex}. Time: [${start} -> ${end}]}`
51710      );
51711      if (currentTime >= start && currentTime < end) {
51712        this.logger_('Found sync point with exact match: ', syncPoint);
51713        return syncPoint;
51714      }
51715    } // Now find the sync-point that is closest to the currentTime because
51716    // that should result in the most accurate guess about which segment
51717    // to fetch
51718
51719    return this.selectSyncPoint_(syncPoints, {
51720      key: 'time',
51721      value: currentTime,
51722    });
51723  }
51724  /**
51725   * Calculate the amount of time that has expired off the playlist during playback
51726   *
51727   * @param {Playlist} playlist
51728   *        Playlist object to calculate expired from
51729   * @param {number} duration
51730   *        Duration of the MediaSource (Infinity if playling a live source)
51731   * @return {number|null}
51732   *          The amount of time that has expired off the playlist during playback. Null
51733   *          if no sync-points for the playlist can be found.
51734   */
51735
51736  getExpiredTime(playlist, duration) {
51737    if (!playlist || !playlist.segments) {
51738      return null;
51739    }
51740    const syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
51741
51742    if (!syncPoints.length) {
51743      return null;
51744    }
51745    const syncPoint = this.selectSyncPoint_(syncPoints, {
51746      key: 'segmentIndex',
51747      value: 0,
51748    }); // If the sync-point is beyond the start of the playlist, we want to subtract the
51749    // duration from index 0 to syncPoint.segmentIndex instead of adding.
51750
51751    if (syncPoint.segmentIndex > 0) {
51752      syncPoint.time *= -1;
51753    }
51754    return Math.abs(
51755      syncPoint.time +
51756        sumDurations({
51757          defaultDuration: playlist.targetDuration,
51758          durationList: playlist.segments,
51759          startIndex: syncPoint.segmentIndex,
51760          endIndex: 0,
51761        })
51762    );
51763  }
51764  /**
51765   * Runs each sync-point strategy and returns a list of sync-points returned by the
51766   * strategies
51767   *
51768   * @private
51769   * @param {Playlist} playlist
51770   *        The playlist that needs a sync-point
51771   * @param {number} duration
51772   *        Duration of the MediaSource (Infinity if playing a live source)
51773   * @param {number} currentTimeline
51774   *        The last timeline from which a segment was loaded
51775   * @param {number} currentTime
51776   *        Current player's time
51777   * @param {string} type
51778   *        Segment loader type
51779   * @return {Array}
51780   *          A list of sync-point objects
51781   */
51782
51783  runStrategies_(playlist, duration, currentTimeline, currentTime, type) {
51784    const syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
51785
51786    for (let i = 0; i < syncPointStrategies.length; i++) {
51787      const strategy = syncPointStrategies[i];
51788      const syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime, type);
51789      if (syncPoint) {
51790        syncPoint.strategy = strategy.name;
51791        syncPoints.push({
51792          strategy: strategy.name,
51793          syncPoint,
51794        });
51795      }
51796    }
51797    return syncPoints;
51798  }
51799  /**
51800   * Selects the sync-point nearest the specified target
51801   *
51802   * @private
51803   * @param {Array} syncPoints
51804   *        List of sync-points to select from
51805   * @param {Object} target
51806   *        Object specifying the property and value we are targeting
51807   * @param {string} target.key
51808   *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
51809   * @param {number} target.value
51810   *        The value to target for the specified key.
51811   * @return {Object}
51812   *          The sync-point nearest the target
51813   */
51814
51815  selectSyncPoint_(syncPoints, target) {
51816    let bestSyncPoint = syncPoints[0].syncPoint;
51817    let bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
51818    let bestStrategy = syncPoints[0].strategy;
51819    for (let i = 1; i < syncPoints.length; i++) {
51820      const newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
51821      if (newDistance < bestDistance) {
51822        bestDistance = newDistance;
51823        bestSyncPoint = syncPoints[i].syncPoint;
51824        bestStrategy = syncPoints[i].strategy;
51825      }
51826    }
51827    this.logger_(
51828      `syncPoint for [${target.key}: ${target.value}] chosen with strategy` +
51829        ` [${bestStrategy}]: [time:${bestSyncPoint.time},` +
51830        ` segmentIndex:${bestSyncPoint.segmentIndex}` +
51831        (typeof bestSyncPoint.partIndex === 'number'
51832          ? `,partIndex:${bestSyncPoint.partIndex}`
51833          : '') +
51834        ']'
51835    );
51836    return bestSyncPoint;
51837  }
51838  /**
51839   * Save any meta-data present on the segments when segments leave
51840   * the live window to the playlist to allow for synchronization at the
51841   * playlist level later.
51842   *
51843   * @param {Playlist} oldPlaylist - The previous active playlist
51844   * @param {Playlist} newPlaylist - The updated and most current playlist
51845   */
51846
51847  saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
51848    const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
51849
51850    if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
51851      videojs.log.warn(
51852        `Not saving expired segment info. Media sequence gap ${mediaSequenceDiff} is too large.`
51853      );
51854      return;
51855    } // When a segment expires from the playlist and it has a start time
51856    // save that information as a possible sync-point reference in future
51857
51858    for (let i = mediaSequenceDiff - 1; i >= 0; i--) {
51859      const lastRemovedSegment = oldPlaylist.segments[i];
51860      if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
51861        newPlaylist.syncInfo = {
51862          mediaSequence: oldPlaylist.mediaSequence + i,
51863          time: lastRemovedSegment.start,
51864        };
51865        this.logger_(
51866          `playlist refresh sync: [time:${newPlaylist.syncInfo.time},` +
51867            ` mediaSequence: ${newPlaylist.syncInfo.mediaSequence}]`
51868        );
51869        this.trigger('syncinfoupdate');
51870        break;
51871      }
51872    }
51873  }
51874  /**
51875   * Save the mapping from playlist's ProgramDateTime to display. This should only happen
51876   * before segments start to load.
51877   *
51878   * @param {Playlist} playlist - The currently active playlist
51879   */
51880
51881  setDateTimeMappingForStart(playlist) {
51882    // It's possible for the playlist to be updated before playback starts, meaning time
51883    // zero is not yet set. If, during these playlist refreshes, a discontinuity is
51884    // crossed, then the old time zero mapping (for the prior timeline) would be retained
51885    // unless the mappings are cleared.
51886    this.timelineToDatetimeMappings = {};
51887    if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
51888      const firstSegment = playlist.segments[0];
51889      const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
51890      this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
51891    }
51892  }
51893  /**
51894   * Calculates and saves timeline mappings, playlist sync info, and segment timing values
51895   * based on the latest timing information.
51896   *
51897   * @param {Object} options
51898   *        Options object
51899   * @param {SegmentInfo} options.segmentInfo
51900   *        The current active request information
51901   * @param {boolean} options.shouldSaveTimelineMapping
51902   *        If there's a timeline change, determines if the timeline mapping should be
51903   *        saved for timeline mapping and program date time mappings.
51904   */
51905
51906  saveSegmentTimingInfo({ segmentInfo, shouldSaveTimelineMapping }) {
51907    const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(
51908      segmentInfo,
51909      segmentInfo.timingInfo,
51910      shouldSaveTimelineMapping
51911    );
51912    const segment = segmentInfo.segment;
51913    if (didCalculateSegmentTimeMapping) {
51914      this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
51915      // now with segment timing information
51916
51917      if (!segmentInfo.playlist.syncInfo) {
51918        segmentInfo.playlist.syncInfo = {
51919          mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
51920          time: segment.start,
51921        };
51922      }
51923    }
51924    const dateTime = segment.dateTimeObject;
51925    if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
51926      this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
51927    }
51928  }
51929  timestampOffsetForTimeline(timeline) {
51930    if (typeof this.timelines[timeline] === 'undefined') {
51931      return null;
51932    }
51933    return this.timelines[timeline].time;
51934  }
51935  mappingForTimeline(timeline) {
51936    if (typeof this.timelines[timeline] === 'undefined') {
51937      return null;
51938    }
51939    return this.timelines[timeline].mapping;
51940  }
51941  /**
51942   * Use the "media time" for a segment to generate a mapping to "display time" and
51943   * save that display time to the segment.
51944   *
51945   * @private
51946   * @param {SegmentInfo} segmentInfo
51947   *        The current active request information
51948   * @param {Object} timingInfo
51949   *        The start and end time of the current segment in "media time"
51950   * @param {boolean} shouldSaveTimelineMapping
51951   *        If there's a timeline change, determines if the timeline mapping should be
51952   *        saved in timelines.
51953   * @return {boolean}
51954   *          Returns false if segment time mapping could not be calculated
51955   */
51956
51957  calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
51958    // TODO: remove side effects
51959    const segment = segmentInfo.segment;
51960    const part = segmentInfo.part;
51961    let mappingObj = this.timelines[segmentInfo.timeline];
51962    let start;
51963    let end;
51964    if (typeof segmentInfo.timestampOffset === 'number') {
51965      mappingObj = {
51966        time: segmentInfo.startOfSegment,
51967        mapping: segmentInfo.startOfSegment - timingInfo.start,
51968      };
51969      if (shouldSaveTimelineMapping) {
51970        this.timelines[segmentInfo.timeline] = mappingObj;
51971        this.trigger('timestampoffset');
51972        this.logger_(
51973          `time mapping for timeline ${segmentInfo.timeline}: ` +
51974            `[time: ${mappingObj.time}] [mapping: ${mappingObj.mapping}]`
51975        );
51976      }
51977      start = segmentInfo.startOfSegment;
51978      end = timingInfo.end + mappingObj.mapping;
51979    } else if (mappingObj) {
51980      start = timingInfo.start + mappingObj.mapping;
51981      end = timingInfo.end + mappingObj.mapping;
51982    } else {
51983      return false;
51984    }
51985    if (part) {
51986      part.start = start;
51987      part.end = end;
51988    } // If we don't have a segment start yet or the start value we got
51989    // is less than our current segment.start value, save a new start value.
51990    // We have to do this because parts will have segment timing info saved
51991    // multiple times and we want segment start to be the earliest part start
51992    // value for that segment.
51993
51994    if (!segment.start || start < segment.start) {
51995      segment.start = start;
51996    }
51997    segment.end = end;
51998    return true;
51999  }
52000  /**
52001   * Each time we have discontinuity in the playlist, attempt to calculate the location
52002   * in display of the start of the discontinuity and save that. We also save an accuracy
52003   * value so that we save values with the most accuracy (closest to 0.)
52004   *
52005   * @private
52006   * @param {SegmentInfo} segmentInfo - The current active request information
52007   */
52008
52009  saveDiscontinuitySyncInfo_(segmentInfo) {
52010    const playlist = segmentInfo.playlist;
52011    const segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
52012    // the start of the range and it's accuracy is 0 (greater accuracy values
52013    // mean more approximation)
52014
52015    if (segment.discontinuity) {
52016      this.discontinuities[segment.timeline] = {
52017        time: segment.start,
52018        accuracy: 0,
52019      };
52020    } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
52021      // Search for future discontinuities that we can provide better timing
52022      // information for and save that information for sync purposes
52023      for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
52024        const segmentIndex = playlist.discontinuityStarts[i];
52025        const discontinuity = playlist.discontinuitySequence + i + 1;
52026        const mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
52027        const accuracy = Math.abs(mediaIndexDiff);
52028        if (
52029          !this.discontinuities[discontinuity] ||
52030          this.discontinuities[discontinuity].accuracy > accuracy
52031        ) {
52032          let time;
52033          if (mediaIndexDiff < 0) {
52034            time =
52035              segment.start -
52036              sumDurations({
52037                defaultDuration: playlist.targetDuration,
52038                durationList: playlist.segments,
52039                startIndex: segmentInfo.mediaIndex,
52040                endIndex: segmentIndex,
52041              });
52042          } else {
52043            time =
52044              segment.end +
52045              sumDurations({
52046                defaultDuration: playlist.targetDuration,
52047                durationList: playlist.segments,
52048                startIndex: segmentInfo.mediaIndex + 1,
52049                endIndex: segmentIndex,
52050              });
52051          }
52052          this.discontinuities[discontinuity] = {
52053            time,
52054            accuracy,
52055          };
52056        }
52057      }
52058    }
52059  }
52060  dispose() {
52061    this.trigger('dispose');
52062    this.off();
52063  }
52064}
52065
52066/**
52067 * The TimelineChangeController acts as a source for segment loaders to listen for and
52068 * keep track of latest and pending timeline changes. This is useful to ensure proper
52069 * sync, as each loader may need to make a consideration for what timeline the other
52070 * loader is on before making changes which could impact the other loader's media.
52071 *
52072 * @class TimelineChangeController
52073 * @extends videojs.EventTarget
52074 */
52075
52076class TimelineChangeController extends videojs.EventTarget {
52077  constructor() {
52078    super();
52079    this.pendingTimelineChanges_ = {};
52080    this.lastTimelineChanges_ = {};
52081  }
52082  clearPendingTimelineChange(type) {
52083    this.pendingTimelineChanges_[type] = null;
52084    this.trigger('pendingtimelinechange');
52085  }
52086  pendingTimelineChange({ type, from, to }) {
52087    if (typeof from === 'number' && typeof to === 'number') {
52088      this.pendingTimelineChanges_[type] = {
52089        type,
52090        from,
52091        to,
52092      };
52093      this.trigger('pendingtimelinechange');
52094    }
52095    return this.pendingTimelineChanges_[type];
52096  }
52097  lastTimelineChange({ type, from, to }) {
52098    if (typeof from === 'number' && typeof to === 'number') {
52099      this.lastTimelineChanges_[type] = {
52100        type,
52101        from,
52102        to,
52103      };
52104      delete this.pendingTimelineChanges_[type];
52105      const metadata = {
52106        timelineChangeInfo: {
52107          from,
52108          to,
52109        },
52110      };
52111      this.trigger({
52112        type: 'timelinechange',
52113        metadata,
52114      });
52115    }
52116    return this.lastTimelineChanges_[type];
52117  }
52118  dispose() {
52119    this.trigger('dispose');
52120    this.pendingTimelineChanges_ = {};
52121    this.lastTimelineChanges_ = {};
52122    this.off();
52123  }
52124}
52125
52126/* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/decrypter-worker.js */
52127const workerCode = transform(
52128  getWorkerString(function () {
52129    /**
52130     * @file stream.js
52131     */
52132
52133    /**
52134     * A lightweight readable stream implemention that handles event dispatching.
52135     *
52136     * @class Stream
52137     */
52138
52139    var Stream = /*#__PURE__*/ (function () {
52140      function Stream() {
52141        this.listeners = {};
52142      }
52143      /**
52144       * Add a listener for a specified event type.
52145       *
52146       * @param {string} type the event name
52147       * @param {Function} listener the callback to be invoked when an event of
52148       * the specified type occurs
52149       */
52150
52151      var _proto = Stream.prototype;
52152      _proto.on = function on(type, listener) {
52153        if (!this.listeners[type]) {
52154          this.listeners[type] = [];
52155        }
52156        this.listeners[type].push(listener);
52157      };
52158      /**
52159       * Remove a listener for a specified event type.
52160       *
52161       * @param {string} type the event name
52162       * @param {Function} listener  a function previously registered for this
52163       * type of event through `on`
52164       * @return {boolean} if we could turn it off or not
52165       */
52166      _proto.off = function off(type, listener) {
52167        if (!this.listeners[type]) {
52168          return false;
vendor: 4,120 bytes, lines 52169-52273
52169        }
52170        var index = this.listeners[type].indexOf(listener); // TODO: which is better?
52171        // In Video.js we slice listener functions
52172        // on trigger so that it does not mess up the order
52173        // while we loop through.
52174        //
52175        // Here we slice on off so that the loop in trigger
52176        // can continue using it's old reference to loop without
52177        // messing up the order.
52178
52179        this.listeners[type] = this.listeners[type].slice(0);
52180        this.listeners[type].splice(index, 1);
52181        return index > -1;
52182      };
52183      /**
52184       * Trigger an event of the specified type on this stream. Any additional
52185       * arguments to this function are passed as parameters to event listeners.
52186       *
52187       * @param {string} type the event name
52188       */
52189      _proto.trigger = function trigger(type) {
52190        var callbacks = this.listeners[type];
52191        if (!callbacks) {
52192          return;
52193        } // Slicing the arguments on every invocation of this method
52194        // can add a significant amount of overhead. Avoid the
52195        // intermediate object creation for the common case of a
52196        // single callback argument
52197
52198        if (arguments.length === 2) {
52199          var length = callbacks.length;
52200          for (var i = 0; i < length; ++i) {
52201            callbacks[i].call(this, arguments[1]);
52202          }
52203        } else {
52204          var args = Array.prototype.slice.call(arguments, 1);
52205          var _length = callbacks.length;
52206          for (var _i = 0; _i < _length; ++_i) {
52207            callbacks[_i].apply(this, args);
52208          }
52209        }
52210      };
52211      /**
52212       * Destroys the stream and cleans up.
52213       */
52214      _proto.dispose = function dispose() {
52215        this.listeners = {};
52216      };
52217      /**
52218       * Forwards all `data` events on this stream to the destination stream. The
52219       * destination stream should provide a method `push` to receive the data
52220       * events as they arrive.
52221       *
52222       * @param {Stream} destination the stream that will receive all `data` events
52223       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
52224       */
52225      _proto.pipe = function pipe(destination) {
52226        this.on('data', function (data) {
52227          destination.push(data);
52228        });
52229      };
52230      return Stream;
52231    })();
52232    /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
52233
52234    /**
52235     * Returns the subarray of a Uint8Array without PKCS#7 padding.
52236     *
52237     * @param padded {Uint8Array} unencrypted bytes that have been padded
52238     * @return {Uint8Array} the unpadded bytes
52239     * @see http://tools.ietf.org/html/rfc5652
52240     */
52241
52242    function unpad(padded) {
52243      return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
52244    }
52245    /*! @name aes-decrypter @version 4.0.2 @license Apache-2.0 */
52246
52247    /**
52248     * @file aes.js
52249     *
52250     * This file contains an adaptation of the AES decryption algorithm
52251     * from the Standford Javascript Cryptography Library. That work is
52252     * covered by the following copyright and permissions notice:
52253     *
52254     * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
52255     * All rights reserved.
52256     *
52257     * Redistribution and use in source and binary forms, with or without
52258     * modification, are permitted provided that the following conditions are
52259     * met:
52260     *
52261     * 1. Redistributions of source code must retain the above copyright
52262     *    notice, this list of conditions and the following disclaimer.
52263     *
52264     * 2. Redistributions in binary form must reproduce the above
52265     *    copyright notice, this list of conditions and the following
52266     *    disclaimer in the documentation and/or other materials provided
52267     *    with the distribution.
52268     *
52269     * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
52270     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52271     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
52272     * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
52273     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
52274     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
52275     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
52276     * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
52277     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
52278     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
52279     * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52280     *
52281     * The views and conclusions contained in the software and documentation
52282     * are those of the authors and should not be interpreted as representing
52283     * official policies, either expressed or implied, of the authors.
52284     */
52285
52286    /**
52287     * Expand the S-box tables.
52288     *
52289     * @private
52290     */
52291
52292    const precompute = function () {
52293      const tables = [
52294        [[], [], [], [], []],
52295        [[], [], [], [], []],
52296      ];
52297      const encTable = tables[0];
52298      const decTable = tables[1];
52299      const sbox = encTable[4];
52300      const sboxInv = decTable[4];
52301      let i;
52302      let x;
52303      let xInv;
52304      const d = [];
52305      const th = [];
52306      let x2;
52307      let x4;
52308      let x8;
52309      let s;
52310      let tEnc;
52311      let tDec; // Compute double and third tables
52312
52313      for (i = 0; i < 256; i++) {
52314        th[(d[i] = (i << 1) ^ ((i >> 7) * 283)) ^ i] = i;
52315      }
52316      for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
52317        // Compute sbox
52318        s = xInv ^ (xInv << 1) ^ (xInv << 2) ^ (xInv << 3) ^ (xInv << 4);
52319        s = (s >> 8) ^ (s & 255) ^ 99;
52320        sbox[x] = s;
52321        sboxInv[s] = x; // Compute MixColumns
52322
52323        x8 = d[(x4 = d[(x2 = d[x])])];
52324        tDec = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
52325        tEnc = (d[s] * 0x101) ^ (s * 0x1010100);
52326        for (i = 0; i < 4; i++) {
52327          encTable[i][x] = tEnc = (tEnc << 24) ^ (tEnc >>> 8);
52328          decTable[i][s] = tDec = (tDec << 24) ^ (tDec >>> 8);
52329        }
52330      } // Compactify. Considerable speedup on Firefox.
52331
52332      for (i = 0; i < 5; i++) {
52333        encTable[i] = encTable[i].slice(0);
52334        decTable[i] = decTable[i].slice(0);
52335      }
52336      return tables;
52337    };
52338    let aesTables = null;
52339    /**
52340     * Schedule out an AES key for both encryption and decryption. This
52341     * is a low-level class. Use a cipher mode to do bulk encryption.
52342     *
52343     * @class AES
52344     * @param key {Array} The key as an array of 4, 6 or 8 words.
52345     */
52346
52347    class AES {
52348      constructor(key) {
52349        /**
52350         * The expanded S-box and inverse S-box tables. These will be computed
52351         * on the client so that we don't have to send them down the wire.
52352         *
52353         * There are two tables, _tables[0] is for encryption and
52354         * _tables[1] is for decryption.
52355         *
52356         * The first 4 sub-tables are the expanded S-box with MixColumns. The
52357         * last (_tables[01][4]) is the S-box itself.
52358         *
52359         * @private
52360         */
52361        // if we have yet to precompute the S-box tables
52362        // do so now
52363        if (!aesTables) {
52364          aesTables = precompute();
52365        } // then make a copy of that object for use
52366
52367        this._tables = [
52368          [
52369            aesTables[0][0].slice(),
52370            aesTables[0][1].slice(),
52371            aesTables[0][2].slice(),
52372            aesTables[0][3].slice(),
52373            aesTables[0][4].slice(),
52374          ],
52375          [
52376            aesTables[1][0].slice(),
52377            aesTables[1][1].slice(),
52378            aesTables[1][2].slice(),
52379            aesTables[1][3].slice(),
52380            aesTables[1][4].slice(),
52381          ],
52382        ];
52383        let i;
52384        let j;
52385        let tmp;
52386        const sbox = this._tables[0][4];
52387        const decTable = this._tables[1];
52388        const keyLen = key.length;
52389        let rcon = 1;
52390        if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
52391          throw new Error('Invalid aes key size');
52392        }
52393        const encKey = key.slice(0);
52394        const decKey = [];
52395        this._key = [encKey, decKey]; // schedule encryption keys
52396
52397        for (i = keyLen; i < 4 * keyLen + 28; i++) {
52398          tmp = encKey[i - 1]; // apply sbox
52399
52400          if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
52401            tmp =
52402              (sbox[tmp >>> 24] << 24) ^
52403              (sbox[(tmp >> 16) & 255] << 16) ^
52404              (sbox[(tmp >> 8) & 255] << 8) ^
52405              sbox[tmp & 255]; // shift rows and add rcon
52406
52407            if (i % keyLen === 0) {
52408              tmp = (tmp << 8) ^ (tmp >>> 24) ^ (rcon << 24);
52409              rcon = (rcon << 1) ^ ((rcon >> 7) * 283);
52410            }
52411          }
52412          encKey[i] = encKey[i - keyLen] ^ tmp;
52413        } // schedule decryption keys
52414
52415        for (j = 0; i; j++, i--) {
52416          tmp = encKey[j & 3 ? i : i - 4];
52417          if (i <= 4 || j < 4) {
52418            decKey[j] = tmp;
52419          } else {
52420            decKey[j] =
52421              decTable[0][sbox[tmp >>> 24]] ^
52422              decTable[1][sbox[(tmp >> 16) & 255]] ^
52423              decTable[2][sbox[(tmp >> 8) & 255]] ^
52424              decTable[3][sbox[tmp & 255]];
52425          }
52426        }
52427      }
52428      /**
52429       * Decrypt 16 bytes, specified as four 32-bit words.
52430       *
52431       * @param {number} encrypted0 the first word to decrypt
52432       * @param {number} encrypted1 the second word to decrypt
52433       * @param {number} encrypted2 the third word to decrypt
52434       * @param {number} encrypted3 the fourth word to decrypt
52435       * @param {Int32Array} out the array to write the decrypted words
52436       * into
52437       * @param {number} offset the offset into the output array to start
52438       * writing results
52439       * @return {Array} The plaintext.
52440       */
52441
52442      decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
52443        const key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
52444
52445        let a = encrypted0 ^ key[0];
52446        let b = encrypted3 ^ key[1];
52447        let c = encrypted2 ^ key[2];
52448        let d = encrypted1 ^ key[3];
52449        let a2;
52450        let b2;
52451        let c2; // key.length === 2 ?
52452
52453        const nInnerRounds = key.length / 4 - 2;
52454        let i;
52455        let kIndex = 4;
52456        const table = this._tables[1]; // load up the tables
52457
52458        const table0 = table[0];
52459        const table1 = table[1];
52460        const table2 = table[2];
52461        const table3 = table[3];
52462        const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
52463
52464        for (i = 0; i < nInnerRounds; i++) {
52465          a2 =
52466            table0[a >>> 24] ^
52467            table1[(b >> 16) & 255] ^
52468            table2[(c >> 8) & 255] ^
52469            table3[d & 255] ^
52470            key[kIndex];
52471          b2 =
52472            table0[b >>> 24] ^
52473            table1[(c >> 16) & 255] ^
52474            table2[(d >> 8) & 255] ^
52475            table3[a & 255] ^
52476            key[kIndex + 1];
52477          c2 =
52478            table0[c >>> 24] ^
52479            table1[(d >> 16) & 255] ^
52480            table2[(a >> 8) & 255] ^
52481            table3[b & 255] ^
52482            key[kIndex + 2];
52483          d =
52484            table0[d >>> 24] ^
52485            table1[(a >> 16) & 255] ^
52486            table2[(b >> 8) & 255] ^
52487            table3[c & 255] ^
52488            key[kIndex + 3];
52489          kIndex += 4;
52490          a = a2;
52491          b = b2;
52492          c = c2;
52493        } // Last round.
52494
52495        for (i = 0; i < 4; i++) {
52496          out[(3 & -i) + offset] =
52497            (sbox[a >>> 24] << 24) ^
52498            (sbox[(b >> 16) & 255] << 16) ^
52499            (sbox[(c >> 8) & 255] << 8) ^
52500            sbox[d & 255] ^
52501            key[kIndex++];
52502          a2 = a;
52503          a = b;
52504          b = c;
52505          c = d;
52506          d = a2;
52507        }
52508      }
52509    }
52510    /**
52511     * @file async-stream.js
52512     */
52513
52514    /**
52515     * A wrapper around the Stream class to use setTimeout
52516     * and run stream "jobs" Asynchronously
52517     *
52518     * @class AsyncStream
52519     * @extends Stream
52520     */
52521
52522    class AsyncStream extends Stream {
52523      constructor() {
52524        super(Stream);
52525        this.jobs = [];
52526        this.delay = 1;
52527        this.timeout_ = null;
52528      }
52529      /**
52530       * process an async job
52531       *
52532       * @private
52533       */
52534
52535      processJob_() {
52536        this.jobs.shift()();
52537        if (this.jobs.length) {
52538          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
52539        } else {
52540          this.timeout_ = null;
52541        }
52542      }
52543      /**
52544       * push a job into the stream
52545       *
52546       * @param {Function} job the job to push into the stream
52547       */
52548
52549      push(job) {
52550        this.jobs.push(job);
52551        if (!this.timeout_) {
52552          this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
52553        }
52554      }
52555    }
52556    /**
52557     * @file decrypter.js
52558     *
52559     * An asynchronous implementation of AES-128 CBC decryption with
52560     * PKCS#7 padding.
52561     */
52562
52563    /**
52564     * Convert network-order (big-endian) bytes into their little-endian
52565     * representation.
52566     */
52567
52568    const ntoh = function (word) {
52569      return (word << 24) | ((word & 0xff00) << 8) | ((word & 0xff0000) >> 8) | (word >>> 24);
52570    };
52571    /**
52572     * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
52573     *
52574     * @param {Uint8Array} encrypted the encrypted bytes
52575     * @param {Uint32Array} key the bytes of the decryption key
52576     * @param {Uint32Array} initVector the initialization vector (IV) to
vendor: 15,759 bytes, lines 52577-53032
52577     * use for the first round of CBC.
52578     * @return {Uint8Array} the decrypted bytes
52579     *
52580     * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
52581     * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
52582     * @see https://tools.ietf.org/html/rfc2315
52583     */
52584
52585    const decrypt = function (encrypted, key, initVector) {
52586      // word-level access to the encrypted bytes
52587      const encrypted32 = new Int32Array(
52588        encrypted.buffer,
52589        encrypted.byteOffset,
52590        encrypted.byteLength >> 2
52591      );
52592      const decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
52593
52594      const decrypted = new Uint8Array(encrypted.byteLength);
52595      const decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
52596      // decrypted data
52597
52598      let init0;
52599      let init1;
52600      let init2;
52601      let init3;
52602      let encrypted0;
52603      let encrypted1;
52604      let encrypted2;
52605      let encrypted3; // iteration variable
52606
52607      let wordIx; // pull out the words of the IV to ensure we don't modify the
52608      // passed-in reference and easier access
52609
52610      init0 = initVector[0];
52611      init1 = initVector[1];
52612      init2 = initVector[2];
52613      init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
52614      // to each decrypted block
52615
52616      for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
52617        // convert big-endian (network order) words into little-endian
52618        // (javascript order)
52619        encrypted0 = ntoh(encrypted32[wordIx]);
52620        encrypted1 = ntoh(encrypted32[wordIx + 1]);
52621        encrypted2 = ntoh(encrypted32[wordIx + 2]);
52622        encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
52623
52624        decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
52625        // plaintext
52626
52627        decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
52628        decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
52629        decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
52630        decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
52631
52632        init0 = encrypted0;
52633        init1 = encrypted1;
52634        init2 = encrypted2;
52635        init3 = encrypted3;
52636      }
52637      return decrypted;
52638    };
52639    /**
52640     * The `Decrypter` class that manages decryption of AES
52641     * data through `AsyncStream` objects and the `decrypt`
52642     * function
52643     *
52644     * @param {Uint8Array} encrypted the encrypted bytes
52645     * @param {Uint32Array} key the bytes of the decryption key
52646     * @param {Uint32Array} initVector the initialization vector (IV) to
52647     * @param {Function} done the function to run when done
52648     * @class Decrypter
52649     */
52650
52651    class Decrypter {
52652      constructor(encrypted, key, initVector, done) {
52653        const step = Decrypter.STEP;
52654        const encrypted32 = new Int32Array(encrypted.buffer);
52655        const decrypted = new Uint8Array(encrypted.byteLength);
52656        let i = 0;
52657        this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
52658
52659        this.asyncStream_.push(
52660          this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)
52661        );
52662        for (i = step; i < encrypted32.length; i += step) {
52663          initVector = new Uint32Array([
52664            ntoh(encrypted32[i - 4]),
52665            ntoh(encrypted32[i - 3]),
52666            ntoh(encrypted32[i - 2]),
52667            ntoh(encrypted32[i - 1]),
52668          ]);
52669          this.asyncStream_.push(
52670            this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)
52671          );
52672        } // invoke the done() callback when everything is finished
52673
52674        this.asyncStream_.push(function () {
52675          // remove pkcs#7 padding from the decrypted bytes
52676          done(null, unpad(decrypted));
52677        });
52678      }
52679      /**
52680       * a getter for step the maximum number of bytes to process at one time
52681       *
52682       * @return {number} the value of step 32000
52683       */
52684
52685      static get STEP() {
52686        // 4 * 8000;
52687        return 32000;
52688      }
52689      /**
52690       * @private
52691       */
52692
52693      decryptChunk_(encrypted, key, initVector, decrypted) {
52694        return function () {
52695          const bytes = decrypt(encrypted, key, initVector);
52696          decrypted.set(bytes, encrypted.byteOffset);
52697        };
52698      }
52699    }
52700    var commonjsGlobal =
52701      typeof globalThis !== 'undefined'
52702        ? globalThis
52703        : typeof window !== 'undefined'
52704          ? window
52705          : typeof global !== 'undefined'
52706            ? global
52707            : typeof self !== 'undefined'
52708              ? self
52709              : {};
52710    var win;
52711    if (typeof window !== 'undefined') {
52712      win = window;
52713    } else if (typeof commonjsGlobal !== 'undefined') {
52714      win = commonjsGlobal;
52715    } else if (typeof self !== 'undefined') {
52716      win = self;
52717    } else {
52718      win = {};
52719    }
52720    var window_1 = win;
52721    var isArrayBufferView = function isArrayBufferView(obj) {
52722      if (ArrayBuffer.isView === 'function') {
52723        return ArrayBuffer.isView(obj);
52724      }
52725      return obj && obj.buffer instanceof ArrayBuffer;
52726    };
52727    var BigInt = window_1.BigInt || Number;
52728    [
52729      BigInt('0x1'),
52730      BigInt('0x100'),
52731      BigInt('0x10000'),
52732      BigInt('0x1000000'),
52733      BigInt('0x100000000'),
52734      BigInt('0x10000000000'),
52735      BigInt('0x1000000000000'),
52736      BigInt('0x100000000000000'),
52737      BigInt('0x10000000000000000'),
52738    ];
52739    (function () {
52740      var a = new Uint16Array([0xffcc]);
52741      var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
52742      if (b[0] === 0xff) {
52743        return 'big';
52744      }
52745      if (b[0] === 0xcc) {
52746        return 'little';
52747      }
52748      return 'unknown';
52749    })();
52750    /**
52751     * Creates an object for sending to a web worker modifying properties that are TypedArrays
52752     * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
52753     *
52754     * @param {Object} message
52755     *        Object of properties and values to send to the web worker
52756     * @return {Object}
52757     *         Modified message with TypedArray values expanded
52758     * @function createTransferableMessage
52759     */
52760
52761    const createTransferableMessage = function (message) {
52762      const transferable = {};
52763      Object.keys(message).forEach((key) => {
52764        const value = message[key];
52765        if (isArrayBufferView(value)) {
52766          transferable[key] = {
52767            bytes: value.buffer,
52768            byteOffset: value.byteOffset,
52769            byteLength: value.byteLength,
52770          };
52771        } else {
52772          transferable[key] = value;
52773        }
52774      });
52775      return transferable;
52776    };
52777    /* global self */
52778
52779    /**
52780     * Our web worker interface so that things can talk to aes-decrypter
52781     * that will be running in a web worker. the scope is passed to this by
52782     * webworkify.
52783     */
52784
52785    self.onmessage = function (event) {
52786      const data = event.data;
52787      const encrypted = new Uint8Array(
52788        data.encrypted.bytes,
52789        data.encrypted.byteOffset,
52790        data.encrypted.byteLength
52791      );
52792      const key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
52793      const iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
52794      /* eslint-disable no-new, handle-callback-err */
52795
52796      new Decrypter(encrypted, key, iv, function (err, bytes) {
52797        self.postMessage(
52798          createTransferableMessage({
52799            source: data.source,
52800            decrypted: bytes,
52801          }),
52802          [bytes.buffer]
52803        );
52804      });
52805      /* eslint-enable */
52806    };
52807  })
52808);
52809var Decrypter = factory(workerCode);
52810/* rollup-plugin-worker-factory end for worker!/home/runner/work/http-streaming/http-streaming/src/decrypter-worker.js */
52811
52812/**
52813 * Convert the properties of an HLS track into an audioTrackKind.
52814 *
52815 * @private
52816 */
52817
52818const audioTrackKind_ = (properties) => {
52819  let kind = properties.default ? 'main' : 'alternative';
52820  if (
52821    properties.characteristics &&
52822    properties.characteristics.indexOf('public.accessibility.describes-video') >= 0
52823  ) {
52824    kind = 'main-desc';
52825  }
52826  return kind;
52827};
52828/**
52829 * Pause provided segment loader and playlist loader if active
52830 *
52831 * @param {SegmentLoader} segmentLoader
52832 *        SegmentLoader to pause
52833 * @param {Object} mediaType
52834 *        Active media type
52835 * @function stopLoaders
52836 */
52837
52838const stopLoaders = (segmentLoader, mediaType) => {
52839  segmentLoader.abort();
52840  segmentLoader.pause();
52841  if (mediaType && mediaType.activePlaylistLoader) {
52842    mediaType.activePlaylistLoader.pause();
52843    mediaType.activePlaylistLoader = null;
52844  }
52845};
52846/**
52847 * Start loading provided segment loader and playlist loader
52848 *
52849 * @param {PlaylistLoader} playlistLoader
52850 *        PlaylistLoader to start loading
52851 * @param {Object} mediaType
52852 *        Active media type
52853 * @function startLoaders
52854 */
52855
52856const startLoaders = (playlistLoader, mediaType) => {
52857  // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
52858  // playlist loader
52859  mediaType.activePlaylistLoader = playlistLoader;
52860  playlistLoader.load();
52861};
52862/**
52863 * Returns a function to be called when the media group changes. It performs a
52864 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
52865 * change of group is merely a rendition switch of the same content at another encoding,
52866 * rather than a change of content, such as switching audio from English to Spanish.
52867 *
52868 * @param {string} type
52869 *        MediaGroup type
52870 * @param {Object} settings
52871 *        Object containing required information for media groups
52872 * @return {Function}
52873 *         Handler for a non-destructive resync of SegmentLoader when the active media
52874 *         group changes.
52875 * @function onGroupChanged
52876 */
52877
52878const onGroupChanged = (type, settings) => () => {
52879  const {
52880    segmentLoaders: { [type]: segmentLoader, main: mainSegmentLoader },
52881    mediaTypes: { [type]: mediaType },
52882  } = settings;
52883  const activeTrack = mediaType.activeTrack();
52884  const activeGroup = mediaType.getActiveGroup();
52885  const previousActiveLoader = mediaType.activePlaylistLoader;
52886  const lastGroup = mediaType.lastGroup_; // the group did not change do nothing
52887
52888  if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
52889    return;
52890  }
52891  mediaType.lastGroup_ = activeGroup;
52892  mediaType.lastTrack_ = activeTrack;
52893  stopLoaders(segmentLoader, mediaType);
52894  if (!activeGroup || activeGroup.isMainPlaylist) {
52895    // there is no group active or active group is a main playlist and won't change
52896    return;
52897  }
52898  if (!activeGroup.playlistLoader) {
52899    if (previousActiveLoader) {
52900      // The previous group had a playlist loader but the new active group does not
52901      // this means we are switching from demuxed to muxed audio. In this case we want to
52902      // do a destructive reset of the main segment loader and not restart the audio
52903      // loaders.
52904      mainSegmentLoader.resetEverything();
52905    }
52906    return;
52907  } // Non-destructive resync
52908
52909  segmentLoader.resyncLoader();
52910  startLoaders(activeGroup.playlistLoader, mediaType);
52911};
52912const onGroupChanging = (type, settings) => () => {
52913  const {
52914    segmentLoaders: { [type]: segmentLoader },
52915    mediaTypes: { [type]: mediaType },
52916  } = settings;
52917  mediaType.lastGroup_ = null;
52918  segmentLoader.abort();
52919  segmentLoader.pause();
52920};
52921/**
52922 * Returns a function to be called when the media track changes. It performs a
52923 * destructive reset of the SegmentLoader to ensure we start loading as close to
52924 * currentTime as possible.
52925 *
52926 * @param {string} type
52927 *        MediaGroup type
52928 * @param {Object} settings
52929 *        Object containing required information for media groups
52930 * @return {Function}
52931 *         Handler for a destructive reset of SegmentLoader when the active media
52932 *         track changes.
52933 * @function onTrackChanged
52934 */
52935
52936const onTrackChanged = (type, settings) => () => {
52937  const {
52938    mainPlaylistLoader,
52939    segmentLoaders: { [type]: segmentLoader, main: mainSegmentLoader },
52940    mediaTypes: { [type]: mediaType },
52941  } = settings;
52942  const activeTrack = mediaType.activeTrack();
52943  const activeGroup = mediaType.getActiveGroup();
52944  const previousActiveLoader = mediaType.activePlaylistLoader;
52945  const lastTrack = mediaType.lastTrack_; // track did not change, do nothing
52946
52947  if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
52948    return;
52949  }
52950  mediaType.lastGroup_ = activeGroup;
52951  mediaType.lastTrack_ = activeTrack;
52952  stopLoaders(segmentLoader, mediaType);
52953  if (!activeGroup) {
52954    // there is no group active so we do not want to restart loaders
52955    return;
52956  }
52957  if (activeGroup.isMainPlaylist) {
52958    // track did not change, do nothing
52959    if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
52960      return;
52961    }
52962    const pc = settings.vhs.playlistController_;
52963    const newPlaylist = pc.selectPlaylist(); // media will not change do nothing
52964
52965    if (pc.media() === newPlaylist) {
52966      return;
52967    }
52968    mediaType.logger_(
52969      `track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`
52970    );
52971    mainPlaylistLoader.pause();
52972    mainSegmentLoader.resetEverything();
52973    pc.fastQualityChange_(newPlaylist);
52974    return;
52975  }
52976  if (type === 'AUDIO') {
52977    if (!activeGroup.playlistLoader) {
52978      // when switching from demuxed audio/video to muxed audio/video (noted by no
52979      // playlist loader for the audio group), we want to do a destructive reset of the
52980      // main segment loader and not restart the audio loaders
52981      mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
52982      // it should be stopped
52983
52984      mainSegmentLoader.resetEverything();
52985      return;
52986    } // although the segment loader is an audio segment loader, call the setAudio
52987    // function to ensure it is prepared to re-append the init segment (or handle other
52988    // config changes)
52989
52990    segmentLoader.setAudio(true);
52991    mainSegmentLoader.setAudio(false);
52992  }
52993  if (previousActiveLoader === activeGroup.playlistLoader) {
52994    // Nothing has actually changed. This can happen because track change events can fire
52995    // multiple times for a "single" change. One for enabling the new active track, and
52996    // one for disabling the track that was active
52997    startLoaders(activeGroup.playlistLoader, mediaType);
52998    return;
52999  }
53000  if (segmentLoader.track) {
53001    // For WebVTT, set the new text track in the segmentloader
53002    segmentLoader.track(activeTrack);
53003  } // destructive reset
53004
53005  segmentLoader.resetEverything();
53006  startLoaders(activeGroup.playlistLoader, mediaType);
53007};
53008const onError = {
53009  /**
53010   * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
53011   * an error.
53012   *
53013   * @param {string} type
53014   *        MediaGroup type
53015   * @param {Object} settings
53016   *        Object containing required information for media groups
53017   * @return {Function}
53018   *         Error handler. Logs warning (or error if the playlist is excluded) to
53019   *         console and switches back to default audio track.
53020   * @function onError.AUDIO
53021   */
53022  AUDIO: (type, settings) => () => {
53023    const {
53024      mediaTypes: { [type]: mediaType },
53025      excludePlaylist,
53026    } = settings; // switch back to default audio track
53027
53028    const activeTrack = mediaType.activeTrack();
53029    const activeGroup = mediaType.activeGroup();
53030    const id = (activeGroup.filter((group) => group.default)[0] || activeGroup[0]).id;
53031    const defaultTrack = mediaType.tracks[id];
53032    if (activeTrack === defaultTrack) {
53033      // Default track encountered an error. All we can do now is exclude the current
53034      // rendition and hope another will switch audio groups
53035      excludePlaylist({
53036        error: {
53037          message: 'Problem encountered loading the default audio track.',
53038        },
53039      });
53040      return;
53041    }
53042    videojs.log.warn(
53043      'Problem encountered loading the alternate audio track.' + 'Switching back to default.'
53044    );
53045    for (const trackId in mediaType.tracks) {
53046      mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
53047    }
53048    mediaType.onTrackChanged();
53049  },
53050  /**
53051   * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
53052   * an error.
53053   *
53054   * @param {string} type
53055   *        MediaGroup type
53056   * @param {Object} settings
53057   *        Object containing required information for media groups
53058   * @return {Function}
53059   *         Error handler. Logs warning to console and disables the active subtitle track
53060   * @function onError.SUBTITLES
53061   */
53062  SUBTITLES: (type, settings) => () => {
53063    const {
53064      mediaTypes: { [type]: mediaType },
53065    } = settings;
53066    videojs.log.warn(
53067      'Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'
53068    );
53069    const track = mediaType.activeTrack();
53070    if (track) {
53071      track.mode = 'disabled';
53072    }
53073    mediaType.onTrackChanged();
53074  },
53075};
53076const setupListeners = {
53077  /**
53078   * Setup event listeners for audio playlist loader
53079   *
53080   * @param {string} type
53081   *        MediaGroup type
53082   * @param {PlaylistLoader|null} playlistLoader
53083   *        PlaylistLoader to register listeners on
53084   * @param {Object} settings
53085   *        Object containing required information for media groups
53086   * @function setupListeners.AUDIO
53087   */
53088  AUDIO: (type, playlistLoader, settings) => {
53089    if (!playlistLoader) {
53090      // no playlist loader means audio will be muxed with the video
53091      return;
53092    }
53093    const {
53094      tech,
53095      requestOptions,
53096      segmentLoaders: { [type]: segmentLoader },
53097    } = settings;
53098    playlistLoader.on('loadedmetadata', () => {
53099      const media = playlistLoader.media();
53100      segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
53101      // permits, start downloading segments
53102
53103      if (!tech.paused() || (media.endList && tech.preload() !== 'none')) {
53104        segmentLoader.load();
53105      }
53106    });
53107    playlistLoader.on('loadedplaylist', () => {
53108      segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
53109
53110      if (!tech.paused()) {
53111        segmentLoader.load();
53112      }
53113    });
53114    playlistLoader.on('error', onError[type](type, settings));
53115  },
53116  /**
53117   * Setup event listeners for subtitle playlist loader
53118   *
53119   * @param {string} type
53120   *        MediaGroup type
53121   * @param {PlaylistLoader|null} playlistLoader
53122   *        PlaylistLoader to register listeners on
53123   * @param {Object} settings
53124   *        Object containing required information for media groups
53125   * @function setupListeners.SUBTITLES
53126   */
53127  SUBTITLES: (type, playlistLoader, settings) => {
53128    const {
53129      tech,
53130      requestOptions,
53131      segmentLoaders: { [type]: segmentLoader },
53132      mediaTypes: { [type]: mediaType },
53133    } = settings;
53134    playlistLoader.on('loadedmetadata', () => {
53135      const media = playlistLoader.media();
53136      segmentLoader.playlist(media, requestOptions);
53137      segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
53138      // permits, start downloading segments
53139
53140      if (!tech.paused() || (media.endList && tech.preload() !== 'none')) {
53141        segmentLoader.load();
53142      }
53143    });
53144    playlistLoader.on('loadedplaylist', () => {
53145      segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
53146
53147      if (!tech.paused()) {
53148        segmentLoader.load();
53149      }
53150    });
53151    playlistLoader.on('error', onError[type](type, settings));
53152  },
53153};
53154const initialize = {
53155  /**
53156   * Setup PlaylistLoaders and AudioTracks for the audio groups
53157   *
53158   * @param {string} type
53159   *        MediaGroup type
53160   * @param {Object} settings
53161   *        Object containing required information for media groups
53162   * @function initialize.AUDIO
53163   */
53164  AUDIO: (type, settings) => {
53165    const {
53166      vhs,
53167      sourceType,
53168      segmentLoaders: { [type]: segmentLoader },
53169      requestOptions,
53170      main: { mediaGroups },
53171      mediaTypes: {
53172        [type]: { groups, tracks, logger_ },
53173      },
53174      mainPlaylistLoader,
53175    } = settings;
53176    const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main); // force a default if we have none
53177
53178    if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
53179      mediaGroups[type] = {
53180        main: {
53181          default: {
53182            default: true,
53183          },
53184        },
53185      };
53186      if (audioOnlyMain) {
53187        mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
53188      }
53189    }
53190    for (const groupId in mediaGroups[type]) {
53191      if (!groups[groupId]) {
53192        groups[groupId] = [];
53193      }
53194      for (const variantLabel in mediaGroups[type][groupId]) {
53195        let properties = mediaGroups[type][groupId][variantLabel];
53196        let playlistLoader;
53197        if (audioOnlyMain) {
53198          logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
53199          properties.isMainPlaylist = true;
53200          playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
53201          // use the resolved media playlist object
53202        } else if (sourceType === 'vhs-json' && properties.playlists) {
53203          playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
53204        } else if (properties.resolvedUri) {
53205          playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
53206          // should we even have properties.playlists in this check.
53207        } else if (properties.playlists && sourceType === 'dash') {
53208          playlistLoader = new DashPlaylistLoader(
53209            properties.playlists[0],
53210            vhs,
53211            requestOptions,
53212            mainPlaylistLoader
53213          );
53214        } else {
53215          // no resolvedUri means the audio is muxed with the video when using this
53216          // audio track
53217          playlistLoader = null;
53218        }
53219        properties = merge(
53220          {
53221            id: variantLabel,
53222            playlistLoader,
53223          },
53224          properties
53225        );
53226        setupListeners[type](type, properties.playlistLoader, settings);
53227        groups[groupId].push(properties);
53228        if (typeof tracks[variantLabel] === 'undefined') {
53229          const track = new videojs.AudioTrack({
53230            id: variantLabel,
53231            kind: audioTrackKind_(properties),
53232            enabled: false,
53233            language: properties.language,
53234            default: properties.default,
53235            label: variantLabel,
53236          });
53237          tracks[variantLabel] = track;
53238        }
53239      }
53240    } // setup single error event handler for the segment loader
53241
53242    segmentLoader.on('error', onError[type](type, settings));
53243  },
53244  /**
53245   * Setup PlaylistLoaders and TextTracks for the subtitle groups
53246   *
53247   * @param {string} type
53248   *        MediaGroup type
53249   * @param {Object} settings
53250   *        Object containing required information for media groups
53251   * @function initialize.SUBTITLES
53252   */
53253  SUBTITLES: (type, settings) => {
53254    const {
53255      tech,
53256      vhs,
53257      sourceType,
53258      segmentLoaders: { [type]: segmentLoader },
53259      requestOptions,
53260      main: { mediaGroups },
53261      mediaTypes: {
53262        [type]: { groups, tracks },
53263      },
53264      mainPlaylistLoader,
53265    } = settings;
53266    for (const groupId in mediaGroups[type]) {
53267      if (!groups[groupId]) {
53268        groups[groupId] = [];
53269      }
53270      for (const variantLabel in mediaGroups[type][groupId]) {
53271        if (!vhs.options_.useForcedSubtitles && mediaGroups[type][groupId][variantLabel].forced) {
53272          // Subtitle playlists with the forced attribute are not selectable in Safari.
53273          // According to Apple's HLS Authoring Specification:
53274          //   If content has forced subtitles and regular subtitles in a given language,
53275          //   the regular subtitles track in that language MUST contain both the forced
53276          //   subtitles and the regular subtitles for that language.
53277          // Because of this requirement and that Safari does not add forced subtitles,
53278          // forced subtitles are skipped here to maintain consistent experience across
53279          // all platforms
53280          continue;
53281        }
53282        let properties = mediaGroups[type][groupId][variantLabel];
53283        let playlistLoader;
53284        if (sourceType === 'hls') {
53285          playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
53286        } else if (sourceType === 'dash') {
53287          const playlists = properties.playlists.filter((p) => p.excludeUntil !== Infinity);
53288          if (!playlists.length) {
53289            return;
53290          }
53291          playlistLoader = new DashPlaylistLoader(
53292            properties.playlists[0],
53293            vhs,
53294            requestOptions,
53295            mainPlaylistLoader
53296          );
53297        } else if (sourceType === 'vhs-json') {
53298          playlistLoader = new PlaylistLoader(
53299            // if the vhs-json object included the media playlist, use the media playlist
53300            // as provided, otherwise use the resolved URI to load the playlist
53301            properties.playlists ? properties.playlists[0] : properties.resolvedUri,
53302            vhs,
53303            requestOptions
53304          );
53305        }
53306        properties = merge(
53307          {
53308            id: variantLabel,
53309            playlistLoader,
53310          },
53311          properties
53312        );
53313        setupListeners[type](type, properties.playlistLoader, settings);
53314        groups[groupId].push(properties);
53315        if (typeof tracks[variantLabel] === 'undefined') {
53316          const track = tech.addRemoteTextTrack(
53317            {
53318              id: variantLabel,
53319              kind: 'subtitles',
53320              default: properties.default && properties.autoselect,
53321              language: properties.language,
53322              label: variantLabel,
53323            },
53324            false
53325          ).track;
53326          tracks[variantLabel] = track;
53327        }
53328      }
53329    } // setup single error event handler for the segment loader
53330
53331    segmentLoader.on('error', onError[type](type, settings));
53332  },
53333  /**
53334   * Setup TextTracks for the closed-caption groups
53335   *
53336   * @param {String} type
53337   *        MediaGroup type
53338   * @param {Object} settings
53339   *        Object containing required information for media groups
53340   * @function initialize['CLOSED-CAPTIONS']
53341   */
53342  'CLOSED-CAPTIONS': (type, settings) => {
53343    const {
53344      tech,
53345      main: { mediaGroups },
53346      mediaTypes: {
53347        [type]: { groups, tracks },
53348      },
53349    } = settings;
53350    for (const groupId in mediaGroups[type]) {
53351      if (!groups[groupId]) {
53352        groups[groupId] = [];
53353      }
53354      for (const variantLabel in mediaGroups[type][groupId]) {
53355        const properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
53356
53357        if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
53358          continue;
53359        }
53360        const captionServices = (tech.options_.vhs && tech.options_.vhs.captionServices) || {};
53361        let newProps = {
53362          label: variantLabel,
53363          language: properties.language,
53364          instreamId: properties.instreamId,
53365          default: properties.default && properties.autoselect,
53366        };
53367        if (captionServices[newProps.instreamId]) {
53368          newProps = merge(newProps, captionServices[newProps.instreamId]);
53369        }
53370        if (newProps.default === undefined) {
53371          delete newProps.default;
53372        } // No PlaylistLoader is required for Closed-Captions because the captions are
53373        // embedded within the video stream
53374
53375        groups[groupId].push(
53376          merge(
53377            {
53378              id: variantLabel,
53379            },
53380            properties
53381          )
53382        );
53383        if (typeof tracks[variantLabel] === 'undefined') {
53384          const track = tech.addRemoteTextTrack(
53385            {
53386              id: newProps.instreamId,
53387              kind: 'captions',
53388              default: newProps.default,
53389              language: newProps.language,
53390              label: newProps.label,
53391            },
53392            false
53393          ).track;
53394          tracks[variantLabel] = track;
53395        }
53396      }
53397    }
53398  },
53399};
53400const groupMatch = (list, media) => {
53401  for (let i = 0; i < list.length; i++) {
53402    if (playlistMatch(media, list[i])) {
53403      return true;
53404    }
53405    if (list[i].playlists && groupMatch(list[i].playlists, media)) {
53406      return true;
53407    }
53408  }
53409  return false;
vendor: 9,168 bytes, lines 53410-53676
53410};
53411/**
53412 * Returns a function used to get the active group of the provided type
53413 *
53414 * @param {string} type
53415 *        MediaGroup type
53416 * @param {Object} settings
53417 *        Object containing required information for media groups
53418 * @return {Function}
53419 *         Function that returns the active media group for the provided type. Takes an
53420 *         optional parameter {TextTrack} track. If no track is provided, a list of all
53421 *         variants in the group, otherwise the variant corresponding to the provided
53422 *         track is returned.
53423 * @function activeGroup
53424 */
53425
53426const activeGroup = (type, settings) => (track) => {
53427  const {
53428    mainPlaylistLoader,
53429    mediaTypes: {
53430      [type]: { groups },
53431    },
53432  } = settings;
53433  const media = mainPlaylistLoader.media();
53434  if (!media) {
53435    return null;
53436  }
53437  let variants = null; // set to variants to main media active group
53438
53439  if (media.attributes[type]) {
53440    variants = groups[media.attributes[type]];
53441  }
53442  const groupKeys = Object.keys(groups);
53443  if (!variants) {
53444    // find the mainPlaylistLoader media
53445    // that is in a media group if we are dealing
53446    // with audio only
53447    if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
53448      for (let i = 0; i < groupKeys.length; i++) {
53449        const groupPropertyList = groups[groupKeys[i]];
53450        if (groupMatch(groupPropertyList, media)) {
53451          variants = groupPropertyList;
53452          break;
53453        }
53454      } // use the main group if it exists
53455    } else if (groups.main) {
53456      variants = groups.main; // only one group, use that one
53457    } else if (groupKeys.length === 1) {
53458      variants = groups[groupKeys[0]];
53459    }
53460  }
53461  if (typeof track === 'undefined') {
53462    return variants;
53463  }
53464  if (track === null || !variants) {
53465    // An active track was specified so a corresponding group is expected. track === null
53466    // means no track is currently active so there is no corresponding group
53467    return null;
53468  }
53469  return variants.filter((props) => props.id === track.id)[0] || null;
53470};
53471const activeTrack = {
53472  /**
53473   * Returns a function used to get the active track of type provided
53474   *
53475   * @param {string} type
53476   *        MediaGroup type
53477   * @param {Object} settings
53478   *        Object containing required information for media groups
53479   * @return {Function}
53480   *         Function that returns the active media track for the provided type. Returns
53481   *         null if no track is active
53482   * @function activeTrack.AUDIO
53483   */
53484  AUDIO: (type, settings) => () => {
53485    const {
53486      mediaTypes: {
53487        [type]: { tracks },
53488      },
53489    } = settings;
53490    for (const id in tracks) {
53491      if (tracks[id].enabled) {
53492        return tracks[id];
53493      }
53494    }
53495    return null;
53496  },
53497  /**
53498   * Returns a function used to get the active track of type provided
53499   *
53500   * @param {string} type
53501   *        MediaGroup type
53502   * @param {Object} settings
53503   *        Object containing required information for media groups
53504   * @return {Function}
53505   *         Function that returns the active media track for the provided type. Returns
53506   *         null if no track is active
53507   * @function activeTrack.SUBTITLES
53508   */
53509  SUBTITLES: (type, settings) => () => {
53510    const {
53511      mediaTypes: {
53512        [type]: { tracks },
53513      },
53514    } = settings;
53515    for (const id in tracks) {
53516      if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
53517        return tracks[id];
53518      }
53519    }
53520    return null;
53521  },
53522};
53523const getActiveGroup =
53524  (type, { mediaTypes }) =>
53525  () => {
53526    const activeTrack_ = mediaTypes[type].activeTrack();
53527    if (!activeTrack_) {
53528      return null;
53529    }
53530    return mediaTypes[type].activeGroup(activeTrack_);
53531  };
53532/**
53533 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
53534 * Closed-Captions) specified in the main manifest.
53535 *
53536 * @param {Object} settings
53537 *        Object containing required information for setting up the media groups
53538 * @param {Tech} settings.tech
53539 *        The tech of the player
53540 * @param {Object} settings.requestOptions
53541 *        XHR request options used by the segment loaders
53542 * @param {PlaylistLoader} settings.mainPlaylistLoader
53543 *        PlaylistLoader for the main source
53544 * @param {VhsHandler} settings.vhs
53545 *        VHS SourceHandler
53546 * @param {Object} settings.main
53547 *        The parsed main manifest
53548 * @param {Object} settings.mediaTypes
53549 *        Object to store the loaders, tracks, and utility methods for each media type
53550 * @param {Function} settings.excludePlaylist
53551 *        Excludes the current rendition and forces a rendition switch.
53552 * @function setupMediaGroups
53553 */
53554
53555const setupMediaGroups = (settings) => {
53556  ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((type) => {
53557    initialize[type](type, settings);
53558  });
53559  const {
53560    mediaTypes,
53561    mainPlaylistLoader,
53562    tech,
53563    vhs,
53564    segmentLoaders: { ['AUDIO']: audioSegmentLoader, main: mainSegmentLoader },
53565  } = settings; // setup active group and track getters and change event handlers
53566
53567  ['AUDIO', 'SUBTITLES'].forEach((type) => {
53568    mediaTypes[type].activeGroup = activeGroup(type, settings);
53569    mediaTypes[type].activeTrack = activeTrack[type](type, settings);
53570    mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
53571    mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
53572    mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
53573    mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
53574  }); // DO NOT enable the default subtitle or caption track.
53575  // DO enable the default audio track
53576
53577  const audioGroup = mediaTypes.AUDIO.activeGroup();
53578  if (audioGroup) {
53579    const groupId = (audioGroup.filter((group) => group.default)[0] || audioGroup[0]).id;
53580    mediaTypes.AUDIO.tracks[groupId].enabled = true;
53581    mediaTypes.AUDIO.onGroupChanged();
53582    mediaTypes.AUDIO.onTrackChanged();
53583    const activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
53584    // track is changed, but needs to be handled here since the track may not be considered
53585    // changed on the first call to onTrackChanged
53586
53587    if (!activeAudioGroup.playlistLoader) {
53588      // either audio is muxed with video or the stream is audio only
53589      mainSegmentLoader.setAudio(true);
53590    } else {
53591      // audio is demuxed
53592      mainSegmentLoader.setAudio(false);
53593      audioSegmentLoader.setAudio(true);
53594    }
53595  }
53596  mainPlaylistLoader.on('mediachange', () => {
53597    ['AUDIO', 'SUBTITLES'].forEach((type) => mediaTypes[type].onGroupChanged());
53598  });
53599  mainPlaylistLoader.on('mediachanging', () => {
53600    ['AUDIO', 'SUBTITLES'].forEach((type) => mediaTypes[type].onGroupChanging());
53601  }); // custom audio track change event handler for usage event
53602
53603  const onAudioTrackChanged = () => {
53604    mediaTypes.AUDIO.onTrackChanged();
53605    tech.trigger({
53606      type: 'usage',
53607      name: 'vhs-audio-change',
53608    });
53609  };
53610  tech.audioTracks().addEventListener('change', onAudioTrackChanged);
53611  tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
53612  vhs.on('dispose', () => {
53613    tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
53614    tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
53615  }); // clear existing audio tracks and add the ones we just created
53616
53617  tech.clearTracks('audio');
53618  for (const id in mediaTypes.AUDIO.tracks) {
53619    tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
53620  }
53621};
53622/**
53623 * Creates skeleton object used to store the loaders, tracks, and utility methods for each
53624 * media type
53625 *
53626 * @return {Object}
53627 *         Object to store the loaders, tracks, and utility methods for each media type
53628 * @function createMediaTypes
53629 */
53630
53631const createMediaTypes = () => {
53632  const mediaTypes = {};
53633  ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((type) => {
53634    mediaTypes[type] = {
53635      groups: {},
53636      tracks: {},
53637      activePlaylistLoader: null,
53638      activeGroup: noop,
53639      activeTrack: noop,
53640      getActiveGroup: noop,
53641      onGroupChanged: noop,
53642      onTrackChanged: noop,
53643      lastTrack_: null,
53644      logger_: logger(`MediaGroups[${type}]`),
53645    };
53646  });
53647  return mediaTypes;
53648};
53649
53650/**
53651 * A utility class for setting properties and maintaining the state of the content steering manifest.
53652 *
53653 * Content Steering manifest format:
53654 * VERSION: number (required) currently only version 1 is supported.
53655 * TTL: number in seconds (optional) until the next content steering manifest reload.
53656 * RELOAD-URI: string (optional) uri to fetch the next content steering manifest.
53657 * SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY a non empty array of unique string values.
53658 * PATHWAY-CLONES: array (optional) (HLS only) pathway clone objects to copy from other playlists.
53659 */
53660
53661class SteeringManifest {
53662  constructor() {
53663    this.priority_ = [];
53664    this.pathwayClones_ = new Map();
53665  }
53666  set version(number) {
53667    // Only version 1 is currently supported for both DASH and HLS.
53668    if (number === 1) {
53669      this.version_ = number;
53670    }
53671  }
53672  set ttl(seconds) {
53673    // TTL = time-to-live, default = 300 seconds.
53674    this.ttl_ = seconds || 300;
53675  }
53676  set reloadUri(uri) {
53677    if (uri) {
53678      // reload URI can be relative to the previous reloadUri.
53679      this.reloadUri_ = resolveUrl(this.reloadUri_, uri);
53680    }
53681  }
53682  set priority(array) {
53683    // priority must be non-empty and unique values.
53684    if (array && array.length) {
53685      this.priority_ = array;
53686    }
53687  }
53688  set pathwayClones(array) {
53689    // pathwayClones must be non-empty.
53690    if (array && array.length) {
53691      this.pathwayClones_ = new Map(array.map((clone) => [clone.ID, clone]));
53692    }
53693  }
53694  get version() {
53695    return this.version_;
53696  }
53697  get ttl() {
53698    return this.ttl_;
53699  }
53700  get reloadUri() {
53701    return this.reloadUri_;
53702  }
53703  get priority() {
53704    return this.priority_;
53705  }
53706  get pathwayClones() {
53707    return this.pathwayClones_;
53708  }
53709}
53710/**
53711 * This class represents a content steering manifest and associated state. See both HLS and DASH specifications.
53712 * HLS: https://developer.apple.com/streaming/HLSContentSteeringSpecification.pdf and
53713 * https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/ section 4.4.6.6.
53714 * DASH: https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
53715 *
53716 * @param {function} xhr for making a network request from the browser.
53717 * @param {function} bandwidth for fetching the current bandwidth from the main segment loader.
53718 */
53719
53720class ContentSteeringController extends videojs.EventTarget {
53721  constructor(xhr, bandwidth) {
53722    super();
53723    this.currentPathway = null;
53724    this.defaultPathway = null;
53725    this.queryBeforeStart = false;
vendor: 3,962 bytes, lines 53726-53818
53726    this.availablePathways_ = new Set();
53727    this.steeringManifest = new SteeringManifest();
53728    this.proxyServerUrl_ = null;
53729    this.manifestType_ = null;
53730    this.ttlTimeout_ = null;
53731    this.request_ = null;
53732    this.currentPathwayClones = new Map();
53733    this.nextPathwayClones = new Map();
53734    this.excludedSteeringManifestURLs = new Set();
53735    this.logger_ = logger('Content Steering');
53736    this.xhr_ = xhr;
53737    this.getBandwidth_ = bandwidth;
53738  }
53739  /**
53740   * Assigns the content steering tag properties to the steering controller
53741   *
53742   * @param {string} baseUrl the baseURL from the main manifest for resolving the steering manifest url
53743   * @param {Object} steeringTag the content steering tag from the main manifest
53744   */
53745
53746  assignTagProperties(baseUrl, steeringTag) {
53747    this.manifestType_ = steeringTag.serverUri ? 'HLS' : 'DASH'; // serverUri is HLS serverURL is DASH
53748
53749    const steeringUri = steeringTag.serverUri || steeringTag.serverURL;
53750    if (!steeringUri) {
53751      this.logger_(`steering manifest URL is ${steeringUri}, cannot request steering manifest.`);
53752      this.trigger('error');
53753      return;
53754    } // Content steering manifests can be encoded as a data URI. We can decode, parse and return early if that's the case.
53755
53756    if (steeringUri.startsWith('data:')) {
53757      this.decodeDataUriManifest_(steeringUri.substring(steeringUri.indexOf(',') + 1));
53758      return;
53759    } // reloadUri is the resolution of the main manifest URL and steering URL.
53760
53761    this.steeringManifest.reloadUri = resolveUrl(baseUrl, steeringUri); // pathwayId is HLS defaultServiceLocation is DASH
53762
53763    this.defaultPathway = steeringTag.pathwayId || steeringTag.defaultServiceLocation; // currently only DASH supports the following properties on <ContentSteering> tags.
53764
53765    this.queryBeforeStart = steeringTag.queryBeforeStart;
53766    this.proxyServerUrl_ = steeringTag.proxyServerURL; // trigger a steering event if we have a pathway from the content steering tag.
53767    // this tells VHS which segment pathway to start with.
53768    // If queryBeforeStart is true we need to wait for the steering manifest response.
53769
53770    if (this.defaultPathway && !this.queryBeforeStart) {
53771      this.trigger('content-steering');
53772    }
53773  }
53774  /**
53775   * Requests the content steering manifest and parse the response. This should only be called after
53776   * assignTagProperties was called with a content steering tag.
53777   *
53778   * @param {string} initialUri The optional uri to make the request with.
53779   *    If set, the request should be made with exactly what is passed in this variable.
53780   *    This scenario should only happen once on initalization.
53781   */
53782
53783  requestSteeringManifest(initial) {
53784    const reloadUri = this.steeringManifest.reloadUri;
53785    if (!reloadUri) {
53786      return;
53787    } // We currently don't support passing MPD query parameters directly to the content steering URL as this requires
53788    // ExtUrlQueryInfo tag support. See the DASH content steering spec section 8.1.
53789    // This request URI accounts for manifest URIs that have been excluded.
53790
53791    const uri = initial ? reloadUri : this.getRequestURI(reloadUri); // If there are no valid manifest URIs, we should stop content steering.
53792
53793    if (!uri) {
53794      this.logger_('No valid content steering manifest URIs. Stopping content steering.');
53795      this.trigger('error');
53796      this.dispose();
53797      return;
53798    }
53799    const metadata = {
53800      contentSteeringInfo: {
53801        uri,
53802      },
53803    };
53804    this.trigger({
53805      type: 'contentsteeringloadstart',
53806      metadata,
53807    });
53808    this.request_ = this.xhr_(
53809      {
53810        uri,
53811        requestType: 'content-steering-manifest',
53812      },
53813      (error, errorInfo) => {
53814        if (error) {
53815          // If the client receives HTTP 410 Gone in response to a manifest request,
53816          // it MUST NOT issue another request for that URI for the remainder of the
53817          // playback session. It MAY continue to use the most-recently obtained set
53818          // of Pathways.
vendor: 5,889 bytes, lines 53819-53954
53819          if (errorInfo.status === 410) {
53820            this.logger_(`manifest request 410 ${error}.`);
53821            this.logger_(`There will be no more content steering requests to ${uri} this session.`);
53822            this.excludedSteeringManifestURLs.add(uri);
53823            return;
53824          } // If the client receives HTTP 429 Too Many Requests with a Retry-After
53825          // header in response to a manifest request, it SHOULD wait until the time
53826          // specified by the Retry-After header to reissue the request.
53827
53828          if (errorInfo.status === 429) {
53829            const retrySeconds = errorInfo.responseHeaders['retry-after'];
53830            this.logger_(`manifest request 429 ${error}.`);
53831            this.logger_(`content steering will retry in ${retrySeconds} seconds.`);
53832            this.startTTLTimeout_(parseInt(retrySeconds, 10));
53833            return;
53834          } // If the Steering Manifest cannot be loaded and parsed correctly, the
53835          // client SHOULD continue to use the previous values and attempt to reload
53836          // it after waiting for the previously-specified TTL (or 5 minutes if
53837          // none).
53838
53839          this.logger_(`manifest failed to load ${error}.`);
53840          this.startTTLTimeout_();
53841          return;
53842        }
53843        this.trigger({
53844          type: 'contentsteeringloadcomplete',
53845          metadata,
53846        });
53847        let steeringManifestJson;
53848        try {
53849          steeringManifestJson = JSON.parse(this.request_.responseText);
53850        } catch (parseError) {
53851          const errorMetadata = {
53852            errorType: videojs.Error.StreamingContentSteeringParserError,
53853            error: parseError,
53854          };
53855          this.trigger({
53856            type: 'error',
53857            metadata: errorMetadata,
53858          });
53859        }
53860        this.assignSteeringProperties_(steeringManifestJson);
53861        const parsedMetadata = {
53862          contentSteeringInfo: metadata.contentSteeringInfo,
53863          contentSteeringManifest: {
53864            version: this.steeringManifest.version,
53865            reloadUri: this.steeringManifest.reloadUri,
53866            priority: this.steeringManifest.priority,
53867          },
53868        };
53869        this.trigger({
53870          type: 'contentsteeringparsed',
53871          metadata: parsedMetadata,
53872        });
53873        this.startTTLTimeout_();
53874      }
53875    );
53876  }
53877  /**
53878   * Set the proxy server URL and add the steering manifest url as a URI encoded parameter.
53879   *
53880   * @param {string} steeringUrl the steering manifest url
53881   * @return the steering manifest url to a proxy server with all parameters set
53882   */
53883
53884  setProxyServerUrl_(steeringUrl) {
53885    const steeringUrlObject = new window__default['default'].URL(steeringUrl);
53886    const proxyServerUrlObject = new window__default['default'].URL(this.proxyServerUrl_);
53887    proxyServerUrlObject.searchParams.set('url', encodeURI(steeringUrlObject.toString()));
53888    return this.setSteeringParams_(proxyServerUrlObject.toString());
53889  }
53890  /**
53891   * Decodes and parses the data uri encoded steering manifest
53892   *
53893   * @param {string} dataUri the data uri to be decoded and parsed.
53894   */
53895
53896  decodeDataUriManifest_(dataUri) {
53897    const steeringManifestJson = JSON.parse(window__default['default'].atob(dataUri));
53898    this.assignSteeringProperties_(steeringManifestJson);
53899  }
53900  /**
53901   * Set the HLS or DASH content steering manifest request query parameters. For example:
53902   * _HLS_pathway="<CURRENT-PATHWAY-ID>" and _HLS_throughput=<THROUGHPUT>
53903   * _DASH_pathway and _DASH_throughput
53904   *
53905   * @param {string} uri to add content steering server parameters to.
53906   * @return a new uri as a string with the added steering query parameters.
53907   */
53908
53909  setSteeringParams_(url) {
53910    const urlObject = new window__default['default'].URL(url);
53911    const path = this.getPathway();
53912    const networkThroughput = this.getBandwidth_();
53913    if (path) {
53914      const pathwayKey = `_${this.manifestType_}_pathway`;
53915      urlObject.searchParams.set(pathwayKey, path);
53916    }
53917    if (networkThroughput) {
53918      const throughputKey = `_${this.manifestType_}_throughput`;
53919      urlObject.searchParams.set(throughputKey, networkThroughput);
53920    }
53921    return urlObject.toString();
53922  }
53923  /**
53924   * Assigns the current steering manifest properties and to the SteeringManifest object
53925   *
53926   * @param {Object} steeringJson the raw JSON steering manifest
53927   */
53928
53929  assignSteeringProperties_(steeringJson) {
53930    this.steeringManifest.version = steeringJson.VERSION;
53931    if (!this.steeringManifest.version) {
53932      this.logger_(`manifest version is ${steeringJson.VERSION}, which is not supported.`);
53933      this.trigger('error');
53934      return;
53935    }
53936    this.steeringManifest.ttl = steeringJson.TTL;
53937    this.steeringManifest.reloadUri = steeringJson['RELOAD-URI']; // HLS = PATHWAY-PRIORITY required. DASH = SERVICE-LOCATION-PRIORITY optional
53938
53939    this.steeringManifest.priority =
53940      steeringJson['PATHWAY-PRIORITY'] || steeringJson['SERVICE-LOCATION-PRIORITY']; // Pathway clones to be created/updated in HLS.
53941    // See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
53942
53943    this.steeringManifest.pathwayClones = steeringJson['PATHWAY-CLONES'];
53944    this.nextPathwayClones = this.steeringManifest.pathwayClones; // 1. apply first pathway from the array.
53945    // 2. if first pathway doesn't exist in manifest, try next pathway.
53946    //    a. if all pathways are exhausted, ignore the steering manifest priority.
53947    // 3. if segments fail from an established pathway, try all variants/renditions, then exclude the failed pathway.
53948    //    a. exclude a pathway for a minimum of the last TTL duration. Meaning, from the next steering response,
53949    //       the excluded pathway will be ignored.
53950    //       See excludePathway usage in excludePlaylist().
53951    // If there are no available pathways, we need to stop content steering.
53952
53953    if (!this.availablePathways_.size) {
53954      this.logger_(
53955        'There are no available pathways for content steering. Ending content steering.'
53956      );
53957      this.trigger('error');
53958      this.dispose();
53959    }
53960    const chooseNextPathway = (pathwaysByPriority) => {
53961      for (const path of pathwaysByPriority) {
53962        if (this.availablePathways_.has(path)) {
53963          return path;
53964        }
53965      } // If no pathway matches, ignore the manifest and choose the first available.
53966
53967      return [...this.availablePathways_][0];
53968    };
53969    const nextPathway = chooseNextPathway(this.steeringManifest.priority);
53970    if (this.currentPathway !== nextPathway) {
53971      this.currentPathway = nextPathway;
53972      this.trigger('content-steering');
53973    }
53974  }
53975  /**
53976   * Returns the pathway to use for steering decisions
53977   *
53978   * @return {string} returns the current pathway or the default
53979   */
53980
53981  getPathway() {
53982    return this.currentPathway || this.defaultPathway;
53983  }
53984  /**
53985   * Chooses the manifest request URI based on proxy URIs and server URLs.
53986   * Also accounts for exclusion on certain manifest URIs.
53987   *
53988   * @param {string} reloadUri the base uri before parameters
53989   *
53990   * @return {string} the final URI for the request to the manifest server.
53991   */
53992
53993  getRequestURI(reloadUri) {
53994    if (!reloadUri) {
53995      return null;
53996    }
53997    const isExcluded = (uri) => this.excludedSteeringManifestURLs.has(uri);
53998    if (this.proxyServerUrl_) {
53999      const proxyURI = this.setProxyServerUrl_(reloadUri);
54000      if (!isExcluded(proxyURI)) {
54001        return proxyURI;
54002      }
54003    }
54004    const steeringURI = this.setSteeringParams_(reloadUri);
54005    if (!isExcluded(steeringURI)) {
54006      return steeringURI;
54007    } // Return nothing if all valid manifest URIs are excluded.
54008
54009    return null;
54010  }
54011  /**
54012   * Start the timeout for re-requesting the steering manifest at the TTL interval.
54013   *
54014   * @param {number} ttl time in seconds of the timeout. Defaults to the
54015   *        ttl interval in the steering manifest
54016   */
54017
54018  startTTLTimeout_(ttl = this.steeringManifest.ttl) {
54019    // 300 (5 minutes) is the default value.
54020    const ttlMS = ttl * 1000;
54021    this.ttlTimeout_ = window__default['default'].setTimeout(() => {
54022      this.requestSteeringManifest();
54023    }, ttlMS);
54024  }
54025  /**
54026   * Clear the TTL timeout if necessary.
54027   */
54028
54029  clearTTLTimeout_() {
54030    window__default['default'].clearTimeout(this.ttlTimeout_);
54031    this.ttlTimeout_ = null;
54032  }
54033  /**
54034   * aborts any current steering xhr and sets the current request object to null
54035   */
54036
54037  abort() {
54038    if (this.request_) {
54039      this.request_.abort();
54040    }
54041    this.request_ = null;
54042  }
54043  /**
54044   * aborts steering requests clears the ttl timeout and resets all properties.
54045   */
54046
54047  dispose() {
54048    this.off('content-steering');
54049    this.off('error');
54050    this.abort();
54051    this.clearTTLTimeout_();
54052    this.currentPathway = null;
54053    this.defaultPathway = null;
54054    this.queryBeforeStart = null;
54055    this.proxyServerUrl_ = null;
54056    this.manifestType_ = null;
54057    this.ttlTimeout_ = null;
54058    this.request_ = null;
54059    this.excludedSteeringManifestURLs = new Set();
54060    this.availablePathways_ = new Set();
54061    this.steeringManifest = new SteeringManifest();
54062  }
54063  /**
54064   * adds a pathway to the available pathways set
54065   *
54066   * @param {string} pathway the pathway string to add
54067   */
54068
54069  addAvailablePathway(pathway) {
54070    if (pathway) {
54071      this.availablePathways_.add(pathway);
54072    }
54073  }
54074  /**
54075   * Clears all pathways from the available pathways set
54076   */
54077
54078  clearAvailablePathways() {
54079    this.availablePathways_.clear();
54080  }
54081  /**
54082   * Removes a pathway from the available pathways set.
54083   */
54084
54085  excludePathway(pathway) {
54086    return this.availablePathways_.delete(pathway);
54087  }
54088  /**
54089   * Checks the refreshed DASH manifest content steering tag for changes.
54090   *
54091   * @param {string} baseURL new steering tag on DASH manifest refresh
54092   * @param {Object} newTag the new tag to check for changes
54093   * @return a true or false whether the new tag has different values
54094   */
54095
54096  didDASHTagChange(baseURL, newTag) {
54097    return (
54098      (!newTag && this.steeringManifest.reloadUri) ||
54099      (newTag &&
54100        (resolveUrl(baseURL, newTag.serverURL) !== this.steeringManifest.reloadUri ||
54101          newTag.defaultServiceLocation !== this.defaultPathway ||
54102          newTag.queryBeforeStart !== this.queryBeforeStart ||
54103          newTag.proxyServerURL !== this.proxyServerUrl_))
54104    );
54105  }
54106  getAvailablePathways() {
54107    return this.availablePathways_;
54108  }
54109}
54110const ABORT_EARLY_EXCLUSION_SECONDS = 10;
54111let Vhs$1; // SegmentLoader stats that need to have each loader's
54112// values summed to calculate the final value
54113
54114const loaderStats = [
54115  'mediaRequests',
54116  'mediaRequestsAborted',
54117  'mediaRequestsTimedout',
54118  'mediaRequestsErrored',
54119  'mediaTransferDuration',
54120  'mediaBytesTransferred',
54121  'mediaAppends',
54122];
54123const sumLoaderStat = function (stat) {
54124  return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
54125};
54126const shouldSwitchToMedia = function ({
54127  currentPlaylist,
54128  buffered,
54129  currentTime,
54130  nextPlaylist,
54131  bufferLowWaterLine,
54132  bufferHighWaterLine,
54133  duration,
54134  bufferBasedABR,
54135  log,
54136}) {
54137  // we have no other playlist to switch to
54138  if (!nextPlaylist) {
54139    videojs.log.warn('We received no playlist to switch to. Please check your stream.');
54140    return false;
vendor: 6,210 bytes, lines 54141-54287
54141  }
54142  const sharedLogLine = `allowing switch ${(currentPlaylist && currentPlaylist.id) || 'null'} -> ${nextPlaylist.id}`;
54143  if (!currentPlaylist) {
54144    log(`${sharedLogLine} as current playlist is not set`);
54145    return true;
54146  } // no need to switch if playlist is the same
54147
54148  if (nextPlaylist.id === currentPlaylist.id) {
54149    return false;
54150  } // determine if current time is in a buffered range.
54151
54152  const isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
54153  // This is because in LIVE, the player plays 3 segments from the end of the
54154  // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
54155  // in those segments, a viewer will never experience a rendition upswitch.
54156
54157  if (!currentPlaylist.endList) {
54158    // For LLHLS live streams, don't switch renditions before playback has started, as it almost
54159    // doubles the time to first playback.
54160    if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
54161      log(
54162        `not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`
54163      );
54164      return false;
54165    }
54166    log(`${sharedLogLine} as current playlist is live`);
54167    return true;
54168  }
54169  const forwardBuffer = timeAheadOf(buffered, currentTime);
54170  const maxBufferLowWaterLine = bufferBasedABR
54171    ? Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE
54172    : Config.MAX_BUFFER_LOW_WATER_LINE; // For the same reason as LIVE, we ignore the low water line when the VOD
54173  // duration is below the max potential low water line
54174
54175  if (duration < maxBufferLowWaterLine) {
54176    log(
54177      `${sharedLogLine} as duration < max low water line (${duration} < ${maxBufferLowWaterLine})`
54178    );
54179    return true;
54180  }
54181  const nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
54182  const currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
54183  // we can switch down
54184
54185  if (nextBandwidth < currBandwidth && (!bufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
54186    let logLine = `${sharedLogLine} as next bandwidth < current bandwidth (${nextBandwidth} < ${currBandwidth})`;
54187    if (bufferBasedABR) {
54188      logLine += ` and forwardBuffer < bufferHighWaterLine (${forwardBuffer} < ${bufferHighWaterLine})`;
54189    }
54190    log(logLine);
54191    return true;
54192  } // and if our buffer is higher than the low water line,
54193  // we can switch up
54194
54195  if ((!bufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
54196    let logLine = `${sharedLogLine} as forwardBuffer >= bufferLowWaterLine (${forwardBuffer} >= ${bufferLowWaterLine})`;
54197    if (bufferBasedABR) {
54198      logLine += ` and next bandwidth > current bandwidth (${nextBandwidth} > ${currBandwidth})`;
54199    }
54200    log(logLine);
54201    return true;
54202  }
54203  log(`not ${sharedLogLine} as no switching criteria met`);
54204  return false;
54205};
54206/**
54207 * the main playlist controller controller all interactons
54208 * between playlists and segmentloaders. At this time this mainly
54209 * involves a main playlist and a series of audio playlists
54210 * if they are available
54211 *
54212 * @class PlaylistController
54213 * @extends videojs.EventTarget
54214 */
54215
54216class PlaylistController extends videojs.EventTarget {
54217  constructor(options) {
54218    super();
54219    const {
54220      src,
54221      withCredentials,
54222      tech,
54223      bandwidth,
54224      externVhs,
54225      useCueTags,
54226      playlistExclusionDuration,
54227      enableLowInitialPlaylist,
54228      sourceType,
54229      cacheEncryptionKeys,
54230      bufferBasedABR,
54231      leastPixelDiffSelector,
54232      captionServices,
54233      experimentalUseMMS,
54234    } = options;
54235    if (!src) {
54236      throw new Error('A non-empty playlist URL or JSON manifest string is required');
54237    }
54238    let { maxPlaylistRetries } = options;
54239    if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
54240      maxPlaylistRetries = Infinity;
54241    }
54242    Vhs$1 = externVhs;
54243    this.bufferBasedABR = Boolean(bufferBasedABR);
54244    this.leastPixelDiffSelector = Boolean(leastPixelDiffSelector);
54245    this.withCredentials = withCredentials;
54246    this.tech_ = tech;
54247    this.vhs_ = tech.vhs;
54248    this.player_ = options.player_;
54249    this.sourceType_ = sourceType;
54250    this.useCueTags_ = useCueTags;
54251    this.playlistExclusionDuration = playlistExclusionDuration;
54252    this.maxPlaylistRetries = maxPlaylistRetries;
54253    this.enableLowInitialPlaylist = enableLowInitialPlaylist;
54254    if (this.useCueTags_) {
54255      this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
54256      this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
54257    }
54258    this.requestOptions_ = {
54259      withCredentials,
54260      maxPlaylistRetries,
54261      timeout: null,
54262    };
54263    this.on('error', this.pauseLoading);
54264    this.mediaTypes_ = createMediaTypes();
54265    if (experimentalUseMMS && window__default['default'].ManagedMediaSource) {
54266      // Airplay source not yet implemented. Remote playback must be disabled.
54267      this.tech_.el_.disableRemotePlayback = true;
54268      this.mediaSource = new window__default['default'].ManagedMediaSource();
54269      videojs.log('Using ManagedMediaSource');
54270    } else if (window__default['default'].MediaSource) {
54271      this.mediaSource = new window__default['default'].MediaSource();
54272    }
54273    this.handleDurationChange_ = this.handleDurationChange_.bind(this);
54274    this.handleSourceOpen_ = this.handleSourceOpen_.bind(this);
54275    this.handleSourceEnded_ = this.handleSourceEnded_.bind(this);
54276    this.load = this.load.bind(this);
54277    this.pause = this.pause.bind(this);
54278    this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
54279
54280    this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_);
54281    this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_);
54282    this.mediaSource.addEventListener('startstreaming', this.load);
54283    this.mediaSource.addEventListener('endstreaming', this.pause); // we don't have to handle sourceclose since dispose will handle termination of
54284    // everything, and the MediaSource should not be detached without a proper disposal
54285
54286    this.seekable_ = createTimeRanges();
54287    this.hasPlayed_ = false;
54288    this.syncController_ = new SyncController(options);
54289    this.segmentMetadataTrack_ = tech.addRemoteTextTrack(
54290      {
54291        kind: 'metadata',
54292        label: 'segment-metadata',
54293      },
54294      false
54295    ).track;
54296    this.decrypter_ = new Decrypter();
54297    this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
54298    this.inbandTextTracks_ = {};
54299    this.timelineChangeController_ = new TimelineChangeController();
54300    this.keyStatusMap_ = new Map();
54301    const segmentLoaderSettings = {
54302      vhs: this.vhs_,
54303      parse708captions: options.parse708captions,
54304      useDtsForTimestampOffset: options.useDtsForTimestampOffset,
54305      captionServices,
54306      mediaSource: this.mediaSource,
54307      currentTime: this.tech_.currentTime.bind(this.tech_),
54308      seekable: () => this.seekable(),
54309      seeking: () => this.tech_.seeking(),
54310      duration: () => this.duration(),
54311      hasPlayed: () => this.hasPlayed_,
54312      goalBufferLength: () => this.goalBufferLength(),
54313      bandwidth,
54314      syncController: this.syncController_,
54315      decrypter: this.decrypter_,
54316      sourceType: this.sourceType_,
54317      inbandTextTracks: this.inbandTextTracks_,
54318      cacheEncryptionKeys,
54319      sourceUpdater: this.sourceUpdater_,
54320      timelineChangeController: this.timelineChangeController_,
54321      exactManifestTimings: options.exactManifestTimings,
54322      addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this),
54323    }; // The source type check not only determines whether a special DASH playlist loader
54324    // should be used, but also covers the case where the provided src is a vhs-json
54325    // manifest object (instead of a URL). In the case of vhs-json, the default
54326    // PlaylistLoader should be used.
54327
54328    this.mainPlaylistLoader_ =
54329      this.sourceType_ === 'dash'
54330        ? new DashPlaylistLoader(
54331            src,
54332            this.vhs_,
54333            merge(this.requestOptions_, {
54334              addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this),
54335            })
54336          )
54337        : new PlaylistLoader(
54338            src,
54339            this.vhs_,
54340            merge(this.requestOptions_, {
54341              addDateRangesToTextTrack: this.addDateRangesToTextTrack_.bind(this),
54342            })
54343          );
54344    this.setupMainPlaylistLoaderListeners_(); // setup segment loaders
54345    // combined audio/video or just video when alternate audio track is selected
54346
54347    this.mainSegmentLoader_ = new SegmentLoader(
54348      merge(segmentLoaderSettings, {
54349        segmentMetadataTrack: this.segmentMetadataTrack_,
54350        loaderType: 'main',
54351      }),
54352      options
54353    ); // alternate audio track
54354
54355    this.audioSegmentLoader_ = new SegmentLoader(
54356      merge(segmentLoaderSettings, {
54357        loaderType: 'audio',
54358      }),
54359      options
54360    );
54361    this.subtitleSegmentLoader_ = new VTTSegmentLoader(
54362      merge(segmentLoaderSettings, {
54363        loaderType: 'vtt',
54364        featuresNativeTextTracks: this.tech_.featuresNativeTextTracks,
54365        loadVttJs: () =>
54366          new Promise((resolve, reject) => {
54367            function onLoad() {
54368              tech.off('vttjserror', onError);
54369              resolve();
54370            }
54371            function onError() {
54372              tech.off('vttjsloaded', onLoad);
54373              reject();
54374            }
54375            tech.one('vttjsloaded', onLoad);
54376            tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
54377
54378            tech.addWebVttScript_();
54379          }),
54380      }),
54381      options
54382    );
54383    const getBandwidth = () => {
54384      return this.mainSegmentLoader_.bandwidth;
54385    };
54386    this.contentSteeringController_ = new ContentSteeringController(this.vhs_.xhr, getBandwidth);
54387    this.setupSegmentLoaderListeners_();
54388    if (this.bufferBasedABR) {
54389      this.mainPlaylistLoader_.one('loadedplaylist', () => this.startABRTimer_());
54390      this.tech_.on('pause', () => this.stopABRTimer_());
54391      this.tech_.on('play', () => this.startABRTimer_());
54392    } // Create SegmentLoader stat-getters
54393    // mediaRequests_
54394    // mediaRequestsAborted_
54395    // mediaRequestsTimedout_
54396    // mediaRequestsErrored_
54397    // mediaTransferDuration_
54398    // mediaBytesTransferred_
54399    // mediaAppends_
54400
vendor: 6,245 bytes, lines 54401-54583
54401    loaderStats.forEach((stat) => {
54402      this[stat + '_'] = sumLoaderStat.bind(this, stat);
54403    });
54404    this.logger_ = logger('pc');
54405    this.triggeredFmp4Usage = false;
54406    if (this.tech_.preload() === 'none') {
54407      this.loadOnPlay_ = () => {
54408        this.loadOnPlay_ = null;
54409        this.mainPlaylistLoader_.load();
54410      };
54411      this.tech_.one('play', this.loadOnPlay_);
54412    } else {
54413      this.mainPlaylistLoader_.load();
54414    }
54415    this.timeToLoadedData__ = -1;
54416    this.mainAppendsToLoadedData__ = -1;
54417    this.audioAppendsToLoadedData__ = -1;
54418    const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
54419
54420    this.tech_.one(event, () => {
54421      const timeToLoadedDataStart = Date.now();
54422      this.tech_.one('loadeddata', () => {
54423        this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
54424        this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
54425        this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
54426      });
54427    });
54428  }
54429  mainAppendsToLoadedData_() {
54430    return this.mainAppendsToLoadedData__;
54431  }
54432  audioAppendsToLoadedData_() {
54433    return this.audioAppendsToLoadedData__;
54434  }
54435  appendsToLoadedData_() {
54436    const main = this.mainAppendsToLoadedData_();
54437    const audio = this.audioAppendsToLoadedData_();
54438    if (main === -1 || audio === -1) {
54439      return -1;
54440    }
54441    return main + audio;
54442  }
54443  timeToLoadedData_() {
54444    return this.timeToLoadedData__;
54445  }
54446  /**
54447   * Run selectPlaylist and switch to the new playlist if we should
54448   *
54449   * @param {string} [reason=abr] a reason for why the ABR check is made
54450   * @private
54451   */
54452
54453  checkABR_(reason = 'abr') {
54454    const nextPlaylist = this.selectPlaylist();
54455    if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
54456      this.switchMedia_(nextPlaylist, reason);
54457    }
54458  }
54459  switchMedia_(playlist, cause, delay) {
54460    const oldMedia = this.media();
54461    const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
54462    const newId = playlist && (playlist.id || playlist.uri);
54463    if (oldId && oldId !== newId) {
54464      this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
54465      const metadata = {
54466        renditionInfo: {
54467          id: newId,
54468          bandwidth: playlist.attributes.BANDWIDTH,
54469          resolution: playlist.attributes.RESOLUTION,
54470          codecs: playlist.attributes.CODECS,
54471        },
54472        cause,
54473      };
54474      this.trigger({
54475        type: 'renditionselected',
54476        metadata,
54477      });
54478      this.tech_.trigger({
54479        type: 'usage',
54480        name: `vhs-rendition-change-${cause}`,
54481      });
54482    }
54483    this.mainPlaylistLoader_.media(playlist, delay);
54484  }
54485  /**
54486   * A function that ensures we switch our playlists inside of `mediaTypes`
54487   * to match the current `serviceLocation` provided by the contentSteering controller.
54488   * We want to check media types of `AUDIO`, `SUBTITLES`, and `CLOSED-CAPTIONS`.
54489   *
54490   * This should only be called on a DASH playback scenario while using content steering.
54491   * This is necessary due to differences in how media in HLS manifests are generally tied to
54492   * a video playlist, where in DASH that is not always the case.
54493   */
54494
54495  switchMediaForDASHContentSteering_() {
54496    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((type) => {
54497      const mediaType = this.mediaTypes_[type];
54498      const activeGroup = mediaType ? mediaType.activeGroup() : null;
54499      const pathway = this.contentSteeringController_.getPathway();
54500      if (activeGroup && pathway) {
54501        // activeGroup can be an array or a single group
54502        const mediaPlaylists = activeGroup.length
54503          ? activeGroup[0].playlists
54504          : activeGroup.playlists;
54505        const dashMediaPlaylists = mediaPlaylists.filter(
54506          (p) => p.attributes.serviceLocation === pathway
54507        ); // Switch the current active playlist to the correct CDN
54508
54509        if (dashMediaPlaylists.length) {
54510          this.mediaTypes_[type].activePlaylistLoader.media(dashMediaPlaylists[0]);
54511        }
54512      }
54513    });
54514  }
54515  /**
54516   * Start a timer that periodically calls checkABR_
54517   *
54518   * @private
54519   */
54520
54521  startABRTimer_() {
54522    this.stopABRTimer_();
54523    this.abrTimer_ = window__default['default'].setInterval(() => this.checkABR_(), 250);
54524  }
54525  /**
54526   * Stop the timer that periodically calls checkABR_
54527   *
54528   * @private
54529   */
54530
54531  stopABRTimer_() {
54532    // if we're scrubbing, we don't need to pause.
54533    // This getter will be added to Video.js in version 7.11.
54534    if (this.tech_.scrubbing && this.tech_.scrubbing()) {
54535      return;
54536    }
54537    window__default['default'].clearInterval(this.abrTimer_);
54538    this.abrTimer_ = null;
54539  }
54540  /**
54541   * Get a list of playlists for the currently selected audio playlist
54542   *
54543   * @return {Array} the array of audio playlists
54544   */
54545
54546  getAudioTrackPlaylists_() {
54547    const main = this.main();
54548    const defaultPlaylists = (main && main.playlists) || []; // if we don't have any audio groups then we can only
54549    // assume that the audio tracks are contained in main
54550    // playlist array, use that or an empty array.
54551
54552    if (!main || !main.mediaGroups || !main.mediaGroups.AUDIO) {
54553      return defaultPlaylists;
54554    }
54555    const AUDIO = main.mediaGroups.AUDIO;
54556    const groupKeys = Object.keys(AUDIO);
54557    let track; // get the current active track
54558
54559    if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
54560      track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from main if mediaTypes_ isn't setup yet
54561    } else {
54562      // default group is `main` or just the first group.
54563      const defaultGroup = AUDIO.main || (groupKeys.length && AUDIO[groupKeys[0]]);
54564      for (const label in defaultGroup) {
54565        if (defaultGroup[label].default) {
54566          track = {
54567            label,
54568          };
54569          break;
54570        }
54571      }
54572    } // no active track no playlists.
54573
54574    if (!track) {
54575      return defaultPlaylists;
54576    }
54577    const playlists = []; // get all of the playlists that are possible for the
54578    // active track.
54579
54580    for (const group in AUDIO) {
54581      if (AUDIO[group][track.label]) {
54582        const properties = AUDIO[group][track.label];
54583        if (properties.playlists && properties.playlists.length) {
vendor: 5,584 bytes, lines 54584-54727
54584          playlists.push.apply(playlists, properties.playlists);
54585        } else if (properties.uri) {
54586          playlists.push(properties);
54587        } else if (main.playlists.length) {
54588          // if an audio group does not have a uri
54589          // see if we have main playlists that use it as a group.
54590          // if we do then add those to the playlists list.
54591          for (let i = 0; i < main.playlists.length; i++) {
54592            const playlist = main.playlists[i];
54593            if (
54594              playlist.attributes &&
54595              playlist.attributes.AUDIO &&
54596              playlist.attributes.AUDIO === group
54597            ) {
54598              playlists.push(playlist);
54599            }
54600          }
54601        }
54602      }
54603    }
54604    if (!playlists.length) {
54605      return defaultPlaylists;
54606    }
54607    return playlists;
54608  }
54609  /**
54610   * Register event handlers on the main playlist loader. A helper
54611   * function for construction time.
54612   *
54613   * @private
54614   */
54615
54616  setupMainPlaylistLoaderListeners_() {
54617    this.mainPlaylistLoader_.on('loadedmetadata', () => {
54618      const media = this.mainPlaylistLoader_.media();
54619      const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
54620      // timeout the request.
54621
54622      if (
54623        isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())
54624      ) {
54625        this.requestOptions_.timeout = 0;
54626      } else {
54627        this.requestOptions_.timeout = requestTimeout;
54628      } // if this isn't a live video and preload permits, start
54629      // downloading segments
54630
54631      if (media.endList && this.tech_.preload() !== 'none') {
54632        this.mainSegmentLoader_.playlist(media, this.requestOptions_);
54633        this.mainSegmentLoader_.load();
54634      }
54635      setupMediaGroups({
54636        sourceType: this.sourceType_,
54637        segmentLoaders: {
54638          AUDIO: this.audioSegmentLoader_,
54639          SUBTITLES: this.subtitleSegmentLoader_,
54640          main: this.mainSegmentLoader_,
54641        },
54642        tech: this.tech_,
54643        requestOptions: this.requestOptions_,
54644        mainPlaylistLoader: this.mainPlaylistLoader_,
54645        vhs: this.vhs_,
54646        main: this.main(),
54647        mediaTypes: this.mediaTypes_,
54648        excludePlaylist: this.excludePlaylist.bind(this),
54649      });
54650      this.triggerPresenceUsage_(this.main(), media);
54651      this.setupFirstPlay();
54652      if (
54653        !this.mediaTypes_.AUDIO.activePlaylistLoader ||
54654        this.mediaTypes_.AUDIO.activePlaylistLoader.media()
54655      ) {
54656        this.trigger('selectedinitialmedia');
54657      } else {
54658        // We must wait for the active audio playlist loader to
54659        // finish setting up before triggering this event so the
54660        // representations API and EME setup is correct
54661        this.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', () => {
54662          this.trigger('selectedinitialmedia');
54663        });
54664      }
54665    });
54666    this.mainPlaylistLoader_.on('loadedplaylist', () => {
54667      if (this.loadOnPlay_) {
54668        this.tech_.off('play', this.loadOnPlay_);
54669      }
54670      let updatedPlaylist = this.mainPlaylistLoader_.media();
54671      if (!updatedPlaylist) {
54672        // Add content steering listeners on first load and init.
54673        this.attachContentSteeringListeners_();
54674        this.initContentSteeringController_(); // exclude any variants that are not supported by the browser before selecting
54675        // an initial media as the playlist selectors do not consider browser support
54676
54677        this.excludeUnsupportedVariants_();
54678        let selectedMedia;
54679        if (this.enableLowInitialPlaylist) {
54680          selectedMedia = this.selectInitialPlaylist();
54681        }
54682        if (!selectedMedia) {
54683          selectedMedia = this.selectPlaylist();
54684        }
54685        if (!selectedMedia || !this.shouldSwitchToMedia_(selectedMedia)) {
54686          return;
54687        }
54688        this.initialMedia_ = selectedMedia;
54689        this.switchMedia_(this.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
54690        // fire again since the playlist will be requested. In the case of vhs-json
54691        // (where the manifest object is provided as the source), when the media
54692        // playlist's `segments` list is already available, a media playlist won't be
54693        // requested, and loadedplaylist won't fire again, so the playlist handler must be
54694        // called on its own here.
54695
54696        const haveJsonSource = this.sourceType_ === 'vhs-json' && this.initialMedia_.segments;
54697        if (!haveJsonSource) {
54698          return;
54699        }
54700        updatedPlaylist = this.initialMedia_;
54701      }
54702      this.handleUpdatedMediaPlaylist(updatedPlaylist);
54703    });
54704    this.mainPlaylistLoader_.on('error', () => {
54705      const error = this.mainPlaylistLoader_.error;
54706      this.excludePlaylist({
54707        playlistToExclude: error.playlist,
54708        error,
54709      });
54710    });
54711    this.mainPlaylistLoader_.on('mediachanging', () => {
54712      this.mainSegmentLoader_.abort();
54713      this.mainSegmentLoader_.pause();
54714    });
54715    this.mainPlaylistLoader_.on('mediachange', () => {
54716      const media = this.mainPlaylistLoader_.media();
54717      const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
54718      // timeout the request.
54719
54720      if (
54721        isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())
54722      ) {
54723        this.requestOptions_.timeout = 0;
54724      } else {
54725        this.requestOptions_.timeout = requestTimeout;
54726      }
54727      if (this.sourceType_ === 'dash') {
54728        // we don't want to re-request the same hls playlist right after it was changed
54729        this.mainPlaylistLoader_.load();
54730      } // TODO: Create a new event on the PlaylistLoader that signals
54731      // that the segments have changed in some way and use that to
54732      // update the SegmentLoader instead of doing it twice here and
54733      // on `loadedplaylist`
54734
54735      this.mainSegmentLoader_.pause();
54736      this.mainSegmentLoader_.playlist(media, this.requestOptions_);
54737      if (this.waitingForFastQualityPlaylistReceived_) {
54738        this.runFastQualitySwitch_();
54739      } else {
54740        this.mainSegmentLoader_.load();
54741      }
54742      this.tech_.trigger({
54743        type: 'mediachange',
54744        bubbles: true,
54745      });
54746    });
54747    this.mainPlaylistLoader_.on('playlistunchanged', () => {
54748      const updatedPlaylist = this.mainPlaylistLoader_.media(); // ignore unchanged playlists that have already been
54749      // excluded for not-changing. We likely just have a really slowly updating
54750      // playlist.
54751
54752      if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
54753        return;
54754      }
54755      const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);
54756      if (playlistOutdated) {
54757        // Playlist has stopped updating and we're stuck at its end. Try to
54758        // exclude it and switch to another playlist in the hope that that
54759        // one is updating (and give the player a chance to re-adjust to the
54760        // safe live point).
54761        this.excludePlaylist({
54762          error: {
54763            message: 'Playlist no longer updating.',
54764            reason: 'playlist-unchanged',
54765          },
54766        }); // useful for monitoring QoS
54767
54768        this.tech_.trigger('playliststuck');
54769      }
54770    });
54771    this.mainPlaylistLoader_.on('renditiondisabled', () => {
54772      this.tech_.trigger({
54773        type: 'usage',
54774        name: 'vhs-rendition-disabled',
54775      });
54776    });
54777    this.mainPlaylistLoader_.on('renditionenabled', () => {
54778      this.tech_.trigger({
54779        type: 'usage',
54780        name: 'vhs-rendition-enabled',
54781      });
54782    });
54783    const playlistLoaderEvents = [
54784      'manifestrequeststart',
54785      'manifestrequestcomplete',
54786      'manifestparsestart',
54787      'manifestparsecomplete',
54788      'playlistrequeststart',
54789      'playlistrequestcomplete',
54790      'playlistparsestart',
54791      'playlistparsecomplete',
54792      'renditiondisabled',
54793      'renditionenabled',
54794    ];
54795    playlistLoaderEvents.forEach((eventName) => {
54796      this.mainPlaylistLoader_.on(eventName, (metadata) => {
54797        // trigger directly on the player to ensure early events are fired.
54798        this.player_.trigger(_extends__default['default']({}, metadata));
54799      });
54800    });
54801  }
54802  /**
54803   * Given an updated media playlist (whether it was loaded for the first time, or
54804   * refreshed for live playlists), update any relevant properties and state to reflect
54805   * changes in the media that should be accounted for (e.g., cues and duration).
54806   *
54807   * @param {Object} updatedPlaylist the updated media playlist object
54808   *
54809   * @private
54810   */
54811
54812  handleUpdatedMediaPlaylist(updatedPlaylist) {
54813    if (this.useCueTags_) {
54814      this.updateAdCues_(updatedPlaylist);
54815    } // TODO: Create a new event on the PlaylistLoader that signals
54816    // that the segments have changed in some way and use that to
54817    // update the SegmentLoader instead of doing it twice here and
54818    // on `mediachange`
54819
54820    this.mainSegmentLoader_.pause();
54821    this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
54822    if (this.waitingForFastQualityPlaylistReceived_) {
54823      this.runFastQualitySwitch_();
54824    }
54825    this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
54826    // as it is possible that it was temporarily stopped while waiting for
54827    // a playlist (e.g., in case the playlist errored and we re-requested it).
54828
54829    if (!this.tech_.paused()) {
54830      this.mainSegmentLoader_.load();
54831      if (this.audioSegmentLoader_) {
54832        this.audioSegmentLoader_.load();
54833      }
54834    }
54835  }
54836  /**
54837   * A helper function for triggerring presence usage events once per source
54838   *
54839   * @private
54840   */
54841
54842  triggerPresenceUsage_(main, media) {
54843    const mediaGroups = main.mediaGroups || {};
54844    let defaultDemuxed = true;
54845    const audioGroupKeys = Object.keys(mediaGroups.AUDIO);
54846    for (const mediaGroup in mediaGroups.AUDIO) {
54847      for (const label in mediaGroups.AUDIO[mediaGroup]) {
54848        const properties = mediaGroups.AUDIO[mediaGroup][label];
54849        if (!properties.uri) {
54850          defaultDemuxed = false;
54851        }
54852      }
54853    }
54854    if (defaultDemuxed) {
54855      this.tech_.trigger({
54856        type: 'usage',
54857        name: 'vhs-demuxed',
54858      });
54859    }
54860    if (Object.keys(mediaGroups.SUBTITLES).length) {
54861      this.tech_.trigger({
54862        type: 'usage',
54863        name: 'vhs-webvtt',
54864      });
54865    }
54866    if (Vhs$1.Playlist.isAes(media)) {
54867      this.tech_.trigger({
54868        type: 'usage',
54869        name: 'vhs-aes',
54870      });
54871    }
54872    if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
54873      this.tech_.trigger({
54874        type: 'usage',
54875        name: 'vhs-alternate-audio',
54876      });
54877    }
54878    if (this.useCueTags_) {
54879      this.tech_.trigger({
54880        type: 'usage',
54881        name: 'vhs-playlist-cue-tags',
54882      });
54883    }
54884  }
54885  shouldSwitchToMedia_(nextPlaylist) {
54886    const currentPlaylist =
54887      this.mainPlaylistLoader_.media() || this.mainPlaylistLoader_.pendingMedia_;
54888    const currentTime = this.tech_.currentTime();
54889    const bufferLowWaterLine = this.bufferLowWaterLine();
54890    const bufferHighWaterLine = this.bufferHighWaterLine();
54891    const buffered = this.tech_.buffered();
54892    return shouldSwitchToMedia({
54893      buffered,
54894      currentTime,
54895      currentPlaylist,
54896      nextPlaylist,
54897      bufferLowWaterLine,
54898      bufferHighWaterLine,
54899      duration: this.duration(),
54900      bufferBasedABR: this.bufferBasedABR,
54901      log: this.logger_,
54902    });
54903  }
54904  /**
54905   * Register event handlers on the segment loaders. A helper function
54906   * for construction time.
54907   *
54908   * @private
54909   */
54910
54911  setupSegmentLoaderListeners_() {
54912    this.mainSegmentLoader_.on('bandwidthupdate', () => {
54913      // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
54914      // useful to check to see if a rendition switch should be made.
54915      this.checkABR_('bandwidthupdate');
54916      this.tech_.trigger('bandwidthupdate');
54917    });
54918    this.mainSegmentLoader_.on('timeout', () => {
54919      if (this.bufferBasedABR) {
54920        // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
54921        // Here the only consideration is that for buffer based ABR there's no guarantee
54922        // of an immediate switch (since the bandwidth is averaged with a timeout
54923        // bandwidth value of 1), so force a load on the segment loader to keep it going.
54924        this.mainSegmentLoader_.load();
54925      }
54926    }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
54927    // based ABR.
54928
54929    if (!this.bufferBasedABR) {
54930      this.mainSegmentLoader_.on('progress', () => {
54931        this.trigger('progress');
54932      });
54933    }
54934    this.mainSegmentLoader_.on('error', () => {
54935      const error = this.mainSegmentLoader_.error();
54936      this.excludePlaylist({
54937        playlistToExclude: error.playlist,
54938        error,
54939      });
54940    });
54941    this.mainSegmentLoader_.on('appenderror', () => {
54942      this.error = this.mainSegmentLoader_.error_;
54943      this.trigger('error');
54944    });
54945    this.mainSegmentLoader_.on('syncinfoupdate', () => {
54946      this.onSyncInfoUpdate_();
54947    });
54948    this.mainSegmentLoader_.on('timestampoffset', () => {
54949      this.tech_.trigger({
54950        type: 'usage',
54951        name: 'vhs-timestamp-offset',
54952      });
54953    });
54954    this.audioSegmentLoader_.on('syncinfoupdate', () => {
54955      this.onSyncInfoUpdate_();
54956    });
54957    this.audioSegmentLoader_.on('appenderror', () => {
54958      this.error = this.audioSegmentLoader_.error_;
54959      this.trigger('error');
54960    });
54961    this.mainSegmentLoader_.on('ended', () => {
54962      this.logger_('main segment loader ended');
54963      this.onEndOfStream();
54964    }); // There is the possibility of the video segment and the audio segment
54965    // at a current time to be on different timelines. When this occurs, the player
54966    // forwards playback to a point where these two segment types are back on the same
54967    // timeline. This time will be just after the end of the audio segment that is on
54968    // a previous timeline.
54969
54970    this.timelineChangeController_.on('audioTimelineBehind', () => {
54971      const segmentInfo = this.audioSegmentLoader_.pendingSegment_;
54972      if (!segmentInfo || !segmentInfo.segment || !segmentInfo.segment.syncInfo) {
54973        return;
54974      } // Update the current time to just after the faulty audio segment.
54975      // This moves playback to a spot where both audio and video segments
54976      // are on the same timeline.
54977
54978      const newTime = segmentInfo.segment.syncInfo.end + 0.01;
54979      this.tech_.setCurrentTime(newTime);
54980    });
54981    this.mainSegmentLoader_.on('earlyabort', (event) => {
54982      // never try to early abort with the new ABR algorithm
54983      if (this.bufferBasedABR) {
54984        return;
54985      }
54986      this.delegateLoaders_('all', ['abort']);
54987      this.excludePlaylist({
54988        error: {
54989          message:
54990            "Aborted early because there isn't enough bandwidth to complete " +
54991            'the request without rebuffering.',
54992        },
54993        playlistExclusionDuration: ABORT_EARLY_EXCLUSION_SECONDS,
54994      });
54995    });
54996    const updateCodecs = () => {
54997      if (!this.sourceUpdater_.hasCreatedSourceBuffers()) {
54998        return this.tryToCreateSourceBuffers_();
54999      }
55000      const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
55001
55002      if (!codecs) {
55003        return;
55004      }
55005      this.sourceUpdater_.addOrChangeSourceBuffers(codecs);
55006    };
55007    this.mainSegmentLoader_.on('trackinfo', updateCodecs);
55008    this.audioSegmentLoader_.on('trackinfo', updateCodecs);
55009    this.mainSegmentLoader_.on('fmp4', () => {
55010      if (!this.triggeredFmp4Usage) {
55011        this.tech_.trigger({
55012          type: 'usage',
55013          name: 'vhs-fmp4',
55014        });
55015        this.triggeredFmp4Usage = true;
55016      }
55017    });
55018    this.audioSegmentLoader_.on('fmp4', () => {
55019      if (!this.triggeredFmp4Usage) {
55020        this.tech_.trigger({
55021          type: 'usage',
55022          name: 'vhs-fmp4',
55023        });
55024        this.triggeredFmp4Usage = true;
55025      }
55026    });
55027    this.audioSegmentLoader_.on('ended', () => {
55028      this.logger_('audioSegmentLoader ended');
vendor: 4,411 bytes, lines 55029-55163
55029      this.onEndOfStream();
55030    });
55031    const segmentLoaderEvents = [
55032      'segmentselected',
55033      'segmentloadstart',
55034      'segmentloaded',
55035      'segmentkeyloadstart',
55036      'segmentkeyloadcomplete',
55037      'segmentdecryptionstart',
55038      'segmentdecryptioncomplete',
55039      'segmenttransmuxingstart',
55040      'segmenttransmuxingcomplete',
55041      'segmenttransmuxingtrackinfoavailable',
55042      'segmenttransmuxingtiminginfoavailable',
55043      'segmentappendstart',
55044      'appendsdone',
55045      'bandwidthupdated',
55046      'timelinechange',
55047      'codecschange',
55048    ];
55049    segmentLoaderEvents.forEach((eventName) => {
55050      this.mainSegmentLoader_.on(eventName, (metadata) => {
55051        this.player_.trigger(_extends__default['default']({}, metadata));
55052      });
55053      this.audioSegmentLoader_.on(eventName, (metadata) => {
55054        this.player_.trigger(_extends__default['default']({}, metadata));
55055      });
55056      this.subtitleSegmentLoader_.on(eventName, (metadata) => {
55057        this.player_.trigger(_extends__default['default']({}, metadata));
55058      });
55059    });
55060  }
55061  mediaSecondsLoaded_() {
55062    return Math.max(
55063      this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded
55064    );
55065  }
55066  /**
55067   * Call load on our SegmentLoaders
55068   */
55069
55070  load() {
55071    this.mainSegmentLoader_.load();
55072    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
55073      this.audioSegmentLoader_.load();
55074    }
55075    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
55076      this.subtitleSegmentLoader_.load();
55077    }
55078  }
55079  /**
55080   * Call pause on our SegmentLoaders
55081   */
55082
55083  pause() {
55084    this.mainSegmentLoader_.pause();
55085    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
55086      this.audioSegmentLoader_.pause();
55087    }
55088    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
55089      this.subtitleSegmentLoader_.pause();
55090    }
55091  }
55092  /**
55093   * Re-tune playback quality level for the current player
55094   * conditions. This method will perform destructive actions like removing
55095   * already buffered content in order to readjust the currently active
55096   * playlist quickly. This is good for manual quality changes
55097   *
55098   * @private
55099   */
55100
55101  fastQualityChange_(media = this.selectPlaylist()) {
55102    if (media && media === this.mainPlaylistLoader_.media()) {
55103      this.logger_('skipping fastQualityChange because new media is same as old');
55104      return;
55105    }
55106    this.switchMedia_(media, 'fast-quality'); // we would like to avoid race condition when we call fastQuality,
55107    // reset everything and start loading segments from prev segments instead of new because new playlist is not received yet
55108
55109    this.waitingForFastQualityPlaylistReceived_ = true;
55110  }
55111  runFastQualitySwitch_() {
55112    this.waitingForFastQualityPlaylistReceived_ = false; // Delete all buffered data to allow an immediate quality switch.
55113
55114    this.mainSegmentLoader_.pause();
55115    this.mainSegmentLoader_.resetEverything(() => {
55116      this.mainSegmentLoader_.load();
55117    }); // don't need to reset audio as it is reset when media changes
55118  }
55119  /**
55120   * Begin playback.
55121   */
55122
55123  play() {
55124    if (this.setupFirstPlay()) {
55125      return;
55126    }
55127    if (this.tech_.ended()) {
55128      this.tech_.setCurrentTime(0);
55129    }
55130    if (this.hasPlayed_) {
55131      this.load();
55132    }
55133    const seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
55134    // seek forward to the live point
55135
55136    if (this.tech_.duration() === Infinity) {
55137      if (this.tech_.currentTime() < seekable.start(0)) {
55138        return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
55139      }
55140    }
55141  }
55142  /**
55143   * Seek to the latest media position if this is a live video and the
55144   * player and video are loaded and initialized.
55145   */
55146
55147  setupFirstPlay() {
55148    const media = this.mainPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
55149    //  If 1) there is no active media
55150    //     2) the player is paused
55151    //     3) the first play has already been setup
55152    // then exit early
55153
55154    if (!media || this.tech_.paused() || this.hasPlayed_) {
55155      return false;
55156    } // when the video is a live stream and/or has a start time
55157
55158    if (!media.endList || media.start) {
55159      const seekable = this.seekable();
55160      if (!seekable.length) {
55161        // without a seekable range, the player cannot seek to begin buffering at the
55162        // live or start point
55163        return false;
vendor: 10,145 bytes, lines 55164-55423
55164      }
55165      const seekableEnd = seekable.end(0);
55166      let startPoint = seekableEnd;
55167      if (media.start) {
55168        const offset = media.start.timeOffset;
55169        if (offset < 0) {
55170          startPoint = Math.max(seekableEnd + offset, seekable.start(0));
55171        } else {
55172          startPoint = Math.min(seekableEnd, offset);
55173        }
55174      } // trigger firstplay to inform the source handler to ignore the next seek event
55175
55176      this.trigger('firstplay'); // seek to the live point
55177
55178      this.tech_.setCurrentTime(startPoint);
55179    }
55180    this.hasPlayed_ = true; // we can begin loading now that everything is ready
55181
55182    this.load();
55183    return true;
55184  }
55185  /**
55186   * handle the sourceopen event on the MediaSource
55187   *
55188   * @private
55189   */
55190
55191  handleSourceOpen_() {
55192    // Only attempt to create the source buffer if none already exist.
55193    // handleSourceOpen is also called when we are "re-opening" a source buffer
55194    // after `endOfStream` has been called (in response to a seek for instance)
55195    this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
55196    // code in video.js but is required because play() must be invoked
55197    // *after* the media source has opened.
55198
55199    if (this.tech_.autoplay()) {
55200      const playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
55201      // on browsers which return a promise
55202
55203      if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
55204        playPromise.then(null, (e) => {});
55205      }
55206    }
55207    this.trigger('sourceopen');
55208  }
55209  /**
55210   * handle the sourceended event on the MediaSource
55211   *
55212   * @private
55213   */
55214
55215  handleSourceEnded_() {
55216    if (!this.inbandTextTracks_.metadataTrack_) {
55217      return;
55218    }
55219    const cues = this.inbandTextTracks_.metadataTrack_.cues;
55220    if (!cues || !cues.length) {
55221      return;
55222    }
55223    const duration = this.duration();
55224    cues[cues.length - 1].endTime =
55225      isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
55226  }
55227  /**
55228   * handle the durationchange event on the MediaSource
55229   *
55230   * @private
55231   */
55232
55233  handleDurationChange_() {
55234    this.tech_.trigger('durationchange');
55235  }
55236  /**
55237   * Calls endOfStream on the media source when all active stream types have called
55238   * endOfStream
55239   *
55240   * @param {string} streamType
55241   *        Stream type of the segment loader that called endOfStream
55242   * @private
55243   */
55244
55245  onEndOfStream() {
55246    let isEndOfStream = this.mainSegmentLoader_.ended_;
55247    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
55248      const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
55249
55250      if (!mainMediaInfo || mainMediaInfo.hasVideo) {
55251        // if we do not know if the main segment loader contains video yet or if we
55252        // definitively know the main segment loader contains video, then we need to wait
55253        // for both main and audio segment loaders to call endOfStream
55254        isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
55255      } else {
55256        // otherwise just rely on the audio loader
55257        isEndOfStream = this.audioSegmentLoader_.ended_;
55258      }
55259    }
55260    if (!isEndOfStream) {
55261      return;
55262    }
55263    this.stopABRTimer_();
55264    this.sourceUpdater_.endOfStream();
55265  }
55266  /**
55267   * Check if a playlist has stopped being updated
55268   *
55269   * @param {Object} playlist the media playlist object
55270   * @return {boolean} whether the playlist has stopped being updated or not
55271   */
55272
55273  stuckAtPlaylistEnd_(playlist) {
55274    const seekable = this.seekable();
55275    if (!seekable.length) {
55276      // playlist doesn't have enough information to determine whether we are stuck
55277      return false;
55278    }
55279    const expired = this.syncController_.getExpiredTime(playlist, this.duration());
55280    if (expired === null) {
55281      return false;
55282    } // does not use the safe live end to calculate playlist end, since we
55283    // don't want to say we are stuck while there is still content
55284
55285    const absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
55286    const currentTime = this.tech_.currentTime();
55287    const buffered = this.tech_.buffered();
55288    if (!buffered.length) {
55289      // return true if the playhead reached the absolute end of the playlist
55290      return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
55291    }
55292    const bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
55293    // end of playlist
55294
55295    return (
55296      bufferedEnd - currentTime <= SAFE_TIME_DELTA &&
55297      absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA
55298    );
55299  }
55300  /**
55301   * Exclude a playlist for a set amount of time, making it unavailable for selection by
55302   * the rendition selection algorithm, then force a new playlist (rendition) selection.
55303   *
55304   * @param {Object=} playlistToExclude
55305   *                  the playlist to exclude, defaults to the currently selected playlist
55306   * @param {Object=} error
55307   *                  an optional error
55308   * @param {number=} playlistExclusionDuration
55309   *                  an optional number of seconds to exclude the playlist
55310   */
55311
55312  excludePlaylist({
55313    playlistToExclude = this.mainPlaylistLoader_.media(),
55314    error = {},
55315    playlistExclusionDuration,
55316  }) {
55317    // If the `error` was generated by the playlist loader, it will contain
55318    // the playlist we were trying to load (but failed) and that should be
55319    // excluded instead of the currently selected playlist which is likely
55320    // out-of-date in this scenario
55321    playlistToExclude = playlistToExclude || this.mainPlaylistLoader_.media();
55322    playlistExclusionDuration =
55323      playlistExclusionDuration ||
55324      error.playlistExclusionDuration ||
55325      this.playlistExclusionDuration; // If there is no current playlist, then an error occurred while we were
55326    // trying to load the main OR while we were disposing of the tech
55327
55328    if (!playlistToExclude) {
55329      this.error = error;
55330      if (this.mediaSource.readyState !== 'open') {
55331        this.trigger('error');
55332      } else {
55333        this.sourceUpdater_.endOfStream('network');
55334      }
55335      return;
55336    }
55337    playlistToExclude.playlistErrors_++;
55338    const playlists = this.mainPlaylistLoader_.main.playlists;
55339    const enabledPlaylists = playlists.filter(isEnabled);
55340    const isFinalRendition =
55341      enabledPlaylists.length === 1 && enabledPlaylists[0] === playlistToExclude; // Don't exclude the only playlist unless it was excluded
55342    // forever
55343
55344    if (playlists.length === 1 && playlistExclusionDuration !== Infinity) {
55345      videojs.log.warn(
55346        `Problem encountered with playlist ${playlistToExclude.id}. ` +
55347          'Trying again since it is the only playlist.'
55348      );
55349      this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
55350
55351      return this.mainPlaylistLoader_.load(isFinalRendition);
55352    }
55353    if (isFinalRendition) {
55354      // If we're content steering, try other pathways.
55355      if (this.main().contentSteering) {
55356        const pathway = this.pathwayAttribute_(playlistToExclude); // Ignore at least 1 steering manifest refresh.
55357
55358        const reIncludeDelay = this.contentSteeringController_.steeringManifest.ttl * 1000;
55359        this.contentSteeringController_.excludePathway(pathway);
55360        this.excludeThenChangePathway_();
55361        setTimeout(() => {
55362          this.contentSteeringController_.addAvailablePathway(pathway);
55363        }, reIncludeDelay);
55364        return;
55365      } // Since we're on the final non-excluded playlist, and we're about to exclude
55366      // it, instead of erring the player or retrying this playlist, clear out the current
55367      // exclusion list. This allows other playlists to be attempted in case any have been
55368      // fixed.
55369
55370      let reincluded = false;
55371      playlists.forEach((playlist) => {
55372        // skip current playlist which is about to be excluded
55373        if (playlist === playlistToExclude) {
55374          return;
55375        }
55376        const excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
55377
55378        if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
55379          reincluded = true;
55380          delete playlist.excludeUntil;
55381        }
55382      });
55383      if (reincluded) {
55384        videojs.log.warn(
55385          'Removing other playlists from the exclusion list because the last ' +
55386            'rendition is about to be excluded.'
55387        ); // Technically we are retrying a playlist, in that we are simply retrying a previous
55388        // playlist. This is needed for users relying on the retryplaylist event to catch a
55389        // case where the player might be stuck and looping through "dead" playlists.
55390
55391        this.tech_.trigger('retryplaylist');
55392      }
55393    } // Exclude this playlist
55394
55395    let excludeUntil;
55396    if (playlistToExclude.playlistErrors_ > this.maxPlaylistRetries) {
55397      excludeUntil = Infinity;
55398    } else {
55399      excludeUntil = Date.now() + playlistExclusionDuration * 1000;
55400    }
55401    playlistToExclude.excludeUntil = excludeUntil;
55402    if (error.reason) {
55403      playlistToExclude.lastExcludeReason_ = error.reason;
55404    }
55405    this.tech_.trigger('excludeplaylist');
55406    this.tech_.trigger({
55407      type: 'usage',
55408      name: 'vhs-rendition-excluded',
55409    }); // TODO: only load a new playlist if we're excluding the current playlist
55410    // If this function was called with a playlist that's not the current active playlist
55411    // (e.g., media().id !== playlistToExclude.id),
55412    // then a new playlist should not be selected and loaded, as there's nothing wrong with the current playlist.
55413
55414    const nextPlaylist = this.selectPlaylist();
55415    if (!nextPlaylist) {
55416      this.error = 'Playback cannot continue. No available working or supported playlists.';
55417      this.trigger('error');
55418      return;
55419    }
55420    const logFn = error.internal ? this.logger_ : videojs.log.warn;
55421    const errorMessage = error.message ? ' ' + error.message : '';
55422    logFn(
55423      `${error.internal ? 'Internal problem' : 'Problem'} encountered with playlist ${playlistToExclude.id}
55423.` +
55424        `${errorMessage} Switching to playlist ${nextPlaylist.id}.`
55425    ); // if audio group changed reset audio loaders
55426
55427    if (nextPlaylist.attributes.AUDIO !== playlistToExclude.attributes.AUDIO) {
55428      this.delegateLoaders_('audio', ['abort', 'pause']);
55429    } // if subtitle group changed reset subtitle loaders
55430
55431    if (nextPlaylist.attributes.SUBTITLES !== playlistToExclude.attributes.SUBTITLES) {
55432      this.delegateLoaders_('subtitle', ['abort', 'pause']);
55433    }
55434    this.delegateLoaders_('main', ['abort', 'pause']);
55435    const delayDuration = (nextPlaylist.targetDuration / 2) * 1000 || 5 * 1000;
55436    const shouldDelay =
55437      typeof nextPlaylist.lastRequest === 'number' &&
55438      Date.now() - nextPlaylist.lastRequest <= delayDuration; // delay if it's a final rendition or if the last refresh is sooner than half targetDuration
55439
55440    return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
55441  }
55442  /**
55443   * Pause all segment/playlist loaders
55444   */
55445
55446  pauseLoading() {
55447    this.delegateLoaders_('all', ['abort', 'pause']);
55448    this.stopABRTimer_();
55449  }
55450  /**
55451   * Call a set of functions in order on playlist loaders, segment loaders,
55452   * or both types of loaders.
55453   *
55454   * @param {string} filter
55455   *        Filter loaders that should call fnNames using a string. Can be:
55456   *        * all - run on all loaders
55457   *        * audio - run on all audio loaders
55458   *        * subtitle - run on all subtitle loaders
55459   *        * main - run on the main loaders
55460   *
55461   * @param {Array|string} fnNames
55462   *        A string or array of function names to call.
55463   */
55464
55465  delegateLoaders_(filter, fnNames) {
55466    const loaders = [];
55467    const dontFilterPlaylist = filter === 'all';
55468    if (dontFilterPlaylist || filter === 'main') {
55469      loaders.push(this.mainPlaylistLoader_);
55470    }
55471    const mediaTypes = [];
55472    if (dontFilterPlaylist || filter === 'audio') {
55473      mediaTypes.push('AUDIO');
55474    }
55475    if (dontFilterPlaylist || filter === 'subtitle') {
55476      mediaTypes.push('CLOSED-CAPTIONS');
55477      mediaTypes.push('SUBTITLES');
55478    }
55479    mediaTypes.forEach((mediaType) => {
55480      const loader =
55481        this.mediaTypes_[mediaType] && this.mediaTypes_[mediaType].activePlaylistLoader;
55482      if (loader) {
55483        loaders.push(loader);
55484      }
55485    });
55486    ['main', 'audio', 'subtitle'].forEach((name) => {
55487      const loader = this[`${name}SegmentLoader_`];
55488      if (loader && (filter === name || filter === 'all')) {
55489        loaders.push(loader);
55490      }
55491    });
55492    loaders.forEach((loader) =>
55493      fnNames.forEach((fnName) => {
55494        if (typeof loader[fnName] === 'function') {
55495          loader[fnName]();
55496        }
55497      })
55498    );
55499  }
55500  /**
55501   * set the current time on all segment loaders
55502   *
55503   * @param {TimeRange} currentTime the current time to set
55504   * @return {TimeRange} the current time
55505   */
55506
55507  setCurrentTime(currentTime) {
55508    const buffered = findRange(this.tech_.buffered(), currentTime);
55509    if (!(this.mainPlaylistLoader_ && this.mainPlaylistLoader_.media())) {
55510      // return immediately if the metadata is not ready yet
55511      return 0;
55512    } // it's clearly an edge-case but don't thrown an error if asked to
55513    // seek within an empty playlist
55514
55515    if (!this.mainPlaylistLoader_.media().segments) {
55516      return 0;
55517    } // if the seek location is already buffered, continue buffering as usual
55518
55519    if (buffered && buffered.length) {
55520      return currentTime;
55521    } // cancel outstanding requests so we begin buffering at the new
55522    // location
55523
55524    this.mainSegmentLoader_.pause();
55525    this.mainSegmentLoader_.resetEverything();
55526    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
55527      this.audioSegmentLoader_.pause();
55528      this.audioSegmentLoader_.resetEverything();
55529    }
55530    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
55531      this.subtitleSegmentLoader_.pause();
55532      this.subtitleSegmentLoader_.resetEverything();
55533    } // start segment loader loading in case they are paused
55534
55535    this.load();
55536  }
55537  /**
55538   * get the current duration
55539   *
55540   * @return {TimeRange} the duration
55541   */
55542
55543  duration() {
55544    if (!this.mainPlaylistLoader_) {
55545      return 0;
55546    }
55547    const media = this.mainPlaylistLoader_.media();
55548    if (!media) {
55549      // no playlists loaded yet, so can't determine a duration
55550      return 0;
55551    } // Don't rely on the media source for duration in the case of a live playlist since
55552    // setting the native MediaSource's duration to infinity ends up with consequences to
55553    // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
55554    //
vendor: 10,334 bytes, lines 55555-55812
55555    // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
55556    // however, few browsers have support for setLiveSeekableRange()
55557    // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
55558    //
55559    // Until a time when the duration of the media source can be set to infinity, and a
55560    // seekable range specified across browsers, just return Infinity.
55561
55562    if (!media.endList) {
55563      return Infinity;
55564    } // Since this is a VOD video, it is safe to rely on the media source's duration (if
55565    // available). If it's not available, fall back to a playlist-calculated estimate.
55566
55567    if (this.mediaSource) {
55568      return this.mediaSource.duration;
55569    }
55570    return Vhs$1.Playlist.duration(media);
55571  }
55572  /**
55573   * check the seekable range
55574   *
55575   * @return {TimeRange} the seekable range
55576   */
55577
55578  seekable() {
55579    return this.seekable_;
55580  }
55581  getSeekableRange_(playlistLoader, mediaType) {
55582    const media = playlistLoader.media();
55583    if (!media) {
55584      return null;
55585    }
55586    const mediaSequenceSync = this.syncController_.getMediaSequenceSync(mediaType);
55587    if (mediaSequenceSync && mediaSequenceSync.isReliable) {
55588      const start = mediaSequenceSync.start;
55589      const end = mediaSequenceSync.end;
55590      if (!isFinite(start) || !isFinite(end)) {
55591        return null;
55592      }
55593      const liveEdgeDelay = Vhs$1.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media); // Make sure our seekable end is not negative
55594
55595      const calculatedEnd = Math.max(0, end - liveEdgeDelay);
55596      if (calculatedEnd < start) {
55597        return null;
55598      }
55599      return createTimeRanges([[start, calculatedEnd]]);
55600    }
55601    const expired = this.syncController_.getExpiredTime(media, this.duration());
55602    if (expired === null) {
55603      return null;
55604    }
55605    const seekable = Vhs$1.Playlist.seekable(
55606      media,
55607      expired,
55608      Vhs$1.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media)
55609    );
55610    return seekable.length ? seekable : null;
55611  }
55612  computeFinalSeekable_(mainSeekable, audioSeekable) {
55613    if (!audioSeekable) {
55614      return mainSeekable;
55615    }
55616    const mainStart = mainSeekable.start(0);
55617    const mainEnd = mainSeekable.end(0);
55618    const audioStart = audioSeekable.start(0);
55619    const audioEnd = audioSeekable.end(0);
55620    if (audioStart > mainEnd || mainStart > audioEnd) {
55621      // Seekables are far apart, rely on main
55622      return mainSeekable;
55623    } // Return the overlapping seekable range
55624
55625    return createTimeRanges([[Math.max(mainStart, audioStart), Math.min(mainEnd, audioEnd)]]);
55626  }
55627  onSyncInfoUpdate_() {
55628    // TODO check for creation of both source buffers before updating seekable
55629    //
55630    // A fix was made to this function where a check for
55631    // this.sourceUpdater_.hasCreatedSourceBuffers
55632    // was added to ensure that both source buffers were created before seekable was
55633    // updated. However, it originally had a bug where it was checking for a true and
55634    // returning early instead of checking for false. Setting it to check for false to
55635    // return early though created other issues. A call to play() would check for seekable
55636    // end without verifying that a seekable range was present. In addition, even checking
55637    // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
55638    // due to a media update calling load on the segment loaders, skipping a seek to live,
55639    // thereby starting live streams at the beginning of the stream rather than at the end.
55640    //
55641    // This conditional should be fixed to wait for the creation of two source buffers at
55642    // the same time as the other sections of code are fixed to properly seek to live and
55643    // not throw an error due to checking for a seekable end when no seekable range exists.
55644    //
55645    // For now, fall back to the older behavior, with the understanding that the seekable
55646    // range may not be completely correct, leading to a suboptimal initial live point.
55647    if (!this.mainPlaylistLoader_) {
55648      return;
55649    }
55650    const mainSeekable = this.getSeekableRange_(this.mainPlaylistLoader_, 'main');
55651    if (!mainSeekable) {
55652      return;
55653    }
55654    let audioSeekable;
55655    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
55656      audioSeekable = this.getSeekableRange_(this.mediaTypes_.AUDIO.activePlaylistLoader, 'audio');
55657      if (!audioSeekable) {
55658        return;
55659      }
55660    }
55661    const oldSeekable = this.seekable_;
55662    this.seekable_ = this.computeFinalSeekable_(mainSeekable, audioSeekable);
55663    if (!this.seekable_) {
55664      return;
55665    }
55666    if (oldSeekable && oldSeekable.length && this.seekable_.length) {
55667      if (
55668        oldSeekable.start(0) === this.seekable_.start(0) &&
55669        oldSeekable.end(0) === this.seekable_.end(0)
55670      ) {
55671        // Seekable range hasn't changed
55672        return;
55673      }
55674    }
55675    this.logger_(`seekable updated [${printableRange(this.seekable_)}]`);
55676    const metadata = {
55677      seekableRanges: this.seekable_,
55678    };
55679    this.trigger({
55680      type: 'seekablerangeschanged',
55681      metadata,
55682    });
55683    this.tech_.trigger('seekablechanged');
55684  }
55685  /**
55686   * Update the player duration
55687   */
55688
55689  updateDuration(isLive) {
55690    if (this.updateDuration_) {
55691      this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
55692      this.updateDuration_ = null;
55693    }
55694    if (this.mediaSource.readyState !== 'open') {
55695      this.updateDuration_ = this.updateDuration.bind(this, isLive);
55696      this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
55697      return;
55698    }
55699    if (isLive) {
55700      const seekable = this.seekable();
55701      if (!seekable.length) {
55702        return;
55703      } // Even in the case of a live playlist, the native MediaSource's duration should not
55704      // be set to Infinity (even though this would be expected for a live playlist), since
55705      // setting the native MediaSource's duration to infinity ends up with consequences to
55706      // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
55707      //
55708      // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
55709      // however, few browsers have support for setLiveSeekableRange()
55710      // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
55711      //
55712      // Until a time when the duration of the media source can be set to infinity, and a
55713      // seekable range specified across browsers, the duration should be greater than or
55714      // equal to the last possible seekable value.
55715      // MediaSource duration starts as NaN
55716      // It is possible (and probable) that this case will never be reached for many
55717      // sources, since the MediaSource reports duration as the highest value without
55718      // accounting for timestamp offset. For example, if the timestamp offset is -100 and
55719      // we buffered times 0 to 100 with real times of 100 to 200, even though current
55720      // time will be between 0 and 100, the native media source may report the duration
55721      // as 200. However, since we report duration separate from the media source (as
55722      // Infinity), and as long as the native media source duration value is greater than
55723      // our reported seekable range, seeks will work as expected. The large number as
55724      // duration for live is actually a strategy used by some players to work around the
55725      // issue of live seekable ranges cited above.
55726
55727      if (
55728        isNaN(this.mediaSource.duration) ||
55729        this.mediaSource.duration < seekable.end(seekable.length - 1)
55730      ) {
55731        this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
55732      }
55733      return;
55734    }
55735    const buffered = this.tech_.buffered();
55736    let duration = Vhs$1.Playlist.duration(this.mainPlaylistLoader_.media());
55737    if (buffered.length > 0) {
55738      duration = Math.max(duration, buffered.end(buffered.length - 1));
55739    }
55740    if (this.mediaSource.duration !== duration) {
55741      this.sourceUpdater_.setDuration(duration);
55742    }
55743  }
55744  /**
55745   * dispose of the PlaylistController and everything
55746   * that it controls
55747   */
55748
55749  dispose() {
55750    this.trigger('dispose');
55751    this.decrypter_.terminate();
55752    this.mainPlaylistLoader_.dispose();
55753    this.mainSegmentLoader_.dispose();
55754    this.contentSteeringController_.dispose();
55755    this.keyStatusMap_.clear();
55756    if (this.loadOnPlay_) {
55757      this.tech_.off('play', this.loadOnPlay_);
55758    }
55759    ['AUDIO', 'SUBTITLES'].forEach((type) => {
55760      const groups = this.mediaTypes_[type].groups;
55761      for (const id in groups) {
55762        groups[id].forEach((group) => {
55763          if (group.playlistLoader) {
55764            group.playlistLoader.dispose();
55765          }
55766        });
55767      }
55768    });
55769    this.audioSegmentLoader_.dispose();
55770    this.subtitleSegmentLoader_.dispose();
55771    this.sourceUpdater_.dispose();
55772    this.timelineChangeController_.dispose();
55773    this.stopABRTimer_();
55774    if (this.updateDuration_) {
55775      this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
55776    }
55777    this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
55778
55779    this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
55780    this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
55781    this.off();
55782  }
55783  /**
55784   * return the main playlist object if we have one
55785   *
55786   * @return {Object} the main playlist object that we parsed
55787   */
55788
55789  main() {
55790    return this.mainPlaylistLoader_.main;
55791  }
55792  /**
55793   * return the currently selected playlist
55794   *
55795   * @return {Object} the currently selected playlist object that we parsed
55796   */
55797
55798  media() {
55799    // playlist loader will not return media if it has not been fully loaded
55800    return this.mainPlaylistLoader_.media() || this.initialMedia_;
55801  }
55802  areMediaTypesKnown_() {
55803    const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
55804    const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
55805    // otherwise check on the segment loader.
55806
55807    const hasAudioMediaInfo = !usingAudioLoader
55808      ? true
55809      : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
55810
55811    if (!hasMainMediaInfo || !hasAudioMediaInfo) {
55812      return false;
55813    }
55814    return true;
55815  } // find from and to for codec switch event
55816
55817  getCodecsOrExclude_() {
55818    const media = {
55819      main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
55820      audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {},
55821    };
55822    const playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
55823
55824    media.video = media.main;
55825    const playlistCodecs = codecsForPlaylist(this.main(), playlist);
55826    const codecs = {};
55827    const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
55828    if (media.main.hasVideo) {
55829      codecs.video = playlistCodecs.video || media.main.videoCodec || codecs_js.DEFAULT_VIDEO_CODEC;
55830    }
55831    if (media.main.isMuxed) {
55832      codecs.video += `,${playlistCodecs.audio || media.main.audioCodec || codecs_js.DEFAULT_AUDIO_CODEC}`;
55833    }
55834    if ((media.main.hasAudio && !media.main.isMuxed) || media.audio.hasAudio || usingAudioLoader) {
55835      codecs.audio =
55836        playlistCodecs.audio ||
55837        media.main.audioCodec ||
55838        media.audio.audioCodec ||
55839        codecs_js.DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
55840
55841      media.audio.isFmp4 =
55842        media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
55843    } // no codecs, no playback.
55844
55845    if (!codecs.audio && !codecs.video) {
55846      this.excludePlaylist({
55847        playlistToExclude: playlist,
55848        error: {
55849          message: 'Could not determine codecs for playlist.',
55850        },
55851        playlistExclusionDuration: Infinity,
55852      });
55853      return;
55854    } // fmp4 relies on browser support, while ts relies on muxer support
55855
55856    const supportFunction = (isFmp4, codec) =>
55857      isFmp4 ? codecs_js.browserSupportsCodec(codec) : codecs_js.muxerSupportsCodec(codec);
55858    const unsupportedCodecs = {};
55859    let unsupportedAudio;
55860    ['video', 'audio'].forEach(function (type) {
55861      if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
55862        const supporter = media[type].isFmp4 ? 'browser' : 'muxer';
55863        unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
55864        unsupportedCodecs[supporter].push(codecs[type]);
55865        if (type === 'audio') {
55866          unsupportedAudio = supporter;
55867        }
55868      }
55869    });
55870    if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
55871      const audioGroup = playlist.attributes.AUDIO;
vendor: 4,664 bytes, lines 55872-56002
55872      this.main().playlists.forEach((variant) => {
55873        const variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
55874        if (variantAudioGroup === audioGroup && variant !== playlist) {
55875          variant.excludeUntil = Infinity;
55876        }
55877      });
55878      this.logger_(
55879        `excluding audio group ${audioGroup} as ${unsupportedAudio} does not support codec(s): "${codecs.audio}"`
55880      );
55881    } // if we have any unsupported codecs exclude this playlist.
55882
55883    if (Object.keys(unsupportedCodecs).length) {
55884      const message =
55885        Object.keys(unsupportedCodecs).reduce((acc, supporter) => {
55886          if (acc) {
55887            acc += ', ';
55888          }
55889          acc += `${supporter} does not support codec(s): "${unsupportedCodecs[supporter].join(',')}"`;
55890          return acc;
55891        }, '') + '.';
55892      this.excludePlaylist({
55893        playlistToExclude: playlist,
55894        error: {
55895          internal: true,
55896          message,
55897        },
55898        playlistExclusionDuration: Infinity,
55899      });
55900      return;
55901    } // check if codec switching is happening
55902
55903    if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
55904      const switchMessages = [];
55905      ['video', 'audio'].forEach((type) => {
55906        const newCodec = (codecs_js.parseCodecs(this.sourceUpdater_.codecs[type] || '')[0] || {})
55907          .type;
55908        const oldCodec = (codecs_js.parseCodecs(codecs[type] || '')[0] || {}).type;
55909        if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
55910          switchMessages.push(`"${this.sourceUpdater_.codecs[type]}" -> "${codecs[type]}"`);
55911        }
55912      });
55913      if (switchMessages.length) {
55914        this.excludePlaylist({
55915          playlistToExclude: playlist,
55916          error: {
55917            message: `Codec switching not supported: ${switchMessages.join(', ')}.`,
55918            internal: true,
55919          },
55920          playlistExclusionDuration: Infinity,
55921        });
55922        return;
55923      }
55924    } // TODO: when using the muxer shouldn't we just return
55925    // the codecs that the muxer outputs?
55926
55927    return codecs;
55928  }
55929  /**
55930   * Create source buffers and exlude any incompatible renditions.
55931   *
55932   * @private
55933   */
55934
55935  tryToCreateSourceBuffers_() {
55936    // media source is not ready yet or sourceBuffers are already
55937    // created.
55938    if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
55939      return;
55940    }
55941    if (!this.areMediaTypesKnown_()) {
55942      return;
55943    }
55944    const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
55945
55946    if (!codecs) {
55947      return;
55948    }
55949    this.sourceUpdater_.createSourceBuffers(codecs);
55950    const codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
55951    this.excludeIncompatibleVariants_(codecString);
55952  }
55953  /**
55954   * Excludes playlists with codecs that are unsupported by the muxer and browser.
55955   */
55956
55957  excludeUnsupportedVariants_() {
55958    const playlists = this.main().playlists;
55959    const ids = []; // TODO: why don't we have a property to loop through all
55960    // playlist? Why did we ever mix indexes and keys?
55961
55962    Object.keys(playlists).forEach((key) => {
55963      const variant = playlists[key]; // check if we already processed this playlist.
55964
55965      if (ids.indexOf(variant.id) !== -1) {
55966        return;
55967      }
55968      ids.push(variant.id);
55969      const codecs = codecsForPlaylist(this.main, variant);
55970      const unsupported = [];
55971      if (
55972        codecs.audio &&
55973        !codecs_js.muxerSupportsCodec(codecs.audio) &&
55974        !codecs_js.browserSupportsCodec(codecs.audio)
55975      ) {
55976        unsupported.push(`audio codec ${codecs.audio}`);
55977      }
55978      if (
55979        codecs.video &&
55980        !codecs_js.muxerSupportsCodec(codecs.video) &&
55981        !codecs_js.browserSupportsCodec(codecs.video)
55982      ) {
55983        unsupported.push(`video codec ${codecs.video}`);
55984      }
55985      if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
55986        unsupported.push(`text codec ${codecs.text}`);
55987      }
55988      if (unsupported.length) {
55989        variant.excludeUntil = Infinity;
55990        this.logger_(`excluding ${variant.id} for unsupported: ${unsupported.join(', ')}`);
55991      }
55992    });
55993  }
55994  /**
55995   * Exclude playlists that are known to be codec or
55996   * stream-incompatible with the SourceBuffer configuration. For
55997   * instance, Media Source Extensions would cause the video element to
55998   * stall waiting for video data if you switched from a variant with
55999   * video and audio to an audio-only one.
56000   *
56001   * @param {Object} media a media playlist compatible with the current
56002   * set of SourceBuffers. Variants in the current main playlist that
56003   * do not appear to have compatible codec or stream configurations
56004   * will be excluded from the default playlist selection algorithm
56005   * indefinitely.
56006   * @private
56007   */
56008
56009  excludeIncompatibleVariants_(codecString) {
56010    const ids = [];
56011    const playlists = this.main().playlists;
56012    const codecs = unwrapCodecList(codecs_js.parseCodecs(codecString));
56013    const codecCount_ = codecCount(codecs);
56014    const videoDetails = (codecs.video && codecs_js.parseCodecs(codecs.video)[0]) || null;
56015    const audioDetails = (codecs.audio && codecs_js.parseCodecs(codecs.audio)[0]) || null;
56016    Object.keys(playlists).forEach((key) => {
56017      const variant = playlists[key]; // check if we already processed this playlist.
56018      // or it if it is already excluded forever.
56019
56020      if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
56021        return;
56022      }
56023      ids.push(variant.id);
56024      const exclusionReasons = []; // get codecs from the playlist for this variant
56025
56026      const variantCodecs = codecsForPlaylist(this.mainPlaylistLoader_.main, variant);
56027      const variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
56028      // variant is incompatible. Wait for mux.js to probe
56029
56030      if (!variantCodecs.audio && !variantCodecs.video) {
56031        return;
56032      } // TODO: we can support this by removing the
56033      // old media source and creating a new one, but it will take some work.
56034      // The number of streams cannot change
56035
56036      if (variantCodecCount !== codecCount_) {
56037        exclusionReasons.push(`codec count "${variantCodecCount}" !== "${codecCount_}"`);
56038      } // only exclude playlists by codec change, if codecs cannot switch
56039      // during playback.
56040
56041      if (!this.sourceUpdater_.canChangeType()) {
56042        const variantVideoDetails =
56043          (variantCodecs.video && codecs_js.parseCodecs(variantCodecs.video)[0]) || null;
56044        const variantAudioDetails =
56045          (variantCodecs.audio && codecs_js.parseCodecs(variantCodecs.audio)[0]) || null; // the video codec cannot change
56046
56047        if (
56048          variantVideoDetails &&
56049          videoDetails &&
56050          variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()
56051        ) {
56052          exclusionReasons.push(
56053            `video codec "${variantVideoDetails.type}" !== "${videoDetails.type}"`
56054          );
56055        } // the audio codec cannot change
56056
56057        if (
56058          variantAudioDetails &&
56059          audioDetails &&
56060          variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()
56061        ) {
56062          exclusionReasons.push(
56063            `audio codec "${variantAudioDetails.type}" !== "${audioDetails.type}"`
56064          );
56065        }
56066      }
56067      if (exclusionReasons.length) {
56068        variant.excludeUntil = Infinity;
56069        this.logger_(`excluding ${variant.id}: ${exclusionReasons.join(' && ')}`);
56070      }
56071    });
56072  }
56073  updateAdCues_(media) {
56074    let offset = 0;
56075    const seekable = this.seekable();
56076    if (seekable.length) {
56077      offset = seekable.start(0);
56078    }
56079    updateAdCues(media, this.cueTagsTrack_, offset);
56080  }
56081  /**
56082   * Calculates the desired forward buffer length based on current time
56083   *
56084   * @return {number} Desired forward buffer length in seconds
56085   */
56086
56087  goalBufferLength() {
56088    const currentTime = this.tech_.currentTime();
56089    const initial = Config.GOAL_BUFFER_LENGTH;
56090    const rate = Config.GOAL_BUFFER_LENGTH_RATE;
56091    const max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
56092    return Math.min(initial + currentTime * rate, max);
56093  }
56094  /**
56095   * Calculates the desired buffer low water line based on current time
56096   *
56097   * @return {number} Desired buffer low water line in seconds
56098   */
56099
56100  bufferLowWaterLine() {
56101    const currentTime = this.tech_.currentTime();
56102    const initial = Config.BUFFER_LOW_WATER_LINE;
56103    const rate = Config.BUFFER_LOW_WATER_LINE_RATE;
56104    const max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
56105    const newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
56106    return Math.min(initial + currentTime * rate, this.bufferBasedABR ? newMax : max);
56107  }
56108  bufferHighWaterLine() {
56109    return Config.BUFFER_HIGH_WATER_LINE;
56110  }
56111  addDateRangesToTextTrack_(dateRanges) {
56112    createMetadataTrackIfNotExists(this.inbandTextTracks_, 'com.apple.streaming', this.tech_);
56113    addDateRangeMetadata({
56114      inbandTextTracks: this.inbandTextTracks_,
56115      dateRanges,
56116    });
56117  }
56118  addMetadataToTextTrack(dispatchType, metadataArray, videoDuration) {
56119    const timestampOffset = this.sourceUpdater_.videoBuffer
56120      ? this.sourceUpdater_.videoTimestampOffset()
56121      : this.sourceUpdater_.audioTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
56122    // audio/video source with a metadata track, and an alt audio with a metadata track.
56123    // However, this probably won't happen, and if it does it can be handled then.
56124
56125    createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.tech_);
56126    addMetadata({
56127      inbandTextTracks: this.inbandTextTracks_,
56128      metadataArray,
56129      timestampOffset,
56130      videoDuration,
56131    });
56132  }
56133  /**
56134   * Utility for getting the pathway or service location from an HLS or DASH playlist.
56135   *
56136   * @param {Object} playlist for getting pathway from.
56137   * @return the pathway attribute of a playlist
56138   */
56139
56140  pathwayAttribute_(playlist) {
56141    return playlist.attributes['PATHWAY-ID'] || playlist.attributes.serviceLocation;
56142  }
56143  /**
56144   * Initialize available pathways and apply the tag properties.
56145   */
56146
vendor: 5,109 bytes, lines 56147-56285
56147  initContentSteeringController_() {
56148    const main = this.main();
56149    if (!main.contentSteering) {
56150      return;
56151    }
56152    for (const playlist of main.playlists) {
56153      this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
56154    }
56155    this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering); // request the steering manifest immediately if queryBeforeStart is set.
56156
56157    if (this.contentSteeringController_.queryBeforeStart) {
56158      // When queryBeforeStart is true, initial request should omit steering parameters.
56159      this.contentSteeringController_.requestSteeringManifest(true);
56160      return;
56161    } // otherwise start content steering after playback starts
56162
56163    this.tech_.one('canplay', () => {
56164      this.contentSteeringController_.requestSteeringManifest();
56165    });
56166  }
56167  /**
56168   * Reset the content steering controller and re-init.
56169   */
56170
56171  resetContentSteeringController_() {
56172    this.contentSteeringController_.clearAvailablePathways();
56173    this.contentSteeringController_.dispose();
56174    this.initContentSteeringController_();
56175  }
56176  /**
56177   * Attaches the listeners for content steering.
56178   */
56179
56180  attachContentSteeringListeners_() {
56181    this.contentSteeringController_.on(
56182      'content-steering',
56183      this.excludeThenChangePathway_.bind(this)
56184    );
56185    const contentSteeringEvents = [
56186      'contentsteeringloadstart',
56187      'contentsteeringloadcomplete',
56188      'contentsteeringparsed',
56189    ];
56190    contentSteeringEvents.forEach((eventName) => {
56191      this.contentSteeringController_.on(eventName, (metadata) => {
56192        this.trigger(_extends__default['default']({}, metadata));
56193      });
56194    });
56195    if (this.sourceType_ === 'dash') {
56196      this.mainPlaylistLoader_.on('loadedplaylist', () => {
56197        const main = this.main(); // check if steering tag or pathways changed.
56198
56199        const didDashTagChange = this.contentSteeringController_.didDASHTagChange(
56200          main.uri,
56201          main.contentSteering
56202        );
56203        const didPathwaysChange = () => {
56204          const availablePathways = this.contentSteeringController_.getAvailablePathways();
56205          const newPathways = [];
56206          for (const playlist of main.playlists) {
56207            const serviceLocation = playlist.attributes.serviceLocation;
56208            if (serviceLocation) {
56209              newPathways.push(serviceLocation);
56210              if (!availablePathways.has(serviceLocation)) {
56211                return true;
56212              }
56213            }
56214          } // If we have no new serviceLocations and previously had availablePathways
56215
56216          if (!newPathways.length && availablePathways.size) {
56217            return true;
56218          }
56219          return false;
56220        };
56221        if (didDashTagChange || didPathwaysChange()) {
56222          this.resetContentSteeringController_();
56223        }
56224      });
56225    }
56226  }
56227  /**
56228   * Simple exclude and change playlist logic for content steering.
56229   */
56230
56231  excludeThenChangePathway_() {
56232    const currentPathway = this.contentSteeringController_.getPathway();
56233    if (!currentPathway) {
56234      return;
56235    }
56236    this.handlePathwayClones_();
56237    const main = this.main();
56238    const playlists = main.playlists;
56239    const ids = new Set();
56240    let didEnablePlaylists = false;
56241    Object.keys(playlists).forEach((key) => {
56242      const variant = playlists[key];
56243      const pathwayId = this.pathwayAttribute_(variant);
56244      const differentPathwayId = pathwayId && currentPathway !== pathwayId;
56245      const steeringExclusion =
56246        variant.excludeUntil === Infinity && variant.lastExcludeReason_ === 'content-steering';
56247      if (steeringExclusion && !differentPathwayId) {
56248        delete variant.excludeUntil;
56249        delete variant.lastExcludeReason_;
56250        didEnablePlaylists = true;
56251      }
56252      const noExcludeUntil = !variant.excludeUntil && variant.excludeUntil !== Infinity;
56253      const shouldExclude = !ids.has(variant.id) && differentPathwayId && noExcludeUntil;
56254      if (!shouldExclude) {
56255        return;
56256      }
56257      ids.add(variant.id);
56258      variant.excludeUntil = Infinity;
56259      variant.lastExcludeReason_ = 'content-steering'; // TODO: kind of spammy, maybe move this.
56260
56261      this.logger_(`excluding ${variant.id} for ${variant.lastExcludeReason_}`);
56262    });
56263    if (this.contentSteeringController_.manifestType_ === 'DASH') {
56264      Object.keys(this.mediaTypes_).forEach((key) => {
56265        const type = this.mediaTypes_[key];
56266        if (type.activePlaylistLoader) {
56267          const currentPlaylist = type.activePlaylistLoader.media_; // Check if the current media playlist matches the current CDN
56268
56269          if (currentPlaylist && currentPlaylist.attributes.serviceLocation !== currentPathway) {
56270            didEnablePlaylists = true;
56271          }
56272        }
56273      });
56274    }
56275    if (didEnablePlaylists) {
56276      this.changeSegmentPathway_();
56277    }
56278  }
56279  /**
56280   * Add, update, or delete playlists and media groups for
56281   * the pathway clones for HLS Content Steering.
56282   *
56283   * See https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
56284   *
56285   * NOTE: Pathway cloning does not currently support the `PER_VARIA
56285NT_URIS` and
56286   * `PER_RENDITION_URIS` as we do not handle `STABLE-VARIANT-ID` or
56287   * `STABLE-RENDITION-ID` values.
56288   */
56289
56290  handlePathwayClones_() {
56291    const main = this.main();
56292    const playlists = main.playlists;
56293    const currentPathwayClones = this.contentSteeringController_.currentPathwayClones;
56294    const nextPathwayClones = this.contentSteeringController_.nextPathwayClones;
56295    const hasClones =
56296      (currentPathwayClones && currentPathwayClones.size) ||
56297      (nextPathwayClones && nextPathwayClones.size);
56298    if (!hasClones) {
56299      return;
56300    }
56301    for (const [id, clone] of currentPathwayClones.entries()) {
56302      const newClone = nextPathwayClones.get(id); // Delete the old pathway clone.
56303
56304      if (!newClone) {
56305        this.mainPlaylistLoader_.updateOrDeleteClone(clone);
56306        this.contentSteeringController_.excludePathway(id);
56307      }
56308    }
56309    for (const [id, clone] of nextPathwayClones.entries()) {
56310      const oldClone = currentPathwayClones.get(id); // Create a new pathway if it is a new pathway clone object.
56311
56312      if (!oldClone) {
56313        const playlistsToClone = playlists.filter((p) => {
56314          return p.attributes['PATHWAY-ID'] === clone['BASE-ID'];
56315        });
56316        playlistsToClone.forEach((p) => {
56317          this.mainPlaylistLoader_.addClonePathway(clone, p);
56318        });
56319        this.contentSteeringController_.addAvailablePathway(id);
56320        continue;
56321      } // There have not been changes to the pathway clone object, so skip.
56322
56323      if (this.equalPathwayClones_(oldClone, clone)) {
56324        continue;
56325      } // Update a preexisting cloned pathway.
56326      // True is set for the update flag.
56327
56328      this.mainPlaylistLoader_.updateOrDeleteClone(clone, true);
56329      this.contentSteeringController_.addAvailablePathway(id);
56330    } // Deep copy contents of next to current pathways.
56331
56332    this.contentSteeringController_.currentPathwayClones = new Map(
56333      JSON.parse(JSON.stringify([...nextPathwayClones]))
56334    );
56335  }
56336  /**
56337   * Determines whether two pathway clone objects are equivalent.
56338   *
56339   * @param {Object} a The first pathway clone object.
56340   * @param {Object} b The second pathway clone object.
56341   * @return {boolean} True if the pathway clone objects are equal, false otherwise.
56342   */
56343
56344  equalPathwayClones_(a, b) {
56345    if (
56346      a['BASE-ID'] !== b['BASE-ID'] ||
56347      a.ID !== b.ID ||
56348      a['URI-REPLACEMENT'].HOST !== b['URI-REPLACEMENT'].HOST
56349    ) {
56350      return false;
56351    }
56352    const aParams = a['URI-REPLACEMENT'].PARAMS;
56353    const bParams = b['URI-REPLACEMENT'].PARAMS; // We need to iterate through both lists of params because one could be
56354    // missing a parameter that the other has.
56355
56356    for (const p in aParams) {
56357      if (aParams[p] !== bParams[p]) {
56358        return false;
56359      }
56360    }
56361    for (const p in bParams) {
56362      if (aParams[p] !== bParams[p]) {
56363        return false;
56364      }
56365    }
56366    return true;
56367  }
56368  /**
56369   * Changes the current playlists for audio, video and subtitles after a new pathway
56370   * is chosen from content steering.
56371   */
56372
56373  changeSegmentPathway_() {
56374    const nextPlaylist = this.selectPlaylist();
56375    this.pauseLoading(); // Switch audio and text track playlists if necessary in DASH
56376
56377    if (this.contentSteeringController_.manifestType_ === 'DASH') {
56378      this.switchMediaForDASHContentSteering_();
56379    }
56380    this.switchMedia_(nextPlaylist, 'content-steering');
56381  }
56382  /**
56383   * Iterates through playlists and check their keyId set and compare with the
56384   * keyStatusMap, only enable playlists that have a usable key. If the playlist
56385   * has no keyId leave it enabled by default.
56386   */
56387
56388  excludeNonUsablePlaylistsByKeyId_() {
56389    if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
56390      return;
56391    }
56392    let nonUsableKeyStatusCount = 0;
56393    const NON_USABLE = 'non-usable';
vendor: 9,158 bytes, lines 56394-56635
56394    this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
56395      const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist); // If the playlist doesn't have keyIDs lets not exclude it.
56396
56397      if (!keyIdSet || !keyIdSet.size) {
56398        return;
56399      }
56400      keyIdSet.forEach((key) => {
56401        const USABLE = 'usable';
56402        const hasUsableKeyStatus =
56403          this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
56404        const nonUsableExclusion =
56405          playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
56406        if (!hasUsableKeyStatus) {
56407          // Only exclude playlists that haven't already been excluded as non-usable.
56408          if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
56409            playlist.excludeUntil = Infinity;
56410            playlist.lastExcludeReason_ = NON_USABLE;
56411            this.logger_(
56412              `excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`
56413            );
56414          } // count all nonUsableKeyStatus
56415
56416          nonUsableKeyStatusCount++;
56417        } else if (hasUsableKeyStatus && nonUsableExclusion) {
56418          delete playlist.excludeUntil;
56419          delete playlist.lastExcludeReason_;
56420          this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
56421        }
56422      });
56423    }); // If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
56424
56425    if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
56426      this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
56427        const isNonHD =
56428          playlist &&
56429          playlist.attributes &&
56430          playlist.attributes.RESOLUTION &&
56431          playlist.attributes.RESOLUTION.height < 720;
56432        const excludedForNonUsableKey =
56433          playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
56434        if (isNonHD && excludedForNonUsableKey) {
56435          // Only delete the excludeUntil so we don't try and re-exclude these playlists.
56436          delete playlist.excludeUntil;
56437          videojs.log.warn(
56438            `enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`
56439          );
56440        }
56441      });
56442    }
56443  }
56444  /**
56445   * Adds a keystatus to the keystatus map, tries to convert to string if necessary.
56446   *
56447   * @param {any} keyId the keyId to add a status for
56448   * @param {string} status the status of the keyId
56449   */
56450
56451  addKeyStatus_(keyId, status) {
56452    const isString = typeof keyId === 'string';
56453    const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
56454    const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
56455    this.logger_(
56456      `KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`
56457    );
56458    this.keyStatusMap_.set(formattedKeyIdString, status);
56459  }
56460  /**
56461   * Utility function for adding key status to the keyStatusMap and filtering usable encrypted playlists.
56462   *
56463   * @param {any} keyId the keyId from the keystatuschange event
56464   * @param {string} status the key status string
56465   */
56466
56467  updatePlaylistByKeyStatus(keyId, status) {
56468    this.addKeyStatus_(keyId, status);
56469    if (!this.waitingForFastQualityPlaylistReceived_) {
56470      this.excludeNonUsableThenChangePlaylist_();
56471    } // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
56472
56473    this.mainPlaylistLoader_.off(
56474      'loadedplaylist',
56475      this.excludeNonUsableThenChangePlaylist_.bind(this)
56476    );
56477    this.mainPlaylistLoader_.on(
56478      'loadedplaylist',
56479      this.excludeNonUsableThenChangePlaylist_.bind(this)
56480    );
56481  }
56482  excludeNonUsableThenChangePlaylist_() {
56483    this.excludeNonUsablePlaylistsByKeyId_();
56484    this.fastQualityChange_();
56485  }
56486}
56487
56488/**
56489 * Returns a function that acts as the Enable/disable playlist function.
56490 *
56491 * @param {PlaylistLoader} loader - The main playlist loader
56492 * @param {string} playlistID - id of the playlist
56493 * @param {Function} changePlaylistFn - A function to be called after a
56494 * playlist's enabled-state has been changed. Will NOT be called if a
56495 * playlist's enabled-state is unchanged
56496 * @param {boolean=} enable - Value to set the playlist enabled-state to
56497 * or if undefined returns the current enabled-state for the playlist
56498 * @return {Function} Function for setting/getting enabled
56499 */
56500
56501const enableFunction = (loader, playlistID, changePlaylistFn) => (enable) => {
56502  const playlist = loader.main.playlists[playlistID];
56503  const incompatible = isIncompatible(playlist);
56504  const currentlyEnabled = isEnabled(playlist);
56505  if (typeof enable === 'undefined') {
56506    return currentlyEnabled;
56507  }
56508  if (enable) {
56509    delete playlist.disabled;
56510  } else {
56511    playlist.disabled = true;
56512  }
56513  const metadata = {
56514    renditionInfo: {
56515      id: playlistID,
56516      bandwidth: playlist.attributes.BANDWIDTH,
56517      resolution: playlist.attributes.RESOLUTION,
56518      codecs: playlist.attributes.CODECS,
56519    },
56520    cause: 'fast-quality',
56521  };
56522  if (enable !== currentlyEnabled && !incompatible) {
56523    // Ensure the outside world knows about our changes
56524    changePlaylistFn(playlist);
56525    if (enable) {
56526      loader.trigger({
56527        type: 'renditionenabled',
56528        metadata,
56529      });
56530    } else {
56531      loader.trigger({
56532        type: 'renditiondisabled',
56533        metadata,
56534      });
56535    }
56536  }
56537  return enable;
56538};
56539/**
56540 * The representation object encapsulates the publicly visible information
56541 * in a media playlist along with a setter/getter-type function (enabled)
56542 * for changing the enabled-state of a particular playlist entry
56543 *
56544 * @class Representation
56545 */
56546
56547class Representation {
56548  constructor(vhsHandler, playlist, id) {
56549    const { playlistController_: pc } = vhsHandler;
56550    const qualityChangeFunction = pc.fastQualityChange_.bind(pc); // some playlist attributes are optional
56551
56552    if (playlist.attributes) {
56553      const resolution = playlist.attributes.RESOLUTION;
56554      this.width = resolution && resolution.width;
56555      this.height = resolution && resolution.height;
56556      this.bandwidth = playlist.attributes.BANDWIDTH;
56557      this.frameRate = playlist.attributes['FRAME-RATE'];
56558    }
56559    this.codecs = codecsForPlaylist(pc.main(), playlist);
56560    this.playlist = playlist; // The id is simply the ordinality of the media playlist
56561    // within the main playlist
56562
56563    this.id = id; // Partially-apply the enableFunction to create a playlist-
56564    // specific variant
56565
56566    this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
56567  }
56568}
56569/**
56570 * A mixin function that adds the `representations` api to an instance
56571 * of the VhsHandler class
56572 *
56573 * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
56574 * representation API into
56575 */
56576
56577const renditionSelectionMixin = function (vhsHandler) {
56578  // Add a single API-specific function to the VhsHandler instance
56579  vhsHandler.representations = () => {
56580    const main = vhsHandler.playlistController_.main();
56581    const playlists = isAudioOnly(main)
56582      ? vhsHandler.playlistController_.getAudioTrackPlaylists_()
56583      : main.playlists;
56584    if (!playlists) {
56585      return [];
56586    }
56587    return playlists
56588      .filter((media) => !isIncompatible(media))
56589      .map((e, i) => new Representation(vhsHandler, e, e.id));
56590  };
56591};
56592
56593/**
56594 * @file playback-watcher.js
56595 *
56596 * Playback starts, and now my watch begins. It shall not end until my death. I shall
56597 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
56598 * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
56599 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
56600 * my life and honor to the Playback Watch, for this Player and all the Players to come.
56601 */
56602
56603const timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
56604/**
56605 * @class PlaybackWatcher
56606 */
56607
56608class PlaybackWatcher extends videojs.EventTarget {
56609  /**
56610   * Represents an PlaybackWatcher object.
56611   *
56612   * @class
56613   * @param {Object} options an object that includes the tech and settings
56614   */
56615  constructor(options) {
56616    super();
56617    this.playlistController_ = options.playlistController;
56618    this.tech_ = options.tech;
56619    this.seekable = options.seekable;
56620    this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
56621    this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
56622    this.media = options.media;
56623    this.playedRanges_ = [];
56624    this.consecutiveUpdates = 0;
56625    this.lastRecordedTime = null;
56626    this.checkCurrentTimeTimeout_ = null;
56627    this.logger_ = logger('PlaybackWatcher');
56628    this.logger_('initialize');
56629    const playHandler = () => this.monitorCurrentTime_();
56630    const canPlayHandler = () => this.monitorCurrentTime_();
56631    const waitingHandler = () => this.techWaiting_();
56632    const cancelTimerHandler = () => this.resetTimeUpdate_();
56633    const pc = this.playlistController_;
56634    const loaderTypes = ['main', 'subtitle', 'audio'];
56635    const loaderChecks = {};
vendor: 8,958 bytes, lines 56636-56875
56636    loaderTypes.forEach((type) => {
56637      loaderChecks[type] = {
56638        reset: () => this.resetSegmentDownloads_(type),
56639        updateend: () => this.checkSegmentDownloads_(type),
56640      };
56641      pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
56642      // isn't changing we want to reset. We cannot assume that the new rendition
56643      // will also be stalled, until after new appends.
56644
56645      pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
56646      // This prevents one segment playlists (single vtt or single segment content)
56647      // from being detected as stalling. As the buffer will not change in those cases, since
56648      // the buffer is the entire video duration.
56649
56650      this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
56651    });
56652    /**
56653     * We check if a seek was into a gap through the following steps:
56654     * 1. We get a seeking event and we do not get a seeked event. This means that
56655     *    a seek was attempted but not completed.
56656     * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
56657     *    removed everything from our buffer and appended a segment, and should be ready
56658     *    to check for gaps.
56659     */
56660
56661    const setSeekingHandlers = (fn) => {
56662      ['main', 'audio'].forEach((type) => {
56663        pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
56664      });
56665    };
56666    this.seekingAppendCheck_ = () => {
56667      if (this.fixesBadSeeks_()) {
56668        this.consecutiveUpdates = 0;
56669        this.lastRecordedTime = this.tech_.currentTime();
56670        setSeekingHandlers('off');
56671      }
56672    };
56673    this.clearSeekingAppendCheck_ = () => setSeekingHandlers('off');
56674    this.watchForBadSeeking_ = () => {
56675      this.clearSeekingAppendCheck_();
56676      setSeekingHandlers('on');
56677    };
56678    this.tech_.on('seeked', this.clearSeekingAppendCheck_);
56679    this.tech_.on('seeking', this.watchForBadSeeking_);
56680    this.tech_.on('waiting', waitingHandler);
56681    this.tech_.on(timerCancelEvents, cancelTimerHandler);
56682    this.tech_.on('canplay', canPlayHandler);
56683    /*
56684      An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
56685      is surfaced in one of two ways:
56686       1)  The `waiting` event is fired before the player has buffered content, making it impossible
56687          to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
56688          we can check if playback is stalled due to a gap, and skip the gap if necessary.
56689      2)  A source with a gap at the beginning of the stream is loaded programatically while the player
56690          is in a playing state. To catch this case, it's important that our one-time play listener is setup
56691          even if the player is in a playing state
56692    */
56693
56694    this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
56695
56696    this.dispose = () => {
56697      this.clearSeekingAppendCheck_();
56698      this.logger_('dispose');
56699      this.tech_.off('waiting', waitingHandler);
56700      this.tech_.off(timerCancelEvents, cancelTimerHandler);
56701      this.tech_.off('canplay', canPlayHandler);
56702      this.tech_.off('play', playHandler);
56703      this.tech_.off('seeking', this.watchForBadSeeking_);
56704      this.tech_.off('seeked', this.clearSeekingAppendCheck_);
56705      loaderTypes.forEach((type) => {
56706        pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
56707        pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
56708        this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
56709      });
56710      if (this.checkCurrentTimeTimeout_) {
56711        window__default['default'].clearTimeout(this.checkCurrentTimeTimeout_);
56712      }
56713      this.resetTimeUpdate_();
56714    };
56715  }
56716  /**
56717   * Periodically check current time to see if playback stopped
56718   *
56719   * @private
56720   */
56721
56722  monitorCurrentTime_() {
56723    this.checkCurrentTime_();
56724    if (this.checkCurrentTimeTimeout_) {
56725      window__default['default'].clearTimeout(this.checkCurrentTimeTimeout_);
56726    } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
56727
56728    this.checkCurrentTimeTimeout_ = window__default['default'].setTimeout(
56729      this.monitorCurrentTime_.bind(this),
56730      250
56731    );
56732  }
56733  /**
56734   * Reset stalled download stats for a specific type of loader
56735   *
56736   * @param {string} type
56737   *        The segment loader type to check.
56738   *
56739   * @listens SegmentLoader#playlistupdate
56740   * @listens Tech#seeking
56741   * @listens Tech#seeked
56742   */
56743
56744  resetSegmentDownloads_(type) {
56745    const loader = this.playlistController_[`${type}SegmentLoader_`];
56746    if (this[`${type}StalledDownloads_`] > 0) {
56747      this.logger_(`resetting possible stalled download count for ${type} loader`);
56748    }
56749    this[`${type}StalledDownloads_`] = 0;
56750    this[`${type}Buffered_`] = loader.buffered_();
56751  }
56752  /**
56753   * Checks on every segment `appendsdone` to see
56754   * if segment appends are making progress. If they are not
56755   * and we are still downloading bytes. We exclude the playlist.
56756   *
56757   * @param {string} type
56758   *        The segment loader type to check.
56759   *
56760   * @listens SegmentLoader#appendsdone
56761   */
56762
56763  checkSegmentDownloads_(type) {
56764    const pc = this.playlistController_;
56765    const loader = pc[`${type}SegmentLoader_`];
56766    const buffered = loader.buffered_();
56767    const isBufferedDifferent = isRangeDifferent(this[`${type}Buffered_`], buffered);
56768    this[`${type}Buffered_`] = buffered; // if another watcher is going to fix the issue or
56769    // the buffered value for this loader changed
56770    // appends are working
56771
56772    if (isBufferedDifferent) {
56773      const metadata = {
56774        bufferedRanges: buffered,
56775      };
56776      pc.trigger({
56777        type: 'bufferedrangeschanged',
56778        metadata,
56779      });
56780      this.resetSegmentDownloads_(type);
56781      return;
56782    }
56783    this[`${type}StalledDownloads_`]++;
56784    this.logger_(
56785      `found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`,
56786      {
56787        playlistId: loader.playlist_ && loader.playlist_.id,
56788        buffered: timeRangesToArray(buffered),
56789      }
56790    ); // after 10 possibly stalled appends with no reset, exclude
56791
56792    if (this[`${type}StalledDownloads_`] < 10) {
56793      return;
56794    }
56795    this.logger_(`${type} loader stalled download exclusion`);
56796    this.resetSegmentDownloads_(type);
56797    this.tech_.trigger({
56798      type: 'usage',
56799      name: `vhs-${type}-download-exclusion`,
56800    });
56801    if (type === 'subtitle') {
56802      return;
56803    } // TODO: should we exclude audio tracks rather than main tracks
56804    // when type is audio?
56805
56806    pc.excludePlaylist({
56807      error: {
56808        message: `Excessive ${type} segment downloading detected.`,
56809      },
56810      playlistExclusionDuration: Infinity,
56811    });
56812  }
56813  /**
56814   * The purpose of this function is to emulate the "waiting" event on
56815   * browsers that do not emit it when they are waiting for more
56816   * data to continue playback
56817   *
56818   * @private
56819   */
56820
56821  checkCurrentTime_() {
56822    if (this.tech_.paused() || this.tech_.seeking()) {
56823      return;
56824    }
56825    const currentTime = this.tech_.currentTime();
56826    const buffered = this.tech_.buffered();
56827    if (
56828      this.lastRecordedTime === currentTime &&
56829      (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))
56830    ) {
56831      // If current time is at the end of the final buffered region, then any playback
56832      // stall is most likely caused by buffering in a low bandwidth environment. The tech
56833      // should fire a `waiting` event in this scenario, but due to browser and tech
56834      // inconsistencies. Calling `techWaiting_` here allows us to simulate
56835      // responding to a native `waiting` event when the tech fails to emit one.
56836      return this.techWaiting_();
56837    }
56838    if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
56839      this.consecutiveUpdates++;
56840      this.waiting_();
56841    } else if (currentTime === this.lastRecordedTime) {
56842      this.consecutiveUpdates++;
56843    } else {
56844      this.playedRanges_.push(createTimeRanges([this.lastRecordedTime, currentTime]));
56845      const metadata = {
56846        playedRanges: this.playedRanges_,
56847      };
56848      this.playlistController_.trigger({
56849        type: 'playedrangeschanged',
56850        metadata,
56851      });
56852      this.consecutiveUpdates = 0;
56853      this.lastRecordedTime = currentTime;
56854    }
56855  }
56856  /**
56857   * Resets the 'timeupdate' mechanism designed to detect that we are stalled
56858   *
56859   * @private
56860   */
56861
56862  resetTimeUpdate_() {
56863    this.consecutiveUpdates = 0;
56864  }
56865  /**
56866   * Fixes situations where there's a bad seek
56867   *
56868   * @return {boolean} whether an action was taken to fix the seek
56869   * @private
56870   */
56871
56872  fixesBadSeeks_() {
56873    const seeking = this.tech_.seeking();
56874    if (!seeking) {
56875      return false;
vendor: 7,242 bytes, lines 56876-57052
56876    } // TODO: It's possible that these seekable checks should be moved out of this function
56877    // and into a function that runs on seekablechange. It's also possible that we only need
56878    // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
56879    // seekable range.
56880
56881    const seekable = this.seekable();
56882    const currentTime = this.tech_.currentTime();
56883    const isAfterSeekableRange = this.afterSeekableWindow_(
56884      seekable,
56885      currentTime,
56886      this.media(),
56887      this.allowSeeksWithinUnsafeLiveWindow
56888    );
56889    let seekTo;
56890    if (isAfterSeekableRange) {
56891      const seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
56892
56893      seekTo = seekableEnd;
56894    }
56895    if (this.beforeSeekableWindow_(seekable, currentTime)) {
56896      const seekableStart = seekable.start(0); // sync to the beginning of the live window
56897      // provide a buffer of .1 seconds to handle rounding/imprecise numbers
56898
56899      seekTo =
56900        seekableStart +
56901        // if the playlist is too short and the seekable range is an exact time (can
56902        // happen in live with a 3 segment playlist), then don't use a time delta
56903        (seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
56904    }
56905    if (typeof seekTo !== 'undefined') {
56906      this.logger_(
56907        `Trying to seek outside of seekable at time ${currentTime} with ` +
56908          `seekable range ${printableRange(seekable)}. Seeking to ` +
56909          `${seekTo}.`
56910      );
56911      this.tech_.setCurrentTime(seekTo);
56912      return true;
56913    }
56914    const sourceUpdater = this.playlistController_.sourceUpdater_;
56915    const buffered = this.tech_.buffered();
56916    const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
56917    const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
56918    const media = this.media(); // verify that at least two segment durations or one part duration have been
56919    // appended before checking for a gap.
56920
56921    const minAppendedDuration = media.partTargetDuration
56922      ? media.partTargetDuration
56923      : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
56924    // appended before checking for a gap.
56925
56926    const bufferedToCheck = [audioBuffered, videoBuffered];
56927    for (let i = 0; i < bufferedToCheck.length; i++) {
56928      // skip null buffered
56929      if (!bufferedToCheck[i]) {
56930        continue;
56931      }
56932      const timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
56933      // duration behind we haven't appended enough to call this a bad seek.
56934
56935      if (timeAhead < minAppendedDuration) {
56936        return false;
56937      }
56938    }
56939    const nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
56940    // to seek over the gap
56941
56942    if (nextRange.length === 0) {
56943      return false;
56944    }
56945    seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
56946    this.logger_(
56947      `Buffered region starts (${nextRange.start(0)}) ` +
56948        ` just beyond seek point (${currentTime}). Seeking to ${seekTo}.`
56949    );
56950    this.tech_.setCurrentTime(seekTo);
56951    return true;
56952  }
56953  /**
56954   * Handler for situations when we determine the player is waiting.
56955   *
56956   * @private
56957   */
56958
56959  waiting_() {
56960    if (this.techWaiting_()) {
56961      return;
56962    } // All tech waiting checks failed. Use last resort correction
56963
56964    const currentTime = this.tech_.currentTime();
56965    const buffered = this.tech_.buffered();
56966    const currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
56967    // region with no indication that anything is amiss (seen in Firefox). Seeking to
56968    // currentTime is usually enough to kickstart the player. This checks that the player
56969    // is currently within a buffered region before attempting a corrective seek.
56970    // Chrome does not appear to continue `timeupdate` events after a `waiting` event
56971    // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
56972    // make sure there is ~3 seconds of forward buffer before taking any corrective action
56973    // to avoid triggering an `unknownwaiting` event when the network is slow.
56974
56975    if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
56976      this.resetTimeUpdate_();
56977      this.tech_.setCurrentTime(currentTime);
56978      this.logger_(
56979        `Stopped at ${currentTime} while inside a buffered region ` +
56980          `[${currentRange.start(0)} -> ${currentRange.end(0)}]. Attempting to resume ` +
56981          'playback by seeking to the current time.'
56982      ); // unknown waiting corrections may be useful for monitoring QoS
56983
56984      this.tech_.trigger({
56985        type: 'usage',
56986        name: 'vhs-unknown-waiting',
56987      });
56988      return;
56989    }
56990  }
56991  /**
56992   * Handler for situations when the tech fires a `waiting` event
56993   *
56994   * @return {boolean}
56995   *         True if an action (or none) was needed to correct the waiting. False if no
56996   *         checks passed
56997   * @private
56998   */
56999
57000  techWaiting_() {
57001    const seekable = this.seekable();
57002    const currentTime = this.tech_.currentTime();
57003    if (this.tech_.seeking()) {
57004      // Tech is seeking or already waiting on another action, no action needed
57005      return true;
57006    }
57007    if (this.beforeSeekableWindow_(seekable, currentTime)) {
57008      const livePoint = seekable.end(seekable.length - 1);
57009      this.logger_(
57010        `Fell out of live window at time ${currentTime}. Seeking to ` +
57011          `live point (seekable end) ${livePoint}`
57012      );
57013      this.resetTimeUpdate_();
57014      this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
57015
57016      this.tech_.trigger({
57017        type: 'usage',
57018        name: 'vhs-live-resync',
57019      });
57020      return true;
57021    }
57022    const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
57023    const buffered = this.tech_.buffered();
57024    const videoUnderflow = this.videoUnderflow_({
57025      audioBuffered: sourceUpdater.audioBuffered(),
57026      videoBuffered: sourceUpdater.videoBuffered(),
57027      currentTime,
57028    });
57029    if (videoUnderflow) {
57030      // Even though the video underflowed and was stuck in a gap, the audio overplayed
57031      // the gap, leading currentTime into a buffered range. Seeking to currentTime
57032      // allows the video to catch up to the audio position without losing any audio
57033      // (only suffering ~3 seconds of frozen video and a pause in audio playback).
57034      this.resetTimeUpdate_();
57035      this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
57036
57037      this.tech_.trigger({
57038        type: 'usage',
57039        name: 'vhs-video-underflow',
57040      });
57041      return true;
57042    }
57043    const nextRange = findNextRange(buffered, currentTime); // check for gap
57044
57045    if (nextRange.length > 0) {
57046      this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
57047      this.resetTimeUpdate_();
57048      this.skipTheGap_(currentTime);
57049      return true;
57050    } // All checks failed. Returning false to indicate failure to correct waiting
57051
57052    return false;
57053  }
57054  afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow = false) {
57055    if (!seekable.length) {
57056      // we can't make a solid case if there's no seekable, default to false
57057      return false;
57058    }
57059    let allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
57060    const isLive = !playlist.endList;
57061    const isLLHLS = typeof playlist.partTargetDuration === 'number';
57062    if (isLive && (isLLHLS || allowSeeksWithinUnsafeLiveWindow)) {
57063      allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
57064    }
57065    if (currentTime > allowedEnd) {
57066      return true;
57067    }
57068    return false;
57069  }
57070  beforeSeekableWindow_(seekable, currentTime) {
57071    if (
57072      seekable.length &&
57073      // can't fall before 0 and 0 seekable start identifies VOD stream
57074      seekable.start(0) > 0 &&
57075      currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta
57076    ) {
57077      return true;
57078    }
57079    return false;
57080  }
57081  videoUnderflow_({ videoBuffered, audioBuffered, currentTime }) {
57082    // audio only content will not have video underflow :)
57083    if (!videoBuffered) {
57084      return;
57085    }
57086    let gap; // find a gap in demuxed content.
57087
57088    if (videoBuffered.length && audioBuffered.length) {
57089      // in Chrome audio will continue to play for ~3s when we run out of video
57090      // so we have to check that the video buffer did have some buffer in the
57091      // past.
57092      const lastVideoRange = findRange(videoBuffered, currentTime - 3);
57093      const videoRange = findRange(videoBuffered, currentTime);
57094      const audioRange = findRange(audioBuffered, currentTime);
57095      if (audioRange.length && !videoRange.length && lastVideoRange.length) {
57096        gap = {
57097          start: lastVideoRange.end(0),
57098          end: audioRange.end(0),
57099        };
57100      } // find a gap in muxed content.
57101    } else {
57102      const nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
57103      // stuck in a gap due to video underflow.
57104
57105      if (!nextRange.length) {
57106        gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
57107      }
57108    }
57109    if (gap) {
57110      this.logger_(
57111        `Encountered a gap in video from ${gap.start} to ${gap.end}. ` +
57112          `Seeking to current time ${currentTime}`
57113      );
57114      return true;
57115    }
57116    return false;
57117  }
57118  /**
57119   * Timer callback. If playback still has not proceeded, then we seek
57120   * to the start of the next buffered region.
57121   *
57122   * @private
57123   */
57124
57125  skipTheGap_(scheduledCurrentTime) {
57126    const buffered = this.tech_.buffered();
57127    const currentTime = this.tech_.currentTime();
57128    const nextRange = findNextRange(buffered, currentTime);
57129    this.resetTimeUpdate_();
57130    if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
57131      return;
57132    }
57133    this.logger_(
57134      'skipTheGap_:',
57135      'currentTime:',
57136      currentTime,
57137      'scheduled currentTime:',
57138      scheduledCurrentTime,
57139      'nextRange start:',
57140      nextRange.start(0)
57141    ); // only seek if we still have not played
57142
57143    this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
57144    const metadata = {
57145      gapInfo: {
57146        from: currentTime,
57147        to: nextRange.start(0),
57148      },
57149    };
57150    this.playlistController_.trigger({
57151      type: 'gapjumped',
57152      metadata,
57153    });
57154    this.tech_.trigger({
57155      type: 'usage',
57156      name: 'vhs-gap-skip',
57157    });
57158  }
57159  gapFromVideoUnderflow_(buffered, currentTime) {
57160    // At least in Chrome, if there is a gap in the video buffer, the audio will continue
57161    // playing for ~3 seconds after the video gap starts. This is done to account for
57162    // video buffer underflow/underrun (note that this is not done when there is audio
57163    // buffer underflow/underrun -- in that case the video will stop as soon as it
57164    // encounters the gap, as audio stalls are more noticeable/jarring to a user than
57165    // video stalls). The player's time will reflect the playthrough of audio, so the
57166    // time will appear as if we are in a buffered region, even if we are stuck in a
57167    // "gap."
57168    //
57169    // Example:
57170    // video buffer:   0 => 10.1, 10.2 => 20
57171    // audio buffer:   0 => 20
57172    // overall buffer: 0 => 10.1, 10.2 => 20
57173    // current time: 13
57174    //
57175    // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
57176    // however, the audio continued playing until it reached ~3 seconds past the gap
57177    // (13 seconds), at which point it stops as well. Since current time is past the
57178    // gap, findNextRange will return no ranges.
57179    //
57180    // To check for this issue, we see if there is a gap that starts somewhere within
57181    // a 3 second range (3 seconds +/- 1 second) back from our current time.
57182    const gaps = findGaps(buffered);
57183    for (let i = 0; i < gaps.length; i++) {
57184      const start = gaps.start(i);
57185      const end = gaps.end(i); // gap is starts no more than 4 seconds back
57186
57187      if (currentTime - start < 4 && currentTime - start > 2) {
57188        return {
57189          start,
57190          end,
57191        };
57192      }
57193    }
57194    return null;
57195  }
57196}
57197const defaultOptions = {
57198  errorInterval: 30,
57199  getSource(next) {
57200    const tech = this.tech({
57201      IWillNotUseThisInPlugins: true,
57202    });
57203    const sourceObj = tech.currentSource_ || this.currentSource();
57204    return next(sourceObj);
57205  },
57206};
57207/**
57208 * Main entry point for the plugin
57209 *
57210 * @param {Player} player a reference to a videojs Player instance
57211 * @param {Object} [options] an object with plugin options
57212 * @private
57213 */
57214
57215const initPlugin = function (player, options) {
57216  let lastCalled = 0;
57217  let seekTo = 0;
57218  const localOptions = merge(defaultOptions, options);
57219  player.ready(() => {
57220    player.trigger({
57221      type: 'usage',
57222      name: 'vhs-error-reload-initialized',
57223    });
57224  });
57225  /**
57226   * Player modifications to perform that must wait until `loadedmetadata`
57227   * has been triggered
57228   *
57229   * @private
57230   */
57231
57232  const loadedMetadataHandler = function () {
57233    if (seekTo) {
57234      player.currentTime(seekTo);
57235    }
57236  };
57237  /**
57238   * Set the source on the player element, play, and seek if necessary
57239   *
57240   * @param {Object} sourceObj An object specifying the source url and mime-type to play
57241   * @private
57242   */
57243
57244  const setSource = function (sourceObj) {
57245    if (sourceObj === null || sourceObj === undefined) {
57246      return;
57247    }
57248    seekTo = (player.duration() !== Infinity && player.currentTime()) || 0;
57249    player.one('loadedmetadata', loadedMetadataHandler);
57250    player.src(sourceObj);
57251    player.trigger({
57252      type: 'usage',
57253      name: 'vhs-error-reload',
57254    });
57255    player.play();
57256  };
57257  /**
57258   * Attempt to get a source from either the built-in getSource function
57259   * or a custom function provided via the options
57260   *
57261   * @private
57262   */
57263
57264  const errorHandler = function () {
vendor: 4,135 bytes, lines 57265-57395
57265    // Do not attempt to reload the source if a source-reload occurred before
57266    // 'errorInterval' time has elapsed since the last source-reload
57267    if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
57268      player.trigger({
57269        type: 'usage',
57270        name: 'vhs-error-reload-canceled',
57271      });
57272      return;
57273    }
57274    if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
57275      videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
57276      return;
57277    }
57278    lastCalled = Date.now();
57279    return localOptions.getSource.call(player, setSource);
57280  };
57281  /**
57282   * Unbind any event handlers that were bound by the plugin
57283   *
57284   * @private
57285   */
57286
57287  const cleanupEvents = function () {
57288    player.off('loadedmetadata', loadedMetadataHandler);
57289    player.off('error', errorHandler);
57290    player.off('dispose', cleanupEvents);
57291  };
57292  /**
57293   * Cleanup before re-initializing the plugin
57294   *
57295   * @param {Object} [newOptions] an object with plugin options
57296   * @private
57297   */
57298
57299  const reinitPlugin = function (newOptions) {
57300    cleanupEvents();
57301    initPlugin(player, newOptions);
57302  };
57303  player.on('error', errorHandler);
57304  player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
57305  // initializing the plugin
57306
57307  player.reloadSourceOnError = reinitPlugin;
57308};
57309/**
57310 * Reload the source when an error is detected as long as there
57311 * wasn't an error previously within the last 30 seconds
57312 *
57313 * @param {Object} [options] an object with plugin options
57314 */
57315
57316const reloadSourceOnError = function (options) {
57317  initPlugin(this, options);
57318};
57319var version$4 = '3.16.0';
57320var version$3 = '7.1.0';
57321var version$2 = '1.3.1';
57322var version$1 = '7.2.0';
57323var version = '4.0.2';
57324const Vhs = {
57325  PlaylistLoader,
57326  Playlist,
57327  utils,
57328  STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
57329  INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
57330  lastBandwidthSelector,
57331  movingAverageBandwidthSelector,
57332  comparePlaylistBandwidth,
57333  comparePlaylistResolution,
57334  xhr: xhrFactory(),
57335}; // Define getter/setters for config properties
57336
57337Object.keys(Config).forEach((prop) => {
57338  Object.defineProperty(Vhs, prop, {
57339    get() {
57340      videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
57341      return Config[prop];
57342    },
57343    set(value) {
57344      videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
57345      if (typeof value !== 'number' || value < 0) {
57346        videojs.log.warn(`value of Vhs.${prop} must be greater than or equal to 0`);
57347        return;
57348      }
57349      Config[prop] = value;
57350    },
57351  });
57352});
57353const LOCAL_STORAGE_KEY = 'videojs-vhs';
57354/**
57355 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
57356 *
57357 * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
57358 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
57359 * @function handleVhsMediaChange
57360 */
57361
57362const handleVhsMediaChange = function (qualityLevels, playlistLoader) {
57363  const newPlaylist = playlistLoader.media();
57364  let selectedIndex = -1;
57365  for (let i = 0; i < qualityLevels.length; i++) {
57366    if (qualityLevels[i].id === newPlaylist.id) {
57367      selectedIndex = i;
57368      break;
57369    }
57370  }
57371  qualityLevels.selectedIndex_ = selectedIndex;
57372  qualityLevels.trigger({
57373    selectedIndex,
57374    type: 'change',
57375  });
57376};
57377/**
57378 * Adds quality levels to list once playlist metadata is available
57379 *
57380 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
57381 * @param {Object} vhs Vhs object to listen to for media events.
57382 * @function handleVhsLoadedMetadata
57383 */
57384
57385const handleVhsLoadedMetadata = function (qualityLevels, vhs) {
57386  vhs.representations().forEach((rep) => {
57387    qualityLevels.addQualityLevel(rep);
57388  });
57389  handleVhsMediaChange(qualityLevels, vhs.playlists);
57390}; // VHS is a source handler, not a tech. Make sure attempts to use it
57391// as one do not cause exceptions.
57392
57393Vhs.canPlaySource = function () {
57394  return videojs.log.warn(
57395    'VHS is no longer a tech. Please remove it from ' + "your player'
vendor: 22,936 bytes, lines 57395-58048
57395s techOrder."
57396  );
57397};
57398const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
57399  if (!keySystemOptions) {
57400    return keySystemOptions;
57401  }
57402  let codecs = {};
57403  if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
57404    codecs = unwrapCodecList(codecs_js.parseCodecs(mainPlaylist.attributes.CODECS));
57405  }
57406  if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
57407    codecs.audio = audioPlaylist.attributes.CODECS;
57408  }
57409  const videoContentType = codecs_js.getMimeForCodec(codecs.video);
57410  const audioContentType = codecs_js.getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
57411
57412  const keySystemContentTypes = {};
57413  for (const keySystem in keySystemOptions) {
57414    keySystemContentTypes[keySystem] = {};
57415    if (audioContentType) {
57416      keySystemContentTypes[keySystem].audioContentType = audioContentType;
57417    }
57418    if (videoContentType) {
57419      keySystemContentTypes[keySystem].videoContentType = videoContentType;
57420    } // Default to using the video playlist's PSSH even though they may be different, as
57421    // videojs-contrib-eme will only accept one in the options.
57422    //
57423    // This shouldn't be an issue for most cases as early intialization will handle all
57424    // unique PSSH values, and if they aren't, then encrypted events should have the
57425    // specific information needed for the unique license.
57426
57427    if (
57428      mainPlaylist.contentProtection &&
57429      mainPlaylist.contentProtection[keySystem] &&
57430      mainPlaylist.contentProtection[keySystem].pssh
57431    ) {
57432      keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
57433    } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
57434    // so we need to prevent overwriting the URL entirely
57435
57436    if (typeof keySystemOptions[keySystem] === 'string') {
57437      keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
57438    }
57439  }
57440  return merge(keySystemOptions, keySystemContentTypes);
57441};
57442/**
57443 * @typedef {Object} KeySystems
57444 *
57445 * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
57446 * Note: not all options are listed here.
57447 *
57448 * @property {Uint8Array} [pssh]
57449 *           Protection System Specific Header
57450 */
57451
57452/**
57453 * Goes through all the playlists and collects an array of KeySystems options objects
57454 * containing each playlist's keySystems and their pssh values, if available.
57455 *
57456 * @param {Object[]} playlists
57457 *        The playlists to look through
57458 * @param {string[]} keySystems
57459 *        The keySystems to collect pssh values for
57460 *
57461 * @return {KeySystems[]}
57462 *         An array of KeySystems objects containing available key systems and their
57463 *         pssh values
57464 */
57465
57466const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
57467  return playlists.reduce((keySystemsArr, playlist) => {
57468    if (!playlist.contentProtection) {
57469      return keySystemsArr;
57470    }
57471    const keySystemsOptions = keySystems.reduce((keySystemsObj, keySystem) => {
57472      const keySystemOptions = playlist.contentProtection[keySystem];
57473      if (keySystemOptions && keySystemOptions.pssh) {
57474        keySystemsObj[keySystem] = {
57475          pssh: keySystemOptions.pssh,
57476        };
57477      }
57478      return keySystemsObj;
57479    }, {});
57480    if (Object.keys(keySystemsOptions).length) {
57481      keySystemsArr.push(keySystemsOptions);
57482    }
57483    return keySystemsArr;
57484  }, []);
57485};
57486/**
57487 * Returns a promise that waits for the
57488 * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
57489 *
57490 * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
57491 * browsers.
57492 *
57493 * As per the above ticket, this is particularly important for Chrome, where, if
57494 * unencrypted content is appended before encrypted content and the key session has not
57495 * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
57496 * during playback.
57497 *
57498 * @param {Object} player
57499 *        The player instance
57500 * @param {Object[]} sourceKeySystems
57501 *        The key systems options from the player source
57502 * @param {Object} [audioMedia]
57503 *        The active audio media playlist (optional)
57504 * @param {Object[]} mainPlaylists
57505 *        The playlists found on the main playlist object
57506 *
57507 * @return {Object}
57508 *         Promise that resolves when the key session has been created
57509 */
57510
57511const waitForKeySessionCreation = ({ player, sourceKeySystems, audioMedia, mainPlaylists }) => {
57512  if (!player.eme.initializeMediaKeys) {
57513    return Promise.resolve();
57514  } // TODO should all audio PSSH values be initialized for DRM?
57515  //
57516  // All unique video rendition pssh values are initialized for DRM, but here only
57517  // the initial audio playlist license is initialized. In theory, an encrypted
57518  // event should be fired if the user switches to an alternative audio playlist
57519  // where a license is required, but this case hasn't yet been tested. In addition, there
57520  // may be many alternate audio playlists unlikely to be used (e.g., multiple different
57521  // languages).
57522
57523  const playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
57524  const keySystemsOptionsArr = getAllPsshKeySystemsOptions(
57525    playlists,
57526    Object.keys(sourceKeySystems)
57527  );
57528  const initializationFinishedPromises = [];
57529  const keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
57530  // only place where it should not be deduped is for ms-prefixed APIs, but
57531  // the existence of modern EME APIs in addition to
57532  // ms-prefixed APIs on Edge should prevent this from being a concern.
57533  // initializeMediaKeys also won't use the webkit-prefixed APIs.
57534
57535  keySystemsOptionsArr.forEach((keySystemsOptions) => {
57536    keySessionCreatedPromises.push(
57537      new Promise((resolve, reject) => {
57538        player.tech_.one('keysessioncreated', resolve);
57539      })
57540    );
57541    initializationFinishedPromises.push(
57542      new Promise((resolve, reject) => {
57543        player.eme.initializeMediaKeys(
57544          {
57545            keySystems: keySystemsOptions,
57546          },
57547          (err) => {
57548            if (err) {
57549              reject(err);
57550              return;
57551            }
57552            resolve();
57553          }
57554        );
57555      })
57556    );
57557  }); // The reasons Promise.race is chosen over Promise.any:
57558  //
57559  // * Promise.any is only available in Safari 14+.
57560  // * None of these promises are expected to reject. If they do reject, it might be
57561  //   better here for the race to surface the rejection, rather than mask it by using
57562  //   Promise.any.
57563
57564  return Promise.race([
57565    // If a session was previously created, these will all finish resolving without
57566    // creating a new session, otherwise it will take until the end of all license
57567    // requests, which is why the key session check is used (to make setup much faster).
57568    Promise.all(initializationFinishedPromises),
57569    // Once a single session is created, the browser knows DRM will be used.
57570    Promise.race(keySessionCreatedPromises),
57571  ]);
57572};
57573/**
57574 * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
57575 * there are keySystems on the source, sets up source options to prepare the source for
57576 * eme.
57577 *
57578 * @param {Object} player
57579 *        The player instance
57580 * @param {Object[]} sourceKeySystems
57581 *        The key systems options from the player source
57582 * @param {Object} media
57583 *        The active media playlist
57584 * @param {Object} [audioMedia]
57585 *        The active audio media playlist (optional)
57586 *
57587 * @return {boolean}
57588 *         Whether or not options were configured and EME is available
57589 */
57590
57591const setupEmeOptions = ({ player, sourceKeySystems, media, audioMedia }) => {
57592  const sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
57593  if (!sourceOptions) {
57594    return false;
57595  }
57596  player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
57597  // do nothing.
57598
57599  if (sourceOptions && !player.eme) {
57600    videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
57601    return false;
57602  }
57603  return true;
57604};
57605const getVhsLocalStorage = () => {
57606  if (!window__default['default'].localStorage) {
57607    return null;
57608  }
57609  const storedObject = window__default['default'].localStorage.getItem(LOCAL_STORAGE_KEY);
57610  if (!storedObject) {
57611    return null;
57612  }
57613  try {
57614    return JSON.parse(storedObject);
57615  } catch (e) {
57616    // someone may have tampered with the value
57617    return null;
57618  }
57619};
57620const updateVhsLocalStorage = (options) => {
57621  if (!window__default['default'].localStorage) {
57622    return false;
57623  }
57624  let objectToStore = getVhsLocalStorage();
57625  objectToStore = objectToStore ? merge(objectToStore, options) : options;
57626  try {
57627    window__default['default'].localStorage.setItem(
57628      LOCAL_STORAGE_KEY,
57629      JSON.stringify(objectToStore)
57630    );
57631  } catch (e) {
57632    // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
57633    // storage is set to 0).
57634    // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
57635    // No need to perform any operation.
57636    return false;
57637  }
57638  return objectToStore;
57639};
57640/**
57641 * Parses VHS-supported media types from data URIs. See
57642 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
57643 * for information on data URIs.
57644 *
57645 * @param {string} dataUri
57646 *        The data URI
57647 *
57648 * @return {string|Object}
57649 *         The parsed object/string, or the original string if no supported media type
57650 *         was found
57651 */
57652
57653const expandDataUri = (dataUri) => {
57654  if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
57655    return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
57656  } // no known case for this data URI, return the string as-is
57657
57658  return dataUri;
57659};
57660/**
57661 * Adds a request hook to an xhr object
57662 *
57663 * @param {Object} xhr object to add the onRequest hook to
57664 * @param {function} callback hook function for an xhr request
57665 */
57666
57667const addOnRequestHook = (xhr, callback) => {
57668  if (!xhr._requestCallbackSet) {
57669    xhr._requestCallbackSet = new Set();
57670  }
57671  xhr._requestCallbackSet.add(callback);
57672};
57673/**
57674 * Adds a response hook to an xhr object
57675 *
57676 * @param {Object} xhr object to add the onResponse hook to
57677 * @param {function} callback hook function for an xhr response
57678 */
57679
57680const addOnResponseHook = (xhr, callback) => {
57681  if (!xhr._responseCallbackSet) {
57682    xhr._responseCallbackSet = new Set();
57683  }
57684  xhr._responseCallbackSet.add(callback);
57685};
57686/**
57687 * Removes a request hook on an xhr object, deletes the onRequest set if empty.
57688 *
57689 * @param {Object} xhr object to remove the onRequest hook from
57690 * @param {function} callback hook function to remove
57691 */
57692
57693const removeOnRequestHook = (xhr, callback) => {
57694  if (!xhr._requestCallbackSet) {
57695    return;
57696  }
57697  xhr._requestCallbackSet.delete(callback);
57698  if (!xhr._requestCallbackSet.size) {
57699    delete xhr._requestCallbackSet;
57700  }
57701};
57702/**
57703 * Removes a response hook on an xhr object, deletes the onResponse set if empty.
57704 *
57705 * @param {Object} xhr object to remove the onResponse hook from
57706 * @param {function} callback hook function to remove
57707 */
57708
57709const removeOnResponseHook = (xhr, callback) => {
57710  if (!xhr._responseCallbackSet) {
57711    return;
57712  }
57713  xhr._responseCallbackSet.delete(callback);
57714  if (!xhr._responseCallbackSet.size) {
57715    delete xhr._responseCallbackSet;
57716  }
57717};
57718/**
57719 * Whether the browser has built-in HLS support.
57720 */
57721
57722Vhs.supportsNativeHls = (function () {
57723  if (!document__default['default'] || !document__default['default'].createElement) {
57724    return false;
57725  }
57726  const video = document__default['default'].createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
57727
57728  if (!videojs.getTech('Html5').isSupported()) {
57729    return false;
57730  } // HLS manifests can go by many mime-types
57731
57732  const canPlay = [
57733    // Apple santioned
57734    'application/vnd.apple.mpegurl',
57735    // Apple sanctioned for backwards compatibility
57736    'audio/mpegurl',
57737    // Very common
57738    'audio/x-mpegurl',
57739    // Very common
57740    'application/x-mpegurl',
57741    // Included for completeness
57742    'video/x-mpegurl',
57743    'video/mpegurl',
57744    'application/mpegurl',
57745  ];
57746  return canPlay.some(function (canItPlay) {
57747    return /maybe|probably/i.test(video.canPlayType(canItPlay));
57748  });
57749})();
57750Vhs.supportsNativeDash = (function () {
57751  if (
57752    !document__default['default'] ||
57753    !document__default['default'].createElement ||
57754    !videojs.getTech('Html5').isSupported()
57755  ) {
57756    return false;
57757  }
57758  return /maybe|probably/i.test(
57759    document__default['default'].createElement('video').canPlayType('application/dash+xml')
57760  );
57761})();
57762Vhs.supportsTypeNatively = (type) => {
57763  if (type === 'hls') {
57764    return Vhs.supportsNativeHls;
57765  }
57766  if (type === 'dash') {
57767    return Vhs.supportsNativeDash;
57768  }
57769  return false;
57770};
57771/**
57772 * VHS is a source handler, not a tech. Make sure attempts to use it
57773 * as one do not cause exceptions.
57774 */
57775
57776Vhs.isSupported = function () {
57777  return videojs.log.warn(
57778    'VHS is no longer a tech. Please remove it from ' + "your player's techOrder."
57779  );
57780};
57781/**
57782 * A global function for setting an onRequest hook
57783 *
57784 * @param {function} callback for request modifiction
57785 */
57786
57787Vhs.xhr.onRequest = function (callback) {
57788  addOnRequestHook(Vhs.xhr, callback);
57789};
57790/**
57791 * A global function for setting an onResponse hook
57792 *
57793 * @param {callback} callback for response data retrieval
57794 */
57795
57796Vhs.xhr.onResponse = function (callback) {
57797  addOnResponseHook(Vhs.xhr, callback);
57798};
57799/**
57800 * Deletes a global onRequest callback if it exists
57801 *
57802 * @param {function} callback to delete from the global set
57803 */
57804
57805Vhs.xhr.offRequest = function (callback) {
57806  removeOnRequestHook(Vhs.xhr, callback);
57807};
57808/**
57809 * Deletes a global onResponse callback if it exists
57810 *
57811 * @param {function} callback to delete from the global set
57812 */
57813
57814Vhs.xhr.offResponse = function (callback) {
57815  removeOnResponseHook(Vhs.xhr, callback);
57816};
57817const Component = videojs.getComponent('Component');
57818/**
57819 * The Vhs Handler object, where we orchestrate all of the parts
57820 * of VHS to interact with video.js
57821 *
57822 * @class VhsHandler
57823 * @extends videojs.Component
57824 * @param {Object} source the soruce object
57825 * @param {Tech} tech the parent tech object
57826 * @param {Object} options optional and required options
57827 */
57828
57829class VhsHandler extends Component {
57830  constructor(source, tech, options) {
57831    super(tech, options.vhs); // if a tech level `initialBandwidth` option was passed
57832    // use that over the VHS level `bandwidth` option
57833
57834    if (typeof options.initialBandwidth === 'number') {
57835      this.options_.bandwidth = options.initialBandwidth;
57836    }
57837    this.logger_ = logger('VhsHandler'); // we need access to the player in some cases,
57838    // so, get it from Video.js via the `playerId`
57839
57840    if (tech.options_ && tech.options_.playerId) {
57841      const _player = videojs.getPlayer(tech.options_.playerId);
57842      this.player_ = _player;
57843    }
57844    this.tech_ = tech;
57845    this.source_ = source;
57846    this.stats = {};
57847    this.ignoreNextSeekingEvent_ = false;
57848    this.setOptions_();
57849    if (
57850      this.options_.overrideNative &&
57851      tech.overrideNativeAudioTracks &&
57852      tech.overrideNativeVideoTracks
57853    ) {
57854      tech.overrideNativeAudioTracks(true);
57855      tech.overrideNativeVideoTracks(true);
57856    } else if (
57857      this.options_.overrideNative &&
57858      (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)
57859    ) {
57860      // overriding native VHS only works if audio tracks have been emulated
57861      // error early if we're misconfigured
57862      throw new Error(
57863        'Overriding native VHS requires emulated tracks. ' + 'See https://git.io/vMpjB'
57864      );
57865    } // listen for fullscreenchange events for this player so that we
57866    // can adjust our quality selection quickly
57867
57868    this.on(
57869      document__default['default'],
57870      ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'],
57871      (event) => {
57872        const fullscreenElement =
57873          document__default['default'].fullscreenElement ||
57874          document__default['default'].webkitFullscreenElement ||
57875          document__default['default'].mozFullScreenElement ||
57876          document__default['default'].msFullscreenElement;
57877        if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
57878          this.playlistController_.fastQualityChange_();
57879        } else {
57880          // When leaving fullscreen, since the in page pixel dimensions should be smaller
57881          // than full screen, see if there should be a rendition switch down to preserve
57882          // bandwidth.
57883          this.playlistController_.checkABR_();
57884        }
57885      }
57886    );
57887    this.on(this.tech_, 'seeking', function () {
57888      if (this.ignoreNextSeekingEvent_) {
57889        this.ignoreNextSeekingEvent_ = false;
57890        return;
57891      }
57892      this.setCurrentTime(this.tech_.currentTime());
57893    });
57894    this.on(this.tech_, 'error', function () {
57895      // verify that the error was real and we are loaded
57896      // enough to have pc loaded.
57897      if (this.tech_.error() && this.playlistController_) {
57898        this.playlistController_.pauseLoading();
57899      }
57900    });
57901    this.on(this.tech_, 'play', this.play);
57902  }
57903  /**
57904   * Set VHS options based on options from configuration, as well as partial
57905   * options to be passed at a later time.
57906   *
57907   * @param {Object} options A partial chunk of config options
57908   */
57909
57910  setOptions_(options = {}) {
57911    this.options_ = merge(this.options_, options); // defaults
57912
57913    this.options_.withCredentials = this.options_.withCredentials || false;
57914    this.options_.limitRenditionByPlayerDimensions =
57915      this.options_.limitRenditionByPlayerDimensions === false ? false : true;
57916    this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
57917    this.options_.useBandwidthFromLocalStorage =
57918      typeof this.source_.useBandwidthFromLocalStorage !== 'undefined'
57919        ? this.source_.useBandwidthFromLocalStorage
57920        : this.options_.useBandwidthFromLocalStorage || false;
57921    this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
57922    this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
57923    this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
57924    this.options_.customTagParsers = this.options_.customTagParsers || [];
57925    this.options_.customTagMappers = this.options_.customTagMappers || [];
57926    this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
57927    this.options_.llhls = this.options_.llhls === false ? false : true;
57928    this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
57929    if (typeof this.options_.playlistExclusionDuration !== 'number') {
57930      this.options_.playlistExclusionDuration = 60;
57931    }
57932    if (typeof this.options_.bandwidth !== 'number') {
57933      if (this.options_.useBandwidthFromLocalStorage) {
57934        const storedObject = getVhsLocalStorage();
57935        if (storedObject && storedObject.bandwidth) {
57936          this.options_.bandwidth = storedObject.bandwidth;
57937          this.tech_.trigger({
57938            type: 'usage',
57939            name: 'vhs-bandwidth-from-local-storage',
57940          });
57941        }
57942        if (storedObject && storedObject.throughput) {
57943          this.options_.throughput = storedObject.throughput;
57944          this.tech_.trigger({
57945            type: 'usage',
57946            name: 'vhs-throughput-from-local-storage',
57947          });
57948        }
57949      }
57950    } // if bandwidth was not set by options or pulled from local storage, start playlist
57951    // selection at a reasonable bandwidth
57952
57953    if (typeof this.options_.bandwidth !== 'number') {
57954      this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
57955    } // If the bandwidth number is unchanged from the initial setting
57956    // then this takes precedence over the enableLowInitialPlaylist option
57957
57958    this.options_.enableLowInitialPlaylist =
57959      this.options_.enableLowInitialPlaylist &&
57960      this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
57961
57962    [
57963      'withCredentials',
57964      'useDevicePixelRatio',
57965      'customPixelRatio',
57966      'limitRenditionByPlayerDimensions',
57967      'bandwidth',
57968      'customTagParsers',
57969      'customTagMappers',
57970      'cacheEncryptionKeys',
57971      'playlistSelector',
57972      'initialPlaylistSelector',
57973      'bufferBasedABR',
57974      'liveRangeSafeTimeDelta',
57975      'llhls',
57976      'useForcedSubtitles',
57977      'useNetworkInformationApi',
57978      'useDtsForTimestampOffset',
57979      'exactManifestTimings',
57980      'leastPixelDiffSelector',
57981    ].forEach((option) => {
57982      if (typeof this.source_[option] !== 'undefined') {
57983        this.options_[option] = this.source_[option];
57984      }
57985    });
57986    this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
57987    this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
57988    const customPixelRatio = this.options_.customPixelRatio; // Ensure the custom pixel ratio is a number greater than or equal to 0
57989
57990    if (typeof customPixelRatio === 'number' && customPixelRatio >= 0) {
57991      this.customPixelRatio = customPixelRatio;
57992    }
57993  } // alias for public method to set options
57994
57995  setOptions(options = {}) {
57996    this.setOptions_(options);
57997  }
57998  /**
57999   * called when player.src gets called, handle a new source
58000   *
58001   * @param {Object} src the source object to handle
58002   */
58003
58004  src(src, type) {
58005    // do nothing if the src is falsey
58006    if (!src) {
58007      return;
58008    }
58009    this.setOptions_(); // add main playlist controller options
58010
58011    this.options_.src = expandDataUri(this.source_.src);
58012    this.options_.tech = this.tech_;
58013    this.options_.externVhs = Vhs;
58014    this.options_.sourceType = mediaTypes_js.simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
58015
58016    this.options_.seekTo = (time) => {
58017      this.tech_.setCurrentTime(time);
58018    }; // pass player to allow for player level eventing on construction.
58019
58020    this.options_.player_ = this.player_;
58021    this.playlistController_ = new PlaylistController(this.options_);
58022    const playbackWatcherOptions = merge(
58023      {
58024        liveRangeSafeTimeDelta: SAFE_TIME_DELTA,
58025      },
58026      this.options_,
58027      {
58028        seekable: () => this.seekable(),
58029        media: () => this.playlistController_.media(),
58030        playlistController: this.playlistController_,
58031      }
58032    );
58033    this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
58034    this.attachStreamingEventListeners_();
58035    this.playlistController_.on('error', () => {
58036      const player = videojs.players[this.tech_.options_.playerId];
58037      let error = this.playlistController_.error;
58038      if (typeof error === 'object' && !error.code) {
58039        error.code = 3;
58040      } else if (typeof error === 'string') {
58041        error = {
58042          message: error,
58043          code: 3,
58044        };
58045      }
58046      player.error(error);
58047    });
58048    const defaultSelector = this.options_.bufferBasedABR
58049      ? Vhs.movingAverageBandwidthSelector(0.55)
58050      : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
58051    // compatibility with < v2
58052
58053    this.playlistController_.selectPlaylist = this.selectPlaylist
58054      ? this.selectPlaylist.bind(this)
58055      : defaultSelector.bind(this);
58056    this.playlistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
58057
58058    this.playlists = this.playlistController_.mainPlaylistLoader_;
58059    this.mediaSource = this.playlistController_.mediaSource; // Proxy assignment of some properties to the main playlist
58060    // controller. Using a custom property for backwards compatibility
58061    // with < v2
58062
58063    Object.defineProperties(this, {
58064      selectPlaylist: {
58065        get() {
58066          return this.playlistController_.selectPlaylist;
58067        },
58068        set(selectPlaylist) {
58069          this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
58070        },
58071      },
58072      throughput: {
58073        get() {
58074          return this.playlistController_.mainSegmentLoader_.throughput.rate;
58075        },
58076        set(throughput) {
58077          this.playlistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
58078          // for the cumulative average
58079
58080          this.playlistController_.mainSegmentLoader_.throughput.count = 1;
58081        },
58082      },
58083      bandwidth: {
58084        get() {
58085          let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
58086          const networkInformation =
58087            window__default['default'].navigator.connection ||
58088            window__default['default'].navigator.mozConnection ||
58089            window__default['default'].navigator.webkitConnection;
58090          const tenMbpsAsBitsPerSecond = 10e6;
58091          if (this.options_.useNetworkInformationApi && networkInformation) {
58092            // downlink returns Mbps
58093            // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
58094            const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
58095            // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
58096            // high quality streams are not filtered out.
58097
58098            if (
58099              networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond &&
58100              playerBandwidthEst >= tenMbpsAsBitsPerSecond
58101            ) {
58102              playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
58103            } else {
58104              playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
58105            }
58106          }
58107          return playerBandwidthEst;
58108        },
58109        set(bandwidth) {
58110          this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
58111          // `count` is set to zero that current value of `rate` isn't included
58112          // in the cumulative average
58113
58114          this.playlistController_.mainSegmentLoader_.throughput = {
58115            rate: 0,
58116            count: 0,
58117          };
58118        },
58119      },
58120      /**
58121       * `systemBandwidth` is a combination of two serial processes bit-rates. The first
58122       * is the network bitrate provided by `bandwidth` and the second is the bitrate of
58123       * the entire process after that - decryption, transmuxing, and appending - provided
58124       * by `throughput`.
58125       *
58126       * Since the two process are serial, the overall system bandwidth is given by:
58127       *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
58128       */
58129      systemBandwidth: {
58130        get() {
58131          const invBandwidth = 1 / (this.bandwidth || 1);
58132          let invThroughput;
58133          if (this.throughput > 0) {
58134            invThroughput = 1 / this.throughput;
58135          } else {
58136            invThroughput = 0;
58137          }
58138          const systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
58139          return systemBitrate;
58140        },
58141        set() {
58142          videojs.log.error('The "systemBandwidth" property is read-only');
58143        },
58144      },
58145    });
58146    if (this.options_.bandwidth) {
58147      this.bandwidth = this.options_.bandwidth;
58148    }
58149    if (this.options_.throughput) {
58150      this.throughput = this.options_.throughput;
58151    }
58152    Object.defineProperties(this.stats, {
58153      bandwidth: {
58154        get: () => this.bandwidth || 0,
58155        enumerable: true,
58156      },
58157      mediaRequests: {
58158        get: () => this.playlistController_.mediaRequests_() || 0,
58159        enumerable: true,
58160      },
58161      mediaRequestsAborted: {
58162        get: () => this.playlistController_.mediaRequestsAborted_() || 0,
58163        enumerable: true,
58164      },
58165      mediaRequestsTimedout: {
58166        get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
58167        enumerable: true,
58168      },
58169      mediaRequestsErrored: {
58170        get: () => this.playlistController_.mediaRequestsErrored_() || 0,
58171        enumerable: true,
58172      },
58173      mediaTransferDuration: {
58174        get: () => this.playlistController_.mediaTransferDuration_() || 0,
58175        enumerable: true,
58176      },
58177      mediaBytesTransferred: {
58178        get: () => this.playlistController_.mediaBytesTransferred_() || 0,
58179        enumerable: true,
58180      },
58181      mediaSecondsLoaded: {
58182        get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
58183        enumerable: true,
58184      },
58185      mediaAppends: {
58186        get: () => this.playlistController_.mediaAppends_() || 0,
58187        enumerable: true,
58188      },
58189      mainAppendsToLoadedData: {
58190        get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
58191        enumerable: true,
58192      },
58193      audioAppendsToLoadedData: {
58194        get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
58195        enumerable: true,
58196      },
58197      appendsToLoadedData: {
58198        get: () => this.playlistController_.appendsToLoadedData_() || 0,
58199        enumerable: true,
58200      },
58201      timeToLoadedData: {
58202        get: () => this.playlistController_.timeToLoadedData_() || 0,
58203        enumerable: true,
58204      },
58205      buffered: {
58206        get: () => timeRangesToArray(this.tech_.buffered()),
58207        enumerable: true,
58208      },
58209      currentTime: {
58210        get: () => this.tech_.currentTime(),
58211        enumerable: true,
58212      },
58213      currentSource: {
58214        get: () => this.tech_.currentSource_,
58215        enumerable: true,
58216      },
58217      currentTech: {
58218        get: () => this.tech_.name_,
58219        enumerable: true,
58220      },
58221      duration: {
58222        get: () => this.tech_.duration(),
58223        enumerable: true,
58224      },
58225      main: {
58226        get: () => this.playlists.main,
58227        enumerable: true,
58228      },
58229      playerDimensions: {
58230        get: () => this.tech_.currentDimensions(),
58231        enumerable: true,
58232      },
58233      seekable: {
58234        get: () => timeRangesToArray(this.tech_.seekable()),
58235        enumerable: true,
58236      },
58237      timestamp: {
58238        get: () => Date.now(),
58239        enumerable: true,
58240      },
58241      videoPlaybackQuality: {
58242        get: () => this.tech_.getVideoPlaybackQuality(),
58243        enumerable: true,
58244      },
58245    });
58246    this.tech_.one(
58247      'canplay',
58248      this.playlistController_.setupFirstPlay.bind(this.playlistController_)
58249    );
58250    this.tech_.on('bandwidthupdate', () => {
58251      if (this.options_.useBandwidthFromLocalStorage) {
58252        updateVhsLocalStorage({
58253          bandwidth: this.bandwidth,
58254          throughput: Math.round(this.throughput),
58255        });
58256      }
58257    });
58258    this.playlistController_.on('selectedinitialmedia', () => {
vendor: 4,586 bytes, lines 58259-58370
58259      // Add the manual rendition mix-in to VhsHandler
58260      renditionSelectionMixin(this);
58261    });
58262    this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
58263      this.setupEme_();
58264    }); // the bandwidth of the primary segment loader is our best
58265    // estimate of overall bandwidth
58266
58267    this.on(this.playlistController_, 'progress', function () {
58268      this.tech_.trigger('progress');
58269    }); // In the live case, we need to ignore the very first `seeking` event since
58270    // that will be the result of the seek-to-live behavior
58271
58272    this.on(this.playlistController_, 'firstplay', function () {
58273      this.ignoreNextSeekingEvent_ = true;
58274    });
58275    this.setupQualityLevels_(); // do nothing if the tech has been disposed already
58276    // this can occur if someone sets the src in player.ready(), for instance
58277
58278    if (!this.tech_.el()) {
58279      return;
58280    }
58281    this.mediaSourceUrl_ = window__default['default'].URL.createObjectURL(
58282      this.playlistController_.mediaSource
58283    ); // If we are playing HLS with MSE in Safari, add source elements for both the blob and manifest URLs.
58284    // The latter will enable Airplay playback on receiver devices.
58285
58286    if (
58287      (videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS) &&
58288      this.options_.overrideNative &&
58289      this.options_.sourceType === 'hls' &&
58290      typeof this.tech_.addSourceElement === 'function'
58291    ) {
58292      this.tech_.addSourceElement(this.mediaSourceUrl_);
58293      this.tech_.addSourceElement(this.source_.src);
58294    } else {
58295      this.tech_.src(this.mediaSourceUrl_);
58296    }
58297  }
58298  createKeySessions_() {
58299    const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
58300    this.logger_('waiting for EME key session creation');
58301    waitForKeySessionCreation({
58302      player: this.player_,
58303      sourceKeySystems: this.source_.keySystems,
58304      audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
58305      mainPlaylists: this.playlists.main.playlists,
58306    })
58307      .then(() => {
58308        this.logger_('created EME key session');
58309        this.playlistController_.sourceUpdater_.initializedEme();
58310      })
58311      .catch((err) => {
58312        this.logger_('error while creating EME key session', err);
58313        this.player_.error({
58314          message: 'Failed to initialize media keys for EME',
58315          code: 3,
58316        });
58317      });
58318  }
58319  handleWaitingForKey_() {
58320    // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
58321    // the key is in the manifest. While this should've happened on initial source load, it
58322    // may happen again in live streams where the keys change, and the manifest info
58323    // reflects the update.
58324    //
58325    // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
58326    // already requested keys for, we don't have to worry about this generating extraneous
58327    // requests.
58328    this.logger_('waitingforkey fired, attempting to create any new key sessions');
58329    this.createKeySessions_();
58330  }
58331  /**
58332   * If necessary and EME is available, sets up EME options and waits for key session
58333   * creation.
58334   *
58335   * This function also updates the source updater so taht it can be used, as for some
58336   * browsers, EME must be configured before content is appended (if appending unencrypted
58337   * content before encrypted content).
58338   */
58339
58340  setupEme_() {
58341    const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
58342    const didSetupEmeOptions = setupEmeOptions({
58343      player: this.player_,
58344      sourceKeySystems: this.source_.keySystems,
58345      media: this.playlists.media(),
58346      audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
58347    });
58348    this.player_.tech_.on('keystatuschange', (e) => {
58349      this.playlistController_.updatePlaylistByKeyStatus(e.keyId, e.status);
58350    });
58351    this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
58352    this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_);
58353    if (!didSetupEmeOptions) {
58354      // If EME options were not set up, we've done all we could to initialize EME.
58355      this.playlistController_.sourceUpdater_.initializedEme();
58356      return;
58357    }
58358    this.createKeySessions_();
58359  }
58360  /**
58361   * Initializes the quality levels and sets listeners to update them.
58362   *
58363   * @method setupQualityLevels_
58364   * @private
58365   */
58366
58367  setupQualityLevels_() {
58368    const player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
58369    // or qualityLevels_ listeners have already been setup, do nothing.
58370
58371    if (!player || !player.qualityLevels || this.qualityLevels_) {
58372      return;
58373    }
58374    this.qualityLevels_ = player.qualityLevels();
58375    this.playlistController_.on('selectedinitialmedia', () => {
58376      handleVhsLoadedMetadata(this.qualityLevels_, this);
58377    });
58378    this.playlists.on('mediachange', () => {
58379      handleVhsMediaChange(this.qualityLevels_, this.playlists);
58380    });
58381  }
58382  /**
58383   * return the version
58384   */
58385
58386  static version() {
58387    return {
58388      '@videojs/http-streaming': version$4,
58389      'mux.js': version$3,
58390      'mpd-parser': version$2,
58391      'm3u8-parser': version$1,
58392      'aes-decrypter': version,
58393    };
58394  }
58395  /**
58396   * return the version
58397   */
58398
58399  version() {
58400    return this.constructor.version();
58401  }
58402  canChangeType() {
58403    return SourceUpdater.canChangeType();
58404  }
58405  /**
58406   * Begin playing the video.
58407   */
58408
58409  play() {
58410    this.playlistController_.play();
58411  }
58412  /**
58413   * a wrapper around the function in PlaylistController
58414   */
58415
58416  setCurrentTime(currentTime) {
58417    this.playlistController_.setCurrentTime(currentTime);
58418  }
58419  /**
58420   * a wrapper around the function in PlaylistController
58421   */
58422
58423  duration() {
58424    return this.playlistController_.duration();
58425  }
58426  /**
58427   * a wrapper around the function in PlaylistController
58428   */
58429
58430  seekable() {
58431    return this.playlistController_.seekable();
58432  }
58433  /**
58434   * Abort all outstanding work and cleanup.
58435   */
58436
58437  dispose() {
58438    if (this.playbackWatcher_) {
58439      this.playbackWatcher_.dispose();
58440    }
58441    if (this.playlistController_) {
58442      this.playlistController_.dispose();
58443    }
vendor: 5,801 bytes, lines 58444-58618
58444    if (this.qualityLevels_) {
58445      this.qualityLevels_.dispose();
58446    }
58447    if (this.tech_ && this.tech_.vhs) {
58448      delete this.tech_.vhs;
58449    }
58450    if (this.mediaSourceUrl_ && window__default['default'].URL.revokeObjectURL) {
58451      window__default['default'].URL.revokeObjectURL(this.mediaSourceUrl_);
58452      this.mediaSourceUrl_ = null;
58453    }
58454    if (this.tech_) {
58455      this.tech_.off('waitingforkey', this.handleWaitingForKey_);
58456    }
58457    super.dispose();
58458  }
58459  convertToProgramTime(time, callback) {
58460    return getProgramTime({
58461      playlist: this.playlistController_.media(),
58462      time,
58463      callback,
58464    });
58465  } // the player must be playing before calling this
58466
58467  seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
58468    return seekToProgramTime({
58469      programTime,
58470      playlist: this.playlistController_.media(),
58471      retryCount,
58472      pauseAfterSeek,
58473      seekTo: this.options_.seekTo,
58474      tech: this.options_.tech,
58475      callback,
58476    });
58477  }
58478  /**
58479   * Adds the onRequest, onResponse, offRequest and offResponse functions
58480   * to the VhsHandler xhr Object.
58481   */
58482
58483  setupXhrHooks_() {
58484    /**
58485     * A player function for setting an onRequest hook
58486     *
58487     * @param {function} callback for request modifiction
58488     */
58489    this.xhr.onRequest = (callback) => {
58490      addOnRequestHook(this.xhr, callback);
58491    };
58492    /**
58493     * A player function for setting an onResponse hook
58494     *
58495     * @param {callback} callback for response data retrieval
58496     */
58497
58498    this.xhr.onResponse = (callback) => {
58499      addOnResponseHook(this.xhr, callback);
58500    };
58501    /**
58502     * Deletes a player onRequest callback if it exists
58503     *
58504     * @param {function} callback to delete from the player set
58505     */
58506
58507    this.xhr.offRequest = (callback) => {
58508      removeOnRequestHook(this.xhr, callback);
58509    };
58510    /**
58511     * Deletes a player onResponse callback if it exists
58512     *
58513     * @param {function} callback to delete from the player set
58514     */
58515
58516    this.xhr.offResponse = (callback) => {
58517      removeOnResponseHook(this.xhr, callback);
58518    }; // Trigger an event on the player to notify the user that vhs is ready to set xhr hooks.
58519    // This allows hooks to be set before the source is set to vhs when handleSource is called.
58520
58521    this.player_.trigger('xhr-hooks-ready');
58522  }
58523  attachStreamingEventListeners_() {
58524    const playlistControllerEvents = [
58525      'seekablerangeschanged',
58526      'bufferedrangeschanged',
58527      'contentsteeringloadstart',
58528      'contentsteeringloadcomplete',
58529      'contentsteeringparsed',
58530    ];
58531    const playbackWatcher = ['gapjumped', 'playedrangeschanged']; // re-emit streaming events and payloads on the player.
58532
58533    playlistControllerEvents.forEach((eventName) => {
58534      this.playlistController_.on(eventName, (metadata) => {
58535        this.player_.trigger(_extends__default['default']({}, metadata));
58536      });
58537    });
58538    playbackWatcher.forEach((eventName) => {
58539      this.playbackWatcher_.on(eventName, (metadata) => {
58540        this.player_.trigger(_extends__default['default']({}, metadata));
58541      });
58542    });
58543  }
58544}
58545/**
58546 * The Source Handler object, which informs video.js what additional
58547 * MIME types are supported and sets up playback. It is registered
58548 * automatically to the appropriate tech based on the capabilities of
58549 * the browser it is running in. It is not necessary to use or modify
58550 * this object in normal usage.
58551 */
58552
58553const VhsSourceHandler = {
58554  name: 'videojs-http-streaming',
58555  VERSION: version$4,
58556  canHandleSource(srcObj, options = {}) {
58557    const localOptions = merge(videojs.options, options); // If not opting to experimentalUseMMS, and playback is only supported with MediaSource, cannot handle source
58558
58559    if (
58560      !localOptions.vhs.experimentalUseMMS &&
58561      !codecs_js.browserSupportsCodec('avc1.4d400d,mp4a.40.2', false)
58562    ) {
58563      return false;
58564    }
58565    return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
58566  },
58567  handleSource(source, tech, options = {}) {
58568    const localOptions = merge(videojs.options, options);
58569    tech.vhs = new VhsHandler(source, tech, localOptions);
58570    tech.vhs.xhr = xhrFactory();
58571    tech.vhs.setupXhrHooks_();
58572    tech.vhs.src(source.src, source.type);
58573    return tech.vhs;
58574  },
58575  canPlayType(type, options) {
58576    const simpleType = mediaTypes_js.simpleTypeFromSourceType(type);
58577    if (!simpleType) {
58578      return '';
58579    }
58580    const overrideNative = VhsSourceHandler.getOverrideNative(options);
58581    const supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
58582    const canUseMsePlayback = !supportsTypeNatively || overrideNative;
58583    return canUseMsePlayback ? 'maybe' : '';
58584  },
58585  getOverrideNative(options = {}) {
58586    const { vhs = {} } = options;
58587    const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
58588    const { overrideNative = defaultOverrideNative } = vhs;
58589    return overrideNative;
58590  },
58591};
58592/**
58593 * Check to see if either the native MediaSource or ManagedMediaSource
58594 * objectx exist and support an MP4 container with both H.264 video
58595 * and AAC-LC audio.
58596 *
58597 * @return {boolean} if  native media sources are supported
58598 */
58599
58600const supportsNativeMediaSources = () => {
58601  return codecs_js.browserSupportsCodec('avc1.4d400d,mp4a.40.2', true);
58602}; // register source handlers with the appropriate techs
58603
58604if (supportsNativeMediaSources()) {
58605  videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
58606}
58607videojs.VhsHandler = VhsHandler;
58608videojs.VhsSourceHandler = VhsSourceHandler;
58609videojs.Vhs = Vhs;
58610if (!videojs.use) {
58611  videojs.registerComponent('Vhs', Vhs);
58612}
58613videojs.options.vhs = videojs.options.vhs || {};
58614if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
58615  videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
58616}
58617
58618module.exports = videojs;

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.