PageSourceSearch

https://www.vtc.edu.hk/.resources/templates/webresources/dist/js/video-player/video.js

js vtc.edu.hk collected 2026-09-24 08:53:14 UTC 1,836,024 bytes, 55,692 lines download raw bytes

1/**
2 * @license
3 * Video.js 7.7.5 <http://videojs.com/>
4 * Copyright Brightcove, Inc. <https://www.brightcove.com/>
5 * Available under Apache License Version 2.0
6 * <https://github.com/videojs/video.js/blob/master/LICENSE>
7 *
8 * Includes vtt.js <https://github.com/mozilla/vtt.js>
9 * Available under Apache License Version 2.0
10 * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
11 */
12
13(function (global, factory) {
14  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) :
15  typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) :
16  (global = global || self, global.videojs = factory(global.window, global.document));
17}(this, function (window$1, document) { 'use strict';
18
19  window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1;
20  document = document && document.hasOwnProperty('default') ? document['default'] : document;
21
22  var version = "7.7.5";
23
24  /**
25   * @file create-logger.js
26   * @module create-logger
27   */
28
29  var history = [];
30  /**
31   * Log messages to the console and history based on the type of message
32   *
33   * @private
34   * @param  {string} type
35   *         The name of the console method to use.
36   *
37   * @param  {Array} args
38   *         The arguments to be passed to the matching console method.
39   */
40
41  var LogByTypeFactory = function LogByTypeFactory(name, log) {
42    return function (type, level, args) {
43      var lvl = log.levels[level];
44      var lvlRegExp = new RegExp("^(" + lvl + ")$");
45
46      if (type !== 'log') {
47        // Add the type to the front of the message when it's not "log".
48        args.unshift(type.toUpperCase() + ':');
49      } // Add console prefix after adding to history.
50
51
52      args.unshift(name + ':'); // Add a clone of the args at this point to history.
53
54      if (history) {
55        history.push([].concat(args)); // only store 1000 history entries
56
57        var splice = history.length - 1000;
58        history.splice(0, splice > 0 ? splice : 0);
59      } // If there's no console then don't try to output messages, but they will
60      // still be stored in history.
61
62
63      if (!window$1.console) {
64        return;
65      } // Was setting these once outside of this function, but containing them
66      // in the function makes it easier to test cases where console doesn't exist
67      // when the module is executed.
68
69
70      var fn = window$1.console[type];
71
72      if (!fn && type === 'debug') {
73        // Certain browsers don't have support for console.debug. For those, we
74        // should default to the closest comparable log.
75        fn = window$1.console.info || window$1.console.log;
76      } // Bail out if there's no console or if this type is not allowed by the
77      // current logging level.
78
79
80      if (!fn || !lvl || !lvlRegExp.test(type)) {
81        return;
82      }
83
84      fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
85    };
86  };
87
88  function createLogger(name) {
89    // This is the private tracking variable for logging level.
90    var level = 'info'; // the curried logByType bound to the specific log and history
91
92    var logByType;
93    /**
94     * Logs plain debug messages. Similar to `console.log`.
95     *
96     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
97     * of our JSDoc template, we cannot properly document this as both a function
98     * and a namespace, so its function signature is documented here.
99     *
100     * #### Arguments
101     * ##### *args
102     * Mixed[]
103     *
104     * Any combination of values that could be passed to `console.log()`.
105     *
106     * #### Return Value
107     *
108     * `undefined`
109     *
110     * @namespace
111     * @param    {Mixed[]} args
112     *           One or more messages or objects that should be logged.
113     */
114
115    var log = function log() {
116      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
117        args[_key] = arguments[_key];
118      }
119
vendor: 4,441 bytes, lines 120-271
120      logByType('log', level, args);
121    }; // This is the logByType helper that the logging methods below use
122
123
124    logByType = LogByTypeFactory(name, log);
125    /**
126     * Create a new sublogger which chains the old name to the new name.
127     *
128     * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
129     * ```js
130     *  mylogger('foo');
131     *  // > VIDEOJS: player: foo
132     * ```
133     *
134     * @param {string} name
135     *        The name to add call the new logger
136     * @return {Object}
137     */
138
139    log.createLogger = function (subname) {
140      return createLogger(name + ': ' + subname);
141    };
142    /**
143     * Enumeration of available logging levels, where the keys are the level names
144     * and the values are `|`-separated strings containing logging methods allowed
145     * in that logging level. These strings are used to create a regular expression
146     * matching the function name being called.
147     *
148     * Levels provided by Video.js are:
149     *
150     * - `off`: Matches no calls. Any value that can be cast to `false` will have
151     *   this effect. The most restrictive.
152     * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
153     *   `log.warn`, and `log.error`).
154     * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
155     * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
156     * - `warn`: Matches `log.warn` and `log.error` calls.
157     * - `error`: Matches only `log.error` calls.
158     *
159     * @type {Object}
160     */
161
162
163    log.levels = {
164      all: 'debug|log|warn|error',
165      off: '',
166      debug: 'debug|log|warn|error',
167      info: 'log|warn|error',
168      warn: 'warn|error',
169      error: 'error',
170      DEFAULT: level
171    };
172    /**
173     * Get or set the current logging level.
174     *
175     * If a string matching a key from {@link module:log.levels} is provided, acts
176     * as a setter.
177     *
178     * @param  {string} [lvl]
179     *         Pass a valid level to set a new logging level.
180     *
181     * @return {string}
182     *         The current logging level.
183     */
184
185    log.level = function (lvl) {
186      if (typeof lvl === 'string') {
187        if (!log.levels.hasOwnProperty(lvl)) {
188          throw new Error("\"" + lvl + "\" in not a valid log level");
189        }
190
191        level = lvl;
192      }
193
194      return level;
195    };
196    /**
197     * Returns an array containing everything that has been logged to the history.
198     *
199     * This array is a shallow clone of the internal history record. However, its
200     * contents are _not_ cloned; so, mutating objects inside this array will
201     * mutate them in history.
202     *
203     * @return {Array}
204     */
205
206
207    log.history = function () {
208      return history ? [].concat(history) : [];
209    };
210    /**
211     * Allows you to filter the history by the given logger name
212     *
213     * @param {string} fname
214     *        The name to filter by
215     *
216     * @return {Array}
217     *         The filtered list to return
218     */
219
220
221    log.history.filter = function (fname) {
222      return (history || []).filter(function (historyItem) {
223        // if the first item in each historyItem includes `fname`, then it's a match
224        return new RegExp(".*" + fname + ".*").test(historyItem[0]);
225      });
226    };
227    /**
228     * Clears the internal history tracking, but does not prevent further history
229     * tracking.
230     */
231
232
233    log.history.clear = function () {
234      if (history) {
235        history.length = 0;
236      }
237    };
238    /**
239     * Disable history tracking if it is currently enabled.
240     */
241
242
243    log.history.disable = function () {
244      if (history !== null) {
245        history.length = 0;
246        history = null;
247      }
248    };
249    /**
250     * Enable history tracking if it is currently disabled.
251     */
252
253
254    log.history.enable = function () {
255      if (history === null) {
256        history = [];
257      }
258    };
259    /**
260     * Logs error messages. Similar to `console.error`.
261     *
262     * @param {Mixed[]} args
263     *        One or more messages or objects that should be logged as an error
264     */
265
266
267    log.error = function () {
268      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
269        args[_key2] = arguments[_key2];
270      }
271
272      return logByType('error', level, args);
273    };
274    /**
275     * Logs warning messages. Similar to `console.warn`.
276     *
277     * @param {Mixed[]} args
278     *        One or more messages or objects that should be logged as a warning.
279     */
280
281
282    log.warn = function () {
283      for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
284        args[_key3] = arguments[_key3];
285      }
286
287      return logByType('warn', level, args);
288    };
289    /**
290     * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
291     * log if `console.debug` is not available
292     *
293     * @param {Mixed[]} args
294     *        One or more messages or objects that should be logged as debug.
295     */
296
297
298    log.debug = function () {
299      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
300        args[_key4] = arguments[_key4];
301      }
302
303      return logByType('debug', level, args);
304    };
305
306    return log;
307  }
308
309  /**
310   * @file log.js
311   * @module log
312   */
313  var log = createLogger('VIDEOJS');
314  var createLogger$1 = log.createLogger;
315
316  function createCommonjsModule(fn, module) {
317  	return module = { exports: {} }, fn(module, module.exports), module.exports;
318  }
319
320  var _extends_1 = createCommonjsModule(function (module) {
321    function _extends() {
322      module.exports = _extends = Object.assign || function (target) {
323        for (var i = 1; i < arguments.length; i++) {
324          var source = arguments[i];
325
326          for (var key in source) {
327            if (Object.prototype.hasOwnProperty.call(source, key)) {
328              target[key] = source[key];
329            }
330          }
331        }
332
333        return target;
334      };
335
336      return _extends.apply(this, arguments);
337    }
338
339    module.exports = _extends;
340  });
341
342  /**
343   * @file obj.js
344   * @module obj
345   */
346
347  /**
348   * @callback obj:EachCallback
349   *
350   * @param {Mixed} value
351   *        The current key for the object that is being iterated over.
352   *
353   * @param {string} key
354   *        The current key-value for object that is being iterated over
355   */
356
357  /**
358   * @callback obj:ReduceCallback
359   *
360   * @param {Mixed} accum
361   *        The value that is accumulating over the reduce loop.
362   *
363   * @param {Mixed} value
364   *        The current key for the object that is being iterated over.
365   *
366   * @param {string} key
367   *        The current key-value for object that is being iterated over
368   *
369   * @return {Mixed}
370   *         The new accumulated value.
371   */
372  var toString = Object.prototype.toString;
373  /**
374   * Get the keys of an Object
375   *
376   * @param {Object}
377   *        The Object to get the keys from
378   *
379   * @return {string[]}
380   *         An array of the keys from the object. Returns an empty array if the
381   *         object passed in was invalid or had no keys.
382   *
383   * @private
384   */
385
386  var keys = function keys(object) {
387    return isObject(object) ? Object.keys(object) : [];
388  };
389  /**
390   * Array-like iteration for objects.
391   *
392   * @param {Object} object
393   *        The object to iterate over
394   *
395   * @param {obj:EachCallback} fn
396   *        The callback function which is called for each key in the object.
397   */
398
399
400  function each(object, fn) {
401    keys(object).forEach(function (key) {
402      return fn(object[key], key);
403    });
404  }
405  /**
406   * Array-like reduce for objects.
407   *
408   * @param {Object} object
409   *        The Object that you want to reduce.
410   *
411   * @param {Function} fn
412   *         A callback function which is called for each key in the object. It
413   *         receives the accumulated value and the per-iteration value and key
414   *         as arguments.
415   *
416   * @param {Mixed} [initial = 0]
417   *        Starting value
418   *
419   * @return {Mixed}
420   *         The final accumulated value.
421   */
422
423  function reduce(object, fn, initial) {
424    if (initial === void 0) {
425      initial = 0;
426    }
427
428    return keys(object).reduce(function (accum, key) {
429      return fn(accum, object[key], key);
430    }, initial);
431  }
432  /**
433   * Object.assign-style object shallow merge/extend.
434   *
435   * @param  {Object} target
436   * @param  {Object} ...sources
437   * @return {Object}
438   */
439
440  function assign(target) {
441    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
442      sources[_key - 1] = arguments[_key];
443    }
444
445    if (Object.assign) {
446      return _extends_1.apply(void 0, [target].concat(sources));
447    }
448
449    sources.forEach(function (source) {
450      if (!source) {
451        return;
452      }
453
454      each(source, function (value, key) {
455        target[key] = value;
456      });
457    });
458    return target;
459  }
460  /**
461   * Returns whether a value is an object of any kind - including DOM nodes,
462   * arrays, regular expressions, etc. Not functions, though.
463   *
464   * This avoids the gotcha where using `typeof` on a `null` value
465   * results in `'object'`.
466   *
467   * @param  {Object} value
468   * @return {boolean}
469   */
470
471  function isObject(value) {
472    return !!value && typeof value === 'object';
473  }
474  /**
475   * Returns whether an object appears to be a "plain" object - that is, a
476   * direct instance of `Object`.
477   *
478   * @param  {Object} value
479   * @return {boolean}
480   */
481
482  function isPlain(value) {
483    return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
484  }
485
486  /**
487   * @file computed-style.js
488   * @module computed-style
489   */
490  /**
491   * A safe getComputedStyle.
492   *
493   * This is needed because in Firefox, if the player is loaded in an iframe with
494   * `display:none`, then `getComputedStyle` returns `null`, so, we do a
495   * null-check to make sure that the player doesn't break in these cases.
496   *
497   * @function
498   * @param    {Element} el
499   *           The element you want the computed style of
500   *
501   * @param    {string} prop
502   *           The property name you want
503   *
504   * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
505   */
506
507  function computedStyle(el, prop) {
508    if (!el || !prop) {
509      return '';
510    }
511
512    if (typeof window$1.getComputedStyle === 'function') {
513      var computedStyleValue = window$1.getComputedStyle(el);
514      return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
515    }
516
517    return '';
518  }
519
520  /**
521   * @file dom.js
522   * @module dom
523   */
524  /**
525   * Detect if a value is a string with any non-whitespace characters.
526   *
527   * @private
528   * @param  {string} str
529   *         The string to check
530   *
531   * @return {boolean}
532   *         Will be `true` if the string is non-blank, `false` otherwise.
533   *
534   */
535
536  function isNonBlankString(str) {
537    // we use str.trim as it will trim any whitespace characters
538    // from the front or back of non-whitespace characters. aka
539    // Any string that contains non-whitespace characters will
540    // still contain them after `trim` but whitespace only strings
541    // will have a length of 0, failing this check.
542    return typeof str === 'string' && Boolean(str.trim());
543  }
544  /**
545   * Throws an error if the passed string has whitespace. This is used by
546   * class methods to be relatively consistent with the classList API.
547   *
548   * @private
549   * @param  {string} str
550   *         The string to check for whitespace.
551   *
552   * @throws {Error}
553   *         Throws an error if there is whitespace in the string.
554   */
555
556
557  function throwIfWhitespace(str) {
558    // str.indexOf instead of regex because str.indexOf is faster performance wise.
559    if (str.indexOf(' ') >= 0) {
560      throw new Error('class has illegal whitespace characters');
561    }
562  }
563  /**
564   * Produce a regular expression for matching a className within an elements className.
565   *
566   * @private
567   * @param  {string} className
568   *         The className to generate the RegExp for.
569   *
570   * @return {RegExp}
571   *         The RegExp that will check for a specific `className` in an elements
572   *         className.
573   */
574
575
576  function classRegExp(className) {
577    return new RegExp('(^|\\s)' + className + '($|\\s)');
578  }
579  /**
580   * Whether the current DOM interface appears to be real (i.e. not simulated).
581   *
582   * @return {boolean}
583   *         Will be `true` if the DOM appears to be real, `false` otherwise.
584   */
585
586
587  function isReal() {
588    // Both document and window will never be undefined thanks to `global`.
589    return document === window$1.document;
590  }
591  /**
592   * Determines, via duck typing, whether or not a value is a DOM element.
593   *
594   * @param  {Mixed} value
595   *         The value to check.
596   *
597   * @return {boolean}
598   *         Will be `true` if the value is a DOM element, `false` otherwise.
599   */
600
601  function isEl(value) {
602    return isObject(value) && value.nodeType === 1;
603  }
604  /**
605   * Determines if the current DOM is embedded in an iframe.
606   *
607   * @return {boolean}
608   *         Will be `true` if the DOM is embedded in an iframe, `false`
609   *         otherwise.
610   */
611
612  function isInFrame() {
613    // We need a try/catch here because Safari will throw errors when attempting
614    // to get either `parent` or `self`
615    try {
616      return window$1.parent !== window$1.self;
617    } catch (x) {
618      return true;
619    }
620  }
621  /**
622   * Creates functions to query the DOM using a given method.
623   *
624   * @private
625   * @param   {string} method
626   *          The method to create the query with.
627   *
628   * @return  {Function}
629   *          The query method
630   */
631
632  function createQuerier(method) {
633    return function (selector, context) {
634      if (!isNonBlankString(selector)) {
635        return document[method](null);
636      }
637
638      if (isNonBlankString(context)) {
639        context = document.querySelector(context);
640      }
641
642      var ctx = isEl(context) ? context : document;
643      return ctx[method] && ctx[method](selector);
644    };
645  }
646  /**
647   * Creates an element and applies properties, attributes, and inserts content.
648   *
649   * @param  {string} [tagName='div']
650   *         Name of tag to be created.
651   *
652   * @param  {Object} [properties={}]
653   *         Element properties to be applied.
654   *
655   * @param  {Object} [attributes={}]
656   *         Element attributes to be applied.
657   *
658   * @param {module:dom~ContentDescriptor} content
659   *        A content descriptor object.
660   *
661   * @return {Element}
662   *         The element that was created.
663   */
664
665
666  function createEl(tagName, properties, attributes, content) {
667    if (tagName === void 0) {
668      tagName = 'div';
669    }
670
671    if (properties === void 0) {
672      properties = {};
673    }
674
675    if (attributes === void 0) {
676      attributes = {};
677    }
678
679    var el = document.createElement(tagName);
vendor: 9,845 bytes, lines 680-1008
680    Object.getOwnPropertyNames(properties).forEach(function (propName) {
681      var val = properties[propName]; // See #2176
682      // We originally were accepting both properties and attributes in the
683      // same object, but that doesn't work so well.
684
685      if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
686        log.warn('Setting attributes in the second argument of createEl()\n' + 'has been deprecated. Use the third argument instead.\n' + ("createEl(type, properties, attributes). Attempting to set " + propName + " to " + val + "."));
687        el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
688        // method for it.
689      } else if (propName === 'textContent') {
690        textContent(el, val);
691      } else if (el[propName] !== val) {
692        el[propName] = val;
693      }
694    });
695    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
696      el.setAttribute(attrName, attributes[attrName]);
697    });
698
699    if (content) {
700      appendContent(el, content);
701    }
702
703    return el;
704  }
705  /**
706   * Injects text into an element, replacing any existing contents entirely.
707   *
708   * @param  {Element} el
709   *         The element to add text content into
710   *
711   * @param  {string} text
712   *         The text content to add.
713   *
714   * @return {Element}
715   *         The element with added text content.
716   */
717
718  function textContent(el, text) {
719    if (typeof el.textContent === 'undefined') {
720      el.innerText = text;
721    } else {
722      el.textContent = text;
723    }
724
725    return el;
726  }
727  /**
728   * Insert an element as the first child node of another
729   *
730   * @param {Element} child
731   *        Element to insert
732   *
733   * @param {Element} parent
734   *        Element to insert child into
735   */
736
737  function prependTo(child, parent) {
738    if (parent.firstChild) {
739      parent.insertBefore(child, parent.firstChild);
740    } else {
741      parent.appendChild(child);
742    }
743  }
744  /**
745   * Check if an element has a class name.
746   *
747   * @param  {Element} element
748   *         Element to check
749   *
750   * @param  {string} classToCheck
751   *         Class name to check for
752   *
753   * @return {boolean}
754   *         Will be `true` if the element has a class, `false` otherwise.
755   *
756   * @throws {Error}
757   *         Throws an error if `classToCheck` has white space.
758   */
759
760  function hasClass(element, classToCheck) {
761    throwIfWhitespace(classToCheck);
762
763    if (element.classList) {
764      return element.classList.contains(classToCheck);
765    }
766
767    return classRegExp(classToCheck).test(element.className);
768  }
769  /**
770   * Add a class name to an element.
771   *
772   * @param  {Element} element
773   *         Element to add class name to.
774   *
775   * @param  {string} classToAdd
776   *         Class name to add.
777   *
778   * @return {Element}
779   *         The DOM element with the added class name.
780   */
781
782  function addClass(element, classToAdd) {
783    if (element.classList) {
784      element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
785      // in the case of classList not being supported.
786    } else if (!hasClass(element, classToAdd)) {
787      element.className = (element.className + ' ' + classToAdd).trim();
788    }
789
790    return element;
791  }
792  /**
793   * Remove a class name from an element.
794   *
795   * @param  {Element} element
796   *         Element to remove a class name from.
797   *
798   * @param  {string} classToRemove
799   *         Class name to remove
800   *
801   * @return {Element}
802   *         The DOM element with class name removed.
803   */
804
805  function removeClass(element, classToRemove) {
806    if (element.classList) {
807      element.classList.remove(classToRemove);
808    } else {
809      throwIfWhitespace(classToRemove);
810      element.className = element.className.split(/\s+/).filter(function (c) {
811        return c !== classToRemove;
812      }).join(' ');
813    }
814
815    return element;
816  }
817  /**
818   * The callback definition for toggleClass.
819   *
820   * @callback module:dom~PredicateCallback
821   * @param    {Element} element
822   *           The DOM element of the Component.
823   *
824   * @param    {string} classToToggle
825   *           The `className` that wants to be toggled
826   *
827   * @return   {boolean|undefined}
828   *           If `true` is returned, the `classToToggle` will be added to the
829   *           `element`. If `false`, the `classToToggle` will be removed from
830   *           the `element`. If `undefined`, the callback will be ignored.
831   */
832
833  /**
834   * Adds or removes a class name to/from an element depending on an optional
835   * condition or the presence/absence of the class name.
836   *
837   * @param  {Element} element
838   *         The element to toggle a class name on.
839   *
840   * @param  {string} classToToggle
841   *         The class that should be toggled.
842   *
843   * @param  {boolean|module:dom~PredicateCallback} [predicate]
844   *         See the return value for {@link module:dom~PredicateCallback}
845   *
846   * @return {Element}
847   *         The element with a class that has been toggled.
848   */
849
850  function toggleClass(element, classToToggle, predicate) {
851    // This CANNOT use `classList` internally because IE11 does not support the
852    // second parameter to the `classList.toggle()` method! Which is fine because
853    // `classList` will be used by the add/remove functions.
854    var has = hasClass(element, classToToggle);
855
856    if (typeof predicate === 'function') {
857      predicate = predicate(element, classToToggle);
858    }
859
860    if (typeof predicate !== 'boolean') {
861      predicate = !has;
862    } // If the necessary class operation matches the current state of the
863    // element, no action is required.
864
865
866    if (predicate === has) {
867      return;
868    }
869
870    if (predicate) {
871      addClass(element, classToToggle);
872    } else {
873      removeClass(element, classToToggle);
874    }
875
876    return element;
877  }
878  /**
879   * Apply attributes to an HTML element.
880   *
881   * @param {Element} el
882   *        Element to add attributes to.
883   *
884   * @param {Object} [attributes]
885   *        Attributes to be applied.
886   */
887
888  function setAttributes(el, attributes) {
889    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
890      var attrValue = attributes[attrName];
891
892      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
893        el.removeAttribute(attrName);
894      } else {
895        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
896      }
897    });
898  }
899  /**
900   * Get an element's attribute values, as defined on the HTML tag.
901   *
902   * Attributes are not the same as properties. They're defined on the tag
903   * or with setAttribute.
904   *
905   * @param  {Element} tag
906   *         Element from which to get tag attributes.
907   *
908   * @return {Object}
909   *         All attributes of the element. Boolean attributes will be `true` or
910   *         `false`, others will be strings.
911   */
912
913  function getAttributes(tag) {
914    var obj = {}; // known boolean attributes
915    // we can check for matching boolean properties, but not all browsers
916    // and not all tags know about these attributes, so, we still want to check them manually
917
918    var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
919
920    if (tag && tag.attributes && tag.attributes.length > 0) {
921      var attrs = tag.attributes;
922
923      for (var i = attrs.length - 1; i >= 0; i--) {
924        var attrName = attrs[i].name;
925        var attrVal = attrs[i].value; // check for known booleans
926        // the matching element property will return a value for typeof
927
928        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
929          // the value of an included boolean attribute is typically an empty
930          // string ('') which would equal false if we just check for a false value.
931          // we also don't want support bad code like autoplay='false'
932          attrVal = attrVal !== null ? true : false;
933        }
934
935        obj[attrName] = attrVal;
936      }
937    }
938
939    return obj;
940  }
941  /**
942   * Get the value of an element's attribute.
943   *
944   * @param {Element} el
945   *        A DOM element.
946   *
947   * @param {string} attribute
948   *        Attribute to get the value of.
949   *
950   * @return {string}
951   *         The value of the attribute.
952   */
953
954  function getAttribute(el, attribute) {
955    return el.getAttribute(attribute);
956  }
957  /**
958   * Set the value of an element's attribute.
959   *
960   * @param {Element} el
961   *        A DOM element.
962   *
963   * @param {string} attribute
964   *        Attribute to set.
965   *
966   * @param {string} value
967   *        Value to set the attribute to.
968   */
969
970  function setAttribute(el, attribute, value) {
971    el.setAttribute(attribute, value);
972  }
973  /**
974   * Remove an element's attribute.
975   *
976   * @param {Element} el
977   *        A DOM element.
978   *
979   * @param {string} attribute
980   *        Attribute to remove.
981   */
982
983  function removeAttribute(el, attribute) {
984    el.removeAttribute(attribute);
985  }
986  /**
987   * Attempt to block the ability to select text.
988   */
989
990  function blockTextSelection() {
991    document.body.focus();
992
993    document.onselectstart = function () {
994      return false;
995    };
996  }
997  /**
998   * Turn off text selection blocking.
999   */
1000
1001  function unblockTextSelection() {
1002    document.onselectstart = function () {
1003      return true;
1004    };
1005  }
1006  /**
1007   * Identical to the native `getBoundingClientRect` function, but ensures that
1008   * the method is supported at all (it is in all browsers we claim to supp
1008ort)
1009   * and that the element is in the DOM before continuing.
1010   *
1011   * This wrapper function also shims properties which are not provided by some
1012   * older browsers (namely, IE8).
1013   *
1014   * Additionally, some browsers do not support adding properties to a
1015   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1016   * properties (except `x` and `y` which are not widely supported). This helps
1017   * avoid implementations where keys are non-enumerable.
1018   *
1019   * @param  {Element} el
1020   *         Element whose `ClientRect` we want to calculate.
1021   *
1022   * @return {Object|undefined}
1023   *         Always returns a plain object - or `undefined` if it cannot.
1024   */
1025
1026  function getBoundingClientRect(el) {
1027    if (el && el.getBoundingClientRect && el.parentNode) {
1028      var rect = el.getBoundingClientRect();
1029      var result = {};
1030      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1031        if (rect[k] !== undefined) {
1032          result[k] = rect[k];
1033        }
1034      });
1035
1036      if (!result.height) {
1037        result.height = parseFloat(computedStyle(el, 'height'));
1038      }
1039
1040      if (!result.width) {
1041        result.width = parseFloat(computedStyle(el, 'width'));
1042      }
1043
1044      return result;
1045    }
1046  }
1047  /**
1048   * Represents the position of a DOM element on the page.
1049   *
1050   * @typedef  {Object} module:dom~Position
1051   *
1052   * @property {number} left
1053   *           Pixels to the left.
1054   *
1055   * @property {number} top
1056   *           Pixels from the top.
1057   */
1058
1059  /**
1060   * Get the position of an element in the DOM.
1061   *
1062   * Uses `getBoundingClientRect` technique from John Resig.
1063   *
1064   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1065   *
1066   * @param  {Element} el
1067   *         Element from which to get offset.
1068   *
1069   * @return {module:dom~Position}
1070   *         The position of the element that was passed in.
1071   */
1072
1073  function findPosition(el) {
1074    var box;
1075
1076    if (el.getBoundingClientRect && el.parentNode) {
1077      box = el.getBoundingClientRect();
1078    }
1079
1080    if (!box) {
1081      return {
1082        left: 0,
1083        top: 0
1084      };
1085    }
1086
1087    var docEl = document.documentElement;
1088    var body = document.body;
1089    var clientLeft = docEl.clientLeft || body.clientLeft || 0;
1090    var scrollLeft = window$1.pageXOffset || body.scrollLeft;
1091    var left = box.left + scrollLeft - clientLeft;
1092    var clientTop = docEl.clientTop || body.clientTop || 0;
1093    var scrollTop = window$1.pageYOffset || body.scrollTop;
1094    var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
1095
1096    return {
1097      left: Math.round(left),
1098      top: Math.round(top)
1099    };
1100  }
1101  /**
1102   * Represents x and y coordinates for a DOM element or mouse pointer.
1103   *
1104   * @typedef  {Object} module:dom~Coordinates
1105   *
1106   * @property {number} x
1107   *           x coordinate in pixels
1108   *
1109   * @property {number} y
1110   *           y coordinate in pixels
1111   */
1112
1113  /**
1114   * Get the pointer position within an element.
1115   *
1116   * The base on the coordinates are the bottom left of the element.
1117   *
1118   * @param  {Element} el
1119   *         Element on which to get the pointer position on.
1120   *
1121   * @param  {EventTarget~Event} event
1122   *         Event object.
1123   *
1124   * @return {module:dom~Coordinates}
1125   *         A coordinates object corresponding to the mouse position.
1126   *
1127   */
1128
1129  function getPointerPosition(el, event) {
1130    var position = {};
1131    var box = findPosition(el);
1132    var boxW = el.offsetWidth;
1133    var boxH = el.offsetHeight;
1134    var boxY = box.top;
1135    var boxX = box.left;
1136    var pageY = event.pageY;
1137    var pageX = event.pageX;
1138
1139    if (event.changedTouches) {
1140      pageX = event.changedTouches[0].pageX;
1141      pageY = event.changedTouches[0].pageY;
1142    }
1143
1144    position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
1145    position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
1146    return position;
1147  }
1148  /**
1149   * Determines, via duck typing, whether or not a value is a text node.
1150   *
1151   * @param  {Mixed} value
1152   *         Check if this value is a text node.
1153   *
1154   * @return {boolean}
1155   *         Will be `true` if the value is a text node, `false` otherwise.
1156   */
1157
1158  function isTextNode(value) {
1159    return isObject(value) && value.nodeType === 3;
1160  }
1161  /**
1162   * Empties the contents of an element.
1163   *
1164   * @param  {Element} el
1165   *         The element to empty children from
1166   *
1167   * @return {Element}
1168   *         The element with no children
1169   */
1170
1171  function emptyEl(el) {
1172    while (el.firstChild) {
1173      el.removeChild(el.firstChild);
1174    }
1175
1176    return el;
1177  }
1178  /**
1179   * This is a mixed value that describes content to be injected into the DOM
1180   * via some method. It can be of the following types:
1181   *
1182   * Type       | Description
1183   * -----------|-------------
1184   * `string`   | The value will be normalized into a text node.
1185   * `Element`  | The value will be accepted as-is.
1186   * `TextNode` | The value will be accepted as-is.
1187   * `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).
1188   * `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.
1189   *
1190   * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
1191   */
1192
1193  /**
1194   * Normalizes content for eventual insertion into the DOM.
1195   *
1196   * This allows a wide range of content definition methods, but helps protect
1197   * from falling into the trap of simply writing to `innerHTML`, which could
1198   * be an XSS concern.
1199   *
1200   * The content for an element can be passed in multiple types and
1201   * combinations, whose behavior is as follows:
1202   *
1203   * @param {module:dom~ContentDescriptor} content
1204   *        A content descriptor value.
1205   *
1206   * @return {Array}
1207   *         All of the content that was passed in, normalized to an array of
1208   *         elements or text nodes.
1209   */
1210
1211  function normalizeContent(content) {
1212    // First, invoke content if it is a function. If it produces an array,
1213    // that needs to happen before normalization.
1214    if (typeof content === 'function') {
1215      content = content();
1216    } // Next up, normalize to an array, so one or many items can be normalized,
1217    // filtered, and returned.
1218
1219
1220    return (Array.isArray(content) ? content : [content]).map(function (value) {
1221      // First, invoke value if it is a function to produce a new value,
1222      // which will be subsequently normalized to a Node of some kind.
1223      if (typeof value === 'function') {
1224        value = value();
1225      }
1226
1227      if (isEl(value) || isTextNode(value)) {
1228        return value;
1229      }
1230
1231      if (typeof value === 'string' && /\S/.test(value)) {
1232        return document.createTextNode(value);
1233      }
1234    }).filter(function (value) {
1235      return value;
1236    });
1237  }
1238  /**
1239   * Normalizes and appends content to an element.
1240   *
1241   * @param  {Element} el
1242   *         Element to append normalized content to.
1243   *
1244   * @param {module:dom~ContentDescriptor} content
1245   *        A content descriptor value.
1246   *
1247   * @return {Element}
1248   *         The element with appended normalized content.
1249   */
1250
1251  function appendContent(el, content) {
1252    normalizeContent(content).forEach(function (node) {
1253      return el.appendChild(node);
1254    });
1255    return el;
1256  }
1257  /**
1258   * Normalizes and inserts content into an element; this is identical to
1259   * `appendContent()`, except it empties the element first.
1260   *
1261   * @param {Element} el
1262   *        Element to insert normalized content into.
1263   *
1264   * @param {module:dom~ContentDescriptor} content
1265   *        A content descriptor value.
1266   *
1267   * @return {Element}
1268   *         The element with inserted normalized content.
1269   */
1270
1271  function insertContent(el, content) {
1272    return appendContent(emptyEl(el), content);
1273  }
1274  /**
1275   * Check if an event was a single left click.
1276   *
1277   * @param  {EventTarget~Event} event
1278   *         Event object.
1279   *
1280   * @return {boolean}
1281   *         Will be `true` if a single left click, `false` otherwise.
1282   */
1283
1284  function isSingleLeftClick(event) {
1285    // Note: if you create something draggable, be sure to
1286    // call it on both `mousedown` and `mousemove` event,
1287    // otherwise `mousedown` should be enough for a button
1288    if (event.button === undefined && event.buttons === undefined) {
1289      // Why do we need `buttons` ?
1290      // Because, middle mouse sometimes have this:
1291      // e.button === 0 and e.buttons === 4
1292      // Furthermore, we want to prevent combination click, something like
1293      // HOLD middlemouse then left click, that would be
1294      // e.button === 0, e.buttons === 5
1295      // just `button` is not gonna work
1296      // Alright, then what this block does ?
1297      // this is for chrome `simulate mobile devices`
1298      // I want to support this as well
1299      return true;
1300    }
1301
1302    if (event.button === 0 && event.buttons === undefined) {
1303      // Touch screen, sometimes on some specific device, `buttons`
1304      // doesn't have anything (safari on ios, blackberry...)
1305      return true;
1306    } // `mouseup` event on a single left click has
1307    // `button` and `buttons` equal to 0
1308
1309
1310    if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
1311      return true;
1312    }
1313
1314    if (event.button !== 0 || event.buttons !== 1) {
1315      // This is the reason we have those if else block above
1316      // if any special case we can catch and let it slide
1317      // we do it above, when get to here, this definitely
1318      // is-not-left-click
1319      return false;
1320    }
1321
1322    return true;
1323  }
1324  /**
1325   * Finds a single DOM element matching `selector` within the optional
1326   * `context` of another DOM element (defaulting to `document`).
1327   *
1328   * @param  {string} selector
1329   *         A valid CSS selector, which will be passed to `querySelector`.
1330   *
1331   * @param  {Element|String} [context=document]
1332   *         A DOM element within which to query. Can also be a selector
1333   *         string in which case the first matching element will be used
1334   *         as context. If missing (or no element matches selector), falls
1335   *         back to `document`.
1336   *
1337   * @return {Element|null}
1338   *         The element that was found or null.
1339   */
1340
1341  var $ = createQuerier('querySelector');
1342  /**
1343   * Finds a all DOM elements matching `selector` within the optional
1344   * `context` of another DOM element (defaulting to `document`).
1345   *
1346   * @param  {string} selector
1347   *         A valid CSS selector, which will be passed to `querySelectorAll`.
1348   *
1349   * @param  {Element|String} [context=document]
1350   *         A DOM element within which to query. Can also be a selector
1351   *         string in which case the first matching element will be used
1352   *         as context. If missing (or no element matches selector), falls
1353   *         back to `document`.
1354   *
1355   * @return {NodeList}
1356   *         A element list of elements that were found. Will be empty if none
1357   *         were found.
1358   *
1359   */
1360
1361  var $$ = createQuerier('querySelectorAll');
1362
1363  var Dom = /*#__PURE__*/Object.freeze({
1364    isReal: isReal,
1365    isEl: isEl,
1366    isInFrame: isInFrame,
1367    createEl: createEl,
1368    textContent: textContent,
1369    prependTo: prependTo,
1370    hasClass: hasClass,
1371    addClass: addClass,
1372    removeClass: removeClass,
1373    toggleClass: toggleClass,
1374    setAttributes: setAttributes,
1375    getAttributes: getAttributes,
1376    getAttribute: getAttribute,
1377    setAttribute: setAttribute,
1378    removeAttribute: removeAttribute,
1379    blockTextSelection: blockTextSelection,
1380    unblockTextSelection: unblockTextSelection,
1381    getBoundingClientRect: getBoundingClientRect,
1382    findPosition: findPosition,
1383    getPointerPosition: getPointerPosition,
1384    isTextNode: isTextNode,
1385    emptyEl: emptyEl,
1386    normalizeContent: normalizeContent,
1387    appendContent: appendContent,
1388    insertContent: insertContent,
1389    isSingleLeftClick: isSingleLeftClick,
1390    $: $,
1391    $$: $$
1392  });
1393
1394  /**
1395   * @file setup.js - Functions for setting up a player without
1396   * user interaction based on the data-setup `attribute` of the video tag.
1397   *
1398   * @module setup
1399   */
1400  var _windowLoaded = false;
1401  var videojs;
1402  /**
1403   * Set up any tags that have a data-setup `attribute` when the player is started.
1404   */
1405
1406  var autoSetup = function autoSetup() {
1407    // Protect against breakage in non-browser environments and check global autoSetup option.
1408    if (!isReal() || videojs.options.autoSetup === false) {
1409      return;
1410    }
1411
1412    var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
1413    var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
1414    var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
1415    var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
1416
1417    if (mediaEls && mediaEls.length > 0) {
1418      for (var i = 0, e = mediaEls.length; i < e; i++) {
1419        var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
1420
1421        if (mediaEl && mediaEl.getAttribute) {
1422          // Make sure this player hasn't already been set up.
1423          if (mediaEl.player === undefined) {
1424            var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
1425            // We only auto-setup if they've added the data-setup attr.
1426
1427            if (options !== null) {
1428              // Create new video.js instance.
1429              videojs(mediaEl);
1430            }
1431          } // If getAttribute isn't defined, we need to wait for the DOM.
1432
1433        } else {
1434          autoSetupTimeout(1);
1435          break;
1436        }
1437      } // No videos were found, so keep looping unless page is finished loading.
1438
1439    } else if (!_windowLoaded) {
1440      autoSetupTimeout(1);
1441    }
1442  };
1443  /**
1444   * Wait until the page is loaded before running autoSetup. This will be called in
1445   * autoSetup if `hasLoaded` returns false.
1446   *
1447   * @param {number} wait
1448   *        How long to wait in ms
1449   *
1450   * @param {module:videojs} [vjs]
1451   *        The videojs library function
1452   */
1453
1454
1455  function autoSetupTimeout(wait, vjs) {
1456    if (vjs) {
1457      videojs = vjs;
1458    }
1459
1460    window$1.setTimeout(autoSetup, wait);
1461  }
1462  /**
1463   * Used to set the internal tracking of window loaded state to true.
1464   *
1465   * @private
1466   */
1467
1468
1469  function setWindowLoaded() {
1470    _windowLoaded = true;
1471    window$1.removeEventListener('load', setWindowLoaded);
1472  }
1473
1474  if (isReal()) {
1475    if (document.readyState === 'complete') {
1476      setWindowLoaded();
1477    } else {
1478      /**
1479       * Listen for the load event on window, and set _windowLoaded to true.
1480       *
1481       * We use a standard event listener here to avoid incrementing the GUID
1482       * before any players are created.
1483       *
1484       * @listens load
1485       */
1486      window$1.addEventListener('load', setWindowLoaded);
1487    }
1488  }
1489
1490  /**
1491   * @file stylesheet.js
1492   * @module stylesheet
1493   */
1494  /**
1495   * Create a DOM syle element given a className for it.
1496   *
1497   * @param {string} className
1498   *        The className to add to the created style element.
1499   *
1500   * @return {Element}
1501   *         The element that was created.
1502   */
1503
1504  var createStyleElement = function createStyleElement(className) {
1505    var style = document.createElement('style');
1506    style.className = className;
1507    return style;
1508  };
1509  /**
1510   * Add text to a DOM element.
1511   *
1512   * @param {Element} el
1513   *        The Element to add text content to.
1514   *
1515   * @param {string} content
1516   *        The text to add to the element.
1517   */
1518
1519  var setTextContent = function setTextContent(el, content) {
1520    if (el.styleSheet) {
1521      el.styleSheet.cssText = content;
1522    } else {
1523      el.textContent = content;
1524    }
1525  };
1526
1527  /**
1528   * @file guid.js
1529   * @module guid
1530   */
1531  // Default value for GUIDs. This allows us to reset the GUID counter in tests.
1532  //
1533  // The initial GUID is 3 because some users have come to rely on the first
1534  // default player ID ending up as `vjs_video_3`.
1535  //
1536  // See: https://github.com/videojs/video.js/pull/6216
1537  var _initialGuid = 3;
1538  /**
1539   * Unique ID for an element or function
1540   *
1541   * @type {Number}
1542   */
1543
1544  var _guid = _initialGuid;
1545  /**
1546   * Get a unique auto-incrementing ID by number that has not been returned before.
1547   *
1548   * @return {number}
1549   *         A new unique ID.
1550   */
1551
1552  function newGUID() {
1553    return _guid++;
1554  }
1555
1556  /**
1557   * @file dom-data.js
1558   * @module dom-data
1559   */
1560  var FakeWeakMap;
1561
1562  if (!window$1.WeakMap) {
1563    FakeWeakMap =
1564    /*#__PURE__*/
1565    function () {
1566      function FakeWeakMap() {
1567        this.vdata = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
1568        this.data = {};
1569      }
1570
1571      var _proto = FakeWeakMap.prototype;
1572
1573      _proto.set = function set(key, value) {
1574        var access = key[this.vdata] || newGUID();
1575
1576        if (!key[this.vdata]) {
1577          key[this.vdata] = access;
1578        }
1579
1580        this.data[access] = value;
1581        return this;
1582      };
1583
1584      _proto.get = function get(key) {
1585        var access = key[this.vdata]; // we have data, return it
1586
1587        if (access) {
1588          return this.data[access];
1589        } // we don't have data, return nothing.
1590        // return undefined explicitly as that's the contract for this method
1591
1592
1593        log('We have no data for this element', key);
1594        return undefined;
1595      };
1596
1597      _proto.has = function has(key) {
1598        var access = key[this.vdata];
1599        return access in this.data;
1600      };
1601
1602      _proto["delete"] = function _delete(key) {
1603        var access = key[this.vdata];
1604
1605        if (access) {
1606          delete this.data[access];
1607          delete key[this.vdata];
1608        }
1609      };
1610
1611      return FakeWeakMap;
1612    }();
1613  }
1614  /**
1615   * Element Data Store.
1616   *
1617   * Allows for binding data to an element without putting it directly on the
1618   * element. Ex. Event listeners are stored here.
1619   * (also from jsninja.com, slightly modified and updated for closure compiler)
1620   *
1621   * @type {Object}
1622   * @private
1623   */
1624
1625
1626  var DomData = window$1.WeakMap ? new WeakMap() : new FakeWeakMap();
1627
1628  /**
1629   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1630   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1631   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1632   * robust as jquery's, so there's probably some differences.
1633   *
1634   * @file events.js
1635   * @module events
1636   */
1637  /**
1638   * Clean up the listener cache and dispatchers
1639   *
vendor: 6,367 bytes, lines 1640-1831
1640   * @param {Element|Object} elem
1641   *        Element to clean up
1642   *
1643   * @param {string} type
1644   *        Type of event to clean up
1645   */
1646
1647  function _cleanUpEvents(elem, type) {
1648    if (!DomData.has(elem)) {
1649      return;
1650    }
1651
1652    var data = DomData.get(elem); // Remove the events of a particular type if there are none left
1653
1654    if (data.handlers[type].length === 0) {
1655      delete data.handlers[type]; // data.handlers[type] = null;
1656      // Setting to null was causing an error with data.handlers
1657      // Remove the meta-handler from the element
1658
1659      if (elem.removeEventListener) {
1660        elem.removeEventListener(type, data.dispatcher, false);
1661      } else if (elem.detachEvent) {
1662        elem.detachEvent('on' + type, data.dispatcher);
1663      }
1664    } // Remove the events object if there are no types left
1665
1666
1667    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1668      delete data.handlers;
1669      delete data.dispatcher;
1670      delete data.disabled;
1671    } // Finally remove the element data if there is no data left
1672
1673
1674    if (Object.getOwnPropertyNames(data).length === 0) {
1675      DomData["delete"](elem);
1676    }
1677  }
1678  /**
1679   * Loops through an array of event types and calls the requested method for each type.
1680   *
1681   * @param {Function} fn
1682   *        The event method we want to use.
1683   *
1684   * @param {Element|Object} elem
1685   *        Element or object to bind listeners to
1686   *
1687   * @param {string} type
1688   *        Type of event to bind to.
1689   *
1690   * @param {EventTarget~EventListener} callback
1691   *        Event listener.
1692   */
1693
1694
1695  function _handleMultipleEvents(fn, elem, types, callback) {
1696    types.forEach(function (type) {
1697      // Call the event method for each one of the types
1698      fn(elem, type, callback);
1699    });
1700  }
1701  /**
1702   * Fix a native event to have standard property values
1703   *
1704   * @param {Object} event
1705   *        Event object to fix.
1706   *
1707   * @return {Object}
1708   *         Fixed event object.
1709   */
1710
1711
1712  function fixEvent(event) {
1713    if (event.fixed_) {
1714      return event;
1715    }
1716
1717    function returnTrue() {
1718      return true;
1719    }
1720
1721    function returnFalse() {
1722      return false;
1723    } // Test if fixing up is needed
1724    // Used to check if !event.stopPropagation instead of isPropagationStopped
1725    // But native events return true for stopPropagation, but don't have
1726    // other expected methods like isPropagationStopped. Seems to be a problem
1727    // with the Javascript Ninja code. So we're just overriding all events now.
1728
1729
1730    if (!event || !event.isPropagationStopped) {
1731      var old = event || window$1.event;
1732      event = {}; // Clone the old object so that we can modify the values event = {};
1733      // IE8 Doesn't like when you mess with native event properties
1734      // Firefox returns false for event.hasOwnProperty('type') and other props
1735      //  which makes copying more difficult.
1736      // TODO: Probably best to create a whitelist of event props
1737
1738      for (var key in old) {
1739        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
1740        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
1741        // and webkitMovementX/Y
1742        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
1743          // Chrome 32+ warns if you try to copy deprecated returnValue, but
1744          // we still want to if preventDefault isn't supported (IE8).
1745          if (!(key === 'returnValue' && old.preventDefault)) {
1746            event[key] = old[key];
1747          }
1748        }
1749      } // The event occurred on this element
1750
1751
1752      if (!event.target) {
1753        event.target = event.srcElement || document;
1754      } // Handle which other element the event is related to
1755
1756
1757      if (!event.relatedTarget) {
1758        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1759      } // Stop the default browser action
1760
1761
1762      event.preventDefault = function () {
1763        if (old.preventDefault) {
1764          old.preventDefault();
1765        }
1766
1767        event.returnValue = false;
1768        old.returnValue = false;
1769        event.defaultPrevented = true;
1770      };
1771
1772      event.defaultPrevented = false; // Stop the event from bubbling
1773
1774      event.stopPropagation = function () {
1775        if (old.stopPropagation) {
1776          old.stopPropagation();
1777        }
1778
1779        event.cancelBubble = true;
1780        old.cancelBubble = true;
1781        event.isPropagationStopped = returnTrue;
1782      };
1783
1784      event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
1785
1786      event.stopImmediatePropagation = function () {
1787        if (old.stopImmediatePropagation) {
1788          old.stopImmediatePropagation();
1789        }
1790
1791        event.isImmediatePropagationStopped = returnTrue;
1792        event.stopPropagation();
1793      };
1794
1795      event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
1796
1797      if (event.clientX !== null && event.clientX !== undefined) {
1798        var doc = document.documentElement;
1799        var body = document.body;
1800        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1801        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
1802      } // Handle key presses
1803
1804
1805      event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
1806      // 0 == left; 1 == middle; 2 == right
1807
1808      if (event.button !== null && event.button !== undefined) {
1809        // The following is disabled because it does not pass videojs-standard
1810        // and... yikes.
1811
1812        /* eslint-disable */
1813        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
1814        /* eslint-enable */
1815      }
1816    }
1817
1818    event.fixed_ = true; // Returns fixed-up instance
1819
1820    return event;
1821  }
1822  /**
1823   * Whether passive event listeners are supported
1824   */
1825
1826  var _supportsPassive;
1827
1828  var supportsPassive = function supportsPassive() {
1829    if (typeof _supportsPassive !== 'boolean') {
1830      _supportsPassive = false;
1831
vendor: 4,581 bytes, lines 1832-2012
1832      try {
1833        var opts = Object.defineProperty({}, 'passive', {
1834          get: function get() {
1835            _supportsPassive = true;
1836          }
1837        });
1838        window$1.addEventListener('test', null, opts);
1839        window$1.removeEventListener('test', null, opts);
1840      } catch (e) {// disregard
1841      }
1842    }
1843
1844    return _supportsPassive;
1845  };
1846  /**
1847   * Touch events Chrome expects to be passive
1848   */
1849
1850
1851  var passiveEvents = ['touchstart', 'touchmove'];
1852  /**
1853   * Add an event listener to element
1854   * It stores the handler function in a separate cache object
1855   * and adds a generic handler to the element's event,
1856   * along with a unique id (guid) to the element.
1857   *
1858   * @param {Element|Object} elem
1859   *        Element or object to bind listeners to
1860   *
1861   * @param {string|string[]} type
1862   *        Type of event to bind to.
1863   *
1864   * @param {EventTarget~EventListener} fn
1865   *        Event listener.
1866   */
1867
1868  function on(elem, type, fn) {
1869    if (Array.isArray(type)) {
1870      return _handleMultipleEvents(on, elem, type, fn);
1871    }
1872
1873    if (!DomData.has(elem)) {
1874      DomData.set(elem, {});
1875    }
1876
1877    var data = DomData.get(elem); // We need a place to store all our handler data
1878
1879    if (!data.handlers) {
1880      data.handlers = {};
1881    }
1882
1883    if (!data.handlers[type]) {
1884      data.handlers[type] = [];
1885    }
1886
1887    if (!fn.guid) {
1888      fn.guid = newGUID();
1889    }
1890
1891    data.handlers[type].push(fn);
1892
1893    if (!data.dispatcher) {
1894      data.disabled = false;
1895
1896      data.dispatcher = function (event, hash) {
1897        if (data.disabled) {
1898          return;
1899        }
1900
1901        event = fixEvent(event);
1902        var handlers = data.handlers[event.type];
1903
1904        if (handlers) {
1905          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1906          var handlersCopy = handlers.slice(0);
1907
1908          for (var m = 0, n = handlersCopy.length; m < n; m++) {
1909            if (event.isImmediatePropagationStopped()) {
1910              break;
1911            } else {
1912              try {
1913                handlersCopy[m].call(elem, event, hash);
1914              } catch (e) {
1915                log.error(e);
1916              }
1917            }
1918          }
1919        }
1920      };
1921    }
1922
1923    if (data.handlers[type].length === 1) {
1924      if (elem.addEventListener) {
1925        var options = false;
1926
1927        if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
1928          options = {
1929            passive: true
1930          };
1931        }
1932
1933        elem.addEventListener(type, data.dispatcher, options);
1934      } else if (elem.attachEvent) {
1935        elem.attachEvent('on' + type, data.dispatcher);
1936      }
1937    }
1938  }
1939  /**
1940   * Removes event listeners from an element
1941   *
1942   * @param {Element|Object} elem
1943   *        Object to remove listeners from.
1944   *
1945   * @param {string|string[]} [type]
1946   *        Type of listener to remove. Don't include to remove all events from element.
1947   *
1948   * @param {EventTarget~EventListener} [fn]
1949   *        Specific listener to remove. Don't include to remove listeners for an event
1950   *        type.
1951   */
1952
1953  function off(elem, type, fn) {
1954    // Don't want to add a cache object through getElData if not needed
1955    if (!DomData.has(elem)) {
1956      return;
1957    }
1958
1959    var data = DomData.get(elem); // If no events exist, nothing to unbind
1960
1961    if (!data.handlers) {
1962      return;
1963    }
1964
1965    if (Array.isArray(type)) {
1966      return _handleMultipleEvents(off, elem, type, fn);
1967    } // Utility function
1968
1969
1970    var removeType = function removeType(el, t) {
1971      data.handlers[t] = [];
1972
1973      _cleanUpEvents(el, t);
1974    }; // Are we removing all bound events?
1975
1976
1977    if (type === undefined) {
1978      for (var t in data.handlers) {
1979        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
1980          removeType(elem, t);
1981        }
1982      }
1983
1984      return;
1985    }
1986
1987    var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
1988
1989    if (!handlers) {
1990      return;
1991    } // If no listener was provided, remove all listeners for type
1992
1993
1994    if (!fn) {
1995      removeType(elem, type);
1996      return;
1997    } // We're only removing a single handler
1998
1999
2000    if (fn.guid) {
2001      for (var n = 0; n < handlers.length; n++) {
2002        if (handlers[n].guid === fn.guid) {
2003          handlers.splice(n--, 1);
2004        }
2005      }
2006    }
2007
2008    _cleanUpEvents(elem, type);
2009  }
2010  /**
2011   * Trigger an event for an element
2012   *
2013   * @param {Element|Object} elem
2014   *        Element to trigger an event on
2015   *
2016   * @param {EventTarget~Event|string} event
2017   *        A string (the type) or an event object with a type attribute
2018   *
2019   * @param {Object} [hash]
2020   *        data hash to pass along with the event
2021   *
2022   * @return {boolean|undefined}
2023   *         Returns the opposite of `defaultPrevented` if default was
2024   *         prevented. Otherwise, returns `undefined`
2025   */
2026
2027  function trigger(elem, event, hash) {
2028    // Fetches element data and a reference to the parent (for bubbling).
2029    // Don't want to add a data object to cache for every parent,
2030    // so checking hasElData first.
2031    var elemData = DomData.has(elem) ? DomData.get(elem) : {};
2032    var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
2033    // handler;
2034    // If an event name was passed as a string, creates an event out of it
2035
2036    if (typeof event === 'string') {
2037      event = {
2038        type: event,
2039        target: elem
2040      };
2041    } else if (!event.target) {
2042      event.target = elem;
2043    } // Normalizes the event properties.
2044
2045
2046    event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
2047
2048    if (elemData.dispatcher) {
2049      elemData.dispatcher.call(elem, event, hash);
2050    } // Unless explicitly stopped or the event does not bubble (e.g. media events)
2051    // recursively calls this function to bubble the event up the DOM.
2052
2053
2054    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2055      trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
2056    } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2057      if (!DomData.has(event.target)) {
2058        DomData.set(event.target, {});
2059      }
2060
2061      var targetData = DomData.get(event.target); // Checks if the target has a default action for this event.
2062
2063      if (event.target[event.type]) {
2064        // Temporarily disables event dispatching on the target as we have already executed the handler.
2065        targetData.disabled = true; // Executes the default action.
2066
2067        if (typeof event.target[event.type] === 'function') {
2068          event.target[event.type]();
2069        } // Re-enables event dispatching.
2070
2071
2072        targetData.disabled = false;
2073      }
2074    } // Inform the triggerer if the default was prevented by returning false
2075
2076
2077    return !event.defaultPrevented;
2078  }
2079  /**
2080   * Trigger a listener only once for an event.
2081   *
2082   * @param {Element|Object} elem
2083   *        Element or object to bind to.
2084   *
2085   * @param {string|string[]} type
2086   *        Name/type of event
2087   *
2088   * @param {Event~EventListener} fn
2089   *        Event listener function
2090   */
2091
2092  function one(elem, type, fn) {
2093    if (Array.isArray(type)) {
2094      return _handleMultipleEvents(one, elem, type, fn);
2095    }
2096
2097    var func = function func() {
2098      off(elem, type, func);
2099      fn.apply(this, arguments);
2100    }; // copy the guid to the new function so it can removed using the original function's ID
2101
2102
2103    func.guid = fn.guid = fn.guid || newGUID();
2104    on(elem, type, func);
2105  }
2106  /**
2107   * Trigger a listener only once and then turn if off for all
2108   * configured events
2109   *
2110   * @param {Element|Object} elem
2111   *        Element or object to bind to.
2112   *
2113   * @param {string|string[]} type
2114   *        Name/type of event
2115   *
2116   * @param {Event~EventListener} fn
2117   *        Event listener function
2118   */
2119
2120  function any(elem, type, fn) {
2121    var func = function func() {
2122      off(elem, type, func);
2123      fn.apply(this, arguments);
2124    }; // copy the guid to the new function so it can removed using the original function's ID
2125
2126
2127    func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything
2128
2129    on(elem, type, func);
2130  }
2131
2132  var Events = /*#__PURE__*/Object.freeze({
2133    fixEvent: fixEvent,
2134    on: on,
2135    off: off,
2136    trigger: trigger,
2137    one: one,
2138    any: any
2139  });
2140
2141  /**
2142   * @file fn.js
2143   * @module fn
2144   */
2145  var UPDATE_REFRESH_INTERVAL = 30;
2146  /**
2147   * Bind (a.k.a proxy or context). A simple method for changing the context of
2148   * a function.
2149   *
2150   * It also stores a unique id on the function so it can be easily removed from
2151   * events.
2152   *
2153   * @function
2154   * @param    {Mixed} context
2155   *           The object to bind as scope.
2156   *
2157   * @param    {Function} fn
2158   *           The function to be bound to a scope.
2159   *
2160   * @param    {number} [uid]
2161   *           An optional unique ID for the function to be set
2162   *
2163   * @return   {Function}
2164   *           The new function that will be bound into the context given
2165   */
2166
2167  var bind = function bind(context, fn, uid) {
2168    // Make sure the function has a unique ID
2169    if (!fn.guid) {
2170      fn.guid = newGUID();
2171    } // Create the new function that changes the context
2172
2173
2174    var bound = fn.bind(context); // Allow for the ability to individualize this function
2175    // Needed in the case where multiple objects might share the same prototype
2176    // IF both items add an event listener with the same function, then you try to remove just one
2177    // it will remove both because they both have the same guid.
2178    // when using this, you need to use the bind method when you remove the listener as well.
2179    // currently used in text tracks
2180
2181    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2182    return bound;
2183  };
2184  /**
2185   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2186   * at most once per every `wait` milliseconds.
2187   *
2188   * @function
2189   * @param    {Function} fn
2190   *           The function to be throttled.
2191   *
2192   * @param    {number}   wait
2193   *           The number of milliseconds by which to throttle.
2194   *
2195   * @return   {Function}
2196   */
2197
2198  var throttle = function throttle(fn, wait) {
2199    var last = window$1.performance.now();
2200
2201    var throttled = function throttled() {
2202      var now = window$1.performance.now();
2203
2204      if (now - last >= wait) {
2205        fn.apply(void 0, arguments);
2206        last = now;
2207      }
2208    };
2209
2210    return throttled;
2211  };
2212  /**
2213   * Creates a debounced function that delays invoking `func` until after `wait`
2214   * milliseconds have elapsed since the last time the debounced function was
2215   * invoked.
2216   *
2217   * Inspired by lodash and underscore implementations.
2218   *
2219   * @function
2220   * @param    {Function} func
2221   *           The function to wrap with debounce behavior.
2222   *
2223   * @param    {number} wait
2224   *           The number of milliseconds to wait after the last invocation.
2225   *
2226   * @param    {boolean} [immediate]
2227   *           Whether or not to invoke the function immediately upon creation.
2228   *
2229   * @param    {Object} [context=window]
2230   *           The "context" in which the debounced function should debounce. For
2231   *           example, if this function should be tied to a Video.js player,
2232   *           the player can be passed here. Alternatively, defaults to the
2233   *           global `window` object.
2234   *
2235   * @return   {Function}
2236   *           A debounced function.
2237   */
2238
2239  var debounce = function debounce(func, wait, immediate, context) {
2240    if (context === void 0) {
2241      context = window$1;
2242    }
2243
2244    var timeout;
2245
2246    var cancel = function cancel() {
2247      context.clearTimeout(timeout);
2248      timeout = null;
2249    };
2250    /* eslint-disable consistent-this */
2251
2252
2253    var debounced = function debounced() {
2254      var self = this;
2255      var args = arguments;
2256
2257      var _later = function later() {
2258        timeout = null;
2259        _later = null;
2260
2261        if (!immediate) {
2262          func.apply(self, args);
2263        }
2264      };
2265
2266      if (!timeout && immediate) {
2267        func.apply(self, args);
2268      }
2269
2270      context.clearTimeout(timeout);
2271      timeout = context.setTimeout(_later, wait);
2272    };
2273    /* eslint-enable consistent-this */
2274
2275
2276    debounced.cancel = cancel;
2277    return debounced;
2278  };
2279
2280  /**
2281   * @file src/js/event-target.js
2282   */
2283  /**
2284   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2285   * adds shorthand functions that wrap around lengthy functions. For example:
2286   * the `on` function is a wrapper around `addEventListener`.
2287   *
2288   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2289   * @class EventTarget
2290   */
2291
2292  var EventTarget = function EventTarget() {};
2293  /**
2294   * A Custom DOM event.
2295   *
2296   * @typedef {Object} EventTarget~Event
2297   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2298   */
2299
2300  /**
2301   * All event listeners should follow the following format.
2302   *
2303   * @callback EventTarget~EventListener
2304   * @this {EventTarget}
2305   *
2306   * @param {EventTarget~Event} event
2307   *        the event that triggered this function
2308   *
2309   * @param {Object} [hash]
2310   *        hash of data sent during the event
2311   */
2312
2313  /**
2314   * An object containing event names as keys and booleans as values.
2315   *
2316   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2317   *         will have extra functionality. See that function for more information.
2318   *
2319   * @property EventTarget.prototype.allowedEvents_
2320   * @private
2321   */
2322
2323
2324  EventTarget.prototype.allowedEvents_ = {};
2325  /**
2326   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2327   * function that will get called when an event with a certain name gets triggered.
2328   *
2329   * @param {string|string[]} type
2330   *        An event name or an array of event names.
2331   *
2332   * @param {EventTarget~EventListener} fn
2333   *        The function to call with `EventTarget`s
2334   */
2335
2336  EventTarget.prototype.on = function (type, fn) {
2337    // Remove the addEventListener alias before calling Events.on
2338    // so we don't get into an infinite type loop
2339    var ael = this.addEventListener;
2340
2341    this.addEventListener = function () {};
2342
2343    on(this, type, fn);
2344    this.addEventListener = ael;
2345  };
2346  /**
2347   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2348   * the standard DOM API.
2349   *
2350   * @function
2351   * @see {@link EventTarget#on}
2352   */
2353
2354
2355  EventTarget.prototype.addEventListener = EventTarget.prototype.on;
2356  /**
2357   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2358   * This makes it so that the `event listener` will no longer get called when the
2359   * named event happens.
2360   *
2361   * @param {string|string[]} type
2362   *        An event name or an array of event names.
2363   *
2364   * @param {EventTarget~EventListener} fn
2365   *        The function to remove.
2366   */
2367
2368  EventTarget.prototype.off = function (type, fn) {
2369    off(this, type, fn);
2370  };
2371  /**
2372   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2373   * the standard DOM API.
2374   *
2375   * @function
2376   * @see {@link EventTarget#off}
2377   */
2378
2379
2380  EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
2381  /**
2382   * This function will add an `event listener` that gets triggered only once. After the
2383   * first trigger it will get removed. This is like adding an `event listener`
2384   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2385   *
2386   * @param {string|string[]} type
2387   *        An event name or an array of event names.
2388   *
2389   * @param {EventTarget~EventListener} fn
2390   *        The function to be called once for each event name.
2391   */
2392
2393  EventTarget.prototype.one = function (type, fn) {
2394    // Remove the addEventListener aliasing Events.on
2395    // so we don't get into an infinite type loop
2396    var ael = this.addEventListener;
2397
2398    this.addEventListener = function () {};
2399
2400    one(this, type, fn);
2401    this.addEventListener = ael;
2402  };
2403
2404  EventTarget.prototype.any = function (type, fn) {
2405    // Remove the addEventListener aliasing Events.on
2406    // so we don't get into an infinite type loop
2407    var ael = this.addEventListener;
2408
2409    this.addEventListener = function () {};
2410
2411    any(this, type, fn);
2412    this.addEventListener = ael;
2413  };
2414  /**
2415   * This function causes an event to happen. This will then cause any `event listeners`
2416   * that are waiting for that event, to get called. If there are no `event listeners`
2417   * for an event then nothing will happen.
2418   *
2419   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2420   * Trigger will also call the `on` + `uppercaseEventName` function.
2421   *
2422   * Example:
2423   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2424   * `onClick` if it exists.
2425   *
2426   * @param {string|EventTarget~Event|Object} event
2427   *        The name of the event, an `Event`, or an object with a key of type set to
2428   *        an event name.
2429   */
2430
2431
2432  EventTarget.prototype.trigger = function (event) {
2433    var type = event.type || event; // deprecation
2434    // In a future version we should default target to `this`
2435    // similar to how we default the target to `elem` in
2436    // `Events.trigger`. Right now the default `target` will be
2437    // `document` due to the `Event.fixEvent` call.
2438
2439    if (typeof event === 'string') {
2440      event = {
2441        type: type
2442      };
2443    }
2444
2445    event = fixEvent(event);
2446
2447    if (this.allowedEvents_[type] && this['on' + type]) {
2448      this['on' + type](event);
2449    }
2450
2451    trigger(this, event);
2452  };
2453  /**
2454   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2455   * the standard DOM API.
2456   *
2457   * @function
2458   * @see {@link EventTarget#trigger}
2459   */
2460
2461
2462  EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
2463  var EVENT_MAP;
2464
2465  EventTarget.prototype.queueTrigger = function (event) {
2466    var _this = this;
2467
2468    // only set up EVENT_MAP if it'll be used
2469    if (!EVENT_MAP) {
2470      EVENT_MAP = new Map();
2471    }
2472
2473    var type = event.type || event;
2474    var map = EVENT_MAP.get(this);
2475
2476    if (!map) {
2477      map = new Map();
2478      EVENT_MAP.set(this, map);
2479    }
2480
2481    var oldTimeout = map.get(type);
2482    map["delete"](type);
2483    window$1.clearTimeout(oldTimeout);
2484    var timeout = window$1.setTimeout(function () {
2485      // if we cleared out all timeouts for the current target, delete its map
2486      if (map.size === 0) {
2487        map = null;
2488        EVENT_MAP["delete"](_this);
2489      }
2490
2491      _this.trigger(event);
2492    }, 0);
2493    map.set(type, timeout);
2494  };
2495
2496  /**
2497   * @file mixins/evented.js
2498   * @module evented
2499   */
2500  /**
2501   * Returns whether or not an object has had the evented mixin applied.
2502   *
2503   * @param  {Object} object
2504   *         An object to test.
2505   *
2506   * @return {boolean}
2507   *         Whether or not the object appears to be evented.
2508   */
2509
2510  var isEvented = function isEvented(object) {
2511    return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2512      return typeof object[k] === 'function';
2513    });
vendor: 4,139 bytes, lines 2514-2661
2514  };
2515  /**
2516   * Adds a callback to run after the evented mixin applied.
2517   *
2518   * @param  {Object} object
2519   *         An object to Add
2520   * @param  {Function} callback
2521   *         The callback to run.
2522   */
2523
2524
2525  var addEventedCallback = function addEventedCallback(target, callback) {
2526    if (isEvented(target)) {
2527      callback();
2528    } else {
2529      if (!target.eventedCallbacks) {
2530        target.eventedCallbacks = [];
2531      }
2532
2533      target.eventedCallbacks.push(callback);
2534    }
2535  };
2536  /**
2537   * Whether a value is a valid event type - non-empty string or array.
2538   *
2539   * @private
2540   * @param  {string|Array} type
2541   *         The type value to test.
2542   *
2543   * @return {boolean}
2544   *         Whether or not the type is a valid event type.
2545   */
2546
2547
2548  var isValidEventType = function isValidEventType(type) {
2549    return (// The regex here verifies that the `type` contains at least one non-
2550      // whitespace character.
2551      typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
2552    );
2553  };
2554  /**
2555   * Validates a value to determine if it is a valid event target. Throws if not.
2556   *
2557   * @private
2558   * @throws {Error}
2559   *         If the target does not appear to be a valid event target.
2560   *
2561   * @param  {Object} target
2562   *         The object to test.
2563   */
2564
2565
2566  var validateTarget = function validateTarget(target) {
2567    if (!target.nodeName && !isEvented(target)) {
2568      throw new Error('Invalid target; must be a DOM node or evented object.');
2569    }
2570  };
2571  /**
2572   * Validates a value to determine if it is a valid event target. Throws if not.
2573   *
2574   * @private
2575   * @throws {Error}
2576   *         If the type does not appear to be a valid event type.
2577   *
2578   * @param  {string|Array} type
2579   *         The type to test.
2580   */
2581
2582
2583  var validateEventType = function validateEventType(type) {
2584    if (!isValidEventType(type)) {
2585      throw new Error('Invalid event type; must be a non-empty string or array.');
2586    }
2587  };
2588  /**
2589   * Validates a value to determine if it is a valid listener. Throws if not.
2590   *
2591   * @private
2592   * @throws {Error}
2593   *         If the listener is not a function.
2594   *
2595   * @param  {Function} listener
2596   *         The listener to test.
2597   */
2598
2599
2600  var validateListener = function validateListener(listener) {
2601    if (typeof listener !== 'function') {
2602      throw new Error('Invalid listener; must be a function.');
2603    }
2604  };
2605  /**
2606   * Takes an array of arguments given to `on()` or `one()`, validates them, and
2607   * normalizes them into an object.
2608   *
2609   * @private
2610   * @param  {Object} self
2611   *         The evented object on which `on()` or `one()` was called. This
2612   *         object will be bound as the `this` value for the listener.
2613   *
2614   * @param  {Array} args
2615   *         An array of arguments passed to `on()` or `one()`.
2616   *
2617   * @return {Object}
2618   *         An object containing useful values for `on()` or `one()` calls.
2619   */
2620
2621
2622  var normalizeListenArgs = function normalizeListenArgs(self, args) {
2623    // If the number of arguments is less than 3, the target is always the
2624    // evented object itself.
2625    var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2626    var target;
2627    var type;
2628    var listener;
2629
2630    if (isTargetingSelf) {
2631      target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
2632      // the evented object itself.
2633
2634      if (args.length >= 3) {
2635        args.shift();
2636      }
2637
2638      type = args[0];
2639      listener = args[1];
2640    } else {
2641      target = args[0];
2642      type = args[1];
2643      listener = args[2];
2644    }
2645
2646    validateTarget(target);
2647    validateEventType(type);
2648    validateListener(listener);
2649    listener = bind(self, listener);
2650    return {
2651      isTargetingSelf: isTargetingSelf,
2652      target: target,
2653      type: type,
2654      listener: listener
2655    };
2656  };
2657  /**
2658   * Adds the listener to the event type(s) on the target, normalizing for
2659   * the type of target.
2660   *
2661   * @private
2662   * @param  {Element|Object} target
2663   *         A DOM node or evented object.
2664   *
2665   * @param  {string} method
2666   *         The event binding method to use ("on" or "one").
2667   *
2668   * @param  {string|Array} type
2669   *         One or more event type(s).
2670   *
2671   * @param  {Function} listener
2672   *         A listener function.
2673   */
2674
2675
2676  var listen = function listen(target, method, type, listener) {
2677    validateTarget(target);
2678
2679    if (target.nodeName) {
2680      Events[method](target, type, listener);
2681    } else {
2682      target[method](type, listener);
2683    }
2684  };
2685  /**
2686   * Contains methods that provide event capabilities to an object which is passed
2687   * to {@link module:evented|evented}.
2688   *
2689   * @mixin EventedMixin
2690   */
2691
2692
2693  var EventedMixin = {
2694    /**
2695     * Add a listener to an event (or events) on this object or another evented
2696     * object.
2697     *
2698     * @param  {string|Array|Element|Object} targetOrType
2699     *         If this is a string or array, it represents the event type(s)
2700     *         that will trigger the listener.
2701     *
2702     *         Another evented object can be passed here instead, which will
2703     *         cause the listener to listen for events on _that_ object.
2704     *
2705     *         In either case, the listener's `this` value will be bound to
2706     *         this object.
2707     *
2708     * @param  {string|Array|Function} typeOrListener
2709     *         If the first argument was a string or array, this should be the
2710     *         listener function. Otherwise, this is a string or array of event
2711     *         type(s).
2712     *
2713     * @param  {Function} [listener]
2714     *         If the first argument was another evented object, this will be
2715     *         the listener function.
2716     */
2717    on: function on() {
2718      var _this = this;
2719
2720      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
2721        args[_key] = arguments[_key];
2722      }
2723
2724      var _normalizeListenArgs = normalizeListenArgs(this, args),
2725          isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
2726          target = _normalizeListenArgs.target,
2727          type = _normalizeListenArgs.type,
2728          listener = _normalizeListenArgs.listener;
2729
2730      listen(target, 'on', type, listener); // If this object is listening to another evented object.
2731
2732      if (!isTargetingSelf) {
2733        // If this object is disposed, remove the listener.
2734        var removeListenerOnDispose = function removeListenerOnDispose() {
2735          return _this.off(target, type, listener);
2736        }; // Use the same function ID as the listener so we can remove it later it
2737        // using the ID of the original listener.
2738
2739
2740        removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
2741        // that if the target is disposed BEFORE this object, we remove the
2742        // removal listener that was just added. Otherwise, we create a memory leak.
2743
2744        var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
2745          return _this.off('dispose', removeListenerOnDispose);
2746        }; // Use the same function ID as the listener so we can remove it later
2747        // it using the ID of the original listener.
2748
2749
2750        removeRemoverOnTargetDispose.guid = listener.guid;
2751        listen(this, 'on', 'dispose', removeListenerOnDispose);
2752        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
2753      }
2754    },
2755
2756    /**
2757     * Add a listener to an event (or events) on this object or another evented
2758     * object. The listener will be called once per event and then removed.
2759     *
2760     * @param  {string|Array|Element|Object} targetOrType
2761     *         If this is a string or array, it represents the event type(s)
2762     *         that will trigger the listener.
2763     *
2764     *         Another evented object can be passed here instead, which will
2765     *         cause the listener to listen for events on _that_ object.
2766     *
2767     *         In either case, the listener's `this` value will be bound to
2768     *         this object.
2769     *
2770     * @param  {string|Array|Function} typeOrListener
2771     *         If the first argument was a string or array, this should be the
2772     *         listener function. Otherwise, this is a string or array of event
2773     *         type(s).
2774     *
2775     * @param  {Function} [listener]
2776     *         If the first argument was another evented object, this will be
2777     *         the listener function.
2778     */
2779    one: function one() {
2780      var _this2 = this;
2781
2782      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
2783        args[_key2] = arguments[_key2];
2784      }
2785
2786      var _normalizeListenArgs2 = normalizeListenArgs(this, args),
2787          isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
2788          target = _normalizeListenArgs2.target,
2789          type = _normalizeListenArgs2.type,
2790          listener = _normalizeListenArgs2.listener; // Targeting this evented object.
2791
2792
2793      if (isTargetingSelf) {
2794        listen(target, 'one', type, listener); // Targeting another evented object.
2795      } else {
2796        // TODO: This wrapper is incorrect! It should only
2797        //       remove the wrapper for the event type that called it.
2798        //       Instead all listners are removed on the first trigger!
2799        //       see https://github.com/videojs/video.js/issues/5962
2800        var wrapper = function wrapper() {
2801          _this2.off(target, type, wrapper);
2802
2803          for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
2804            largs[_key3] = arguments[_key3];
2805          }
2806
2807          listener.apply(null, largs);
2808        }; // Use the same function ID as the listener so we can remove it later
2809        // it using the ID of the original listener.
2810
2811
2812        wrapper.guid = listener.guid;
2813        listen(target, 'one', type, wrapper);
2814      }
2815    },
2816
2817    /**
2818     * Add a listener to an event (or events) on this object or another evented
2819     * object. The listener will only be called once for the first event that is triggered
2820     * then removed.
2821     *
2822     * @param  {string|Array|Element|Object} targetOrType
2823     *         If this is a string or array, it represents the event type(s)
2824     *         that will trigger the listener.
2825     *
2826     *         Another evented object can be passed here instead, which will
2827     *         cause the listener to listen for events on _that_ object.
2828     *
2829     *         In either case, the listener's `this` value will be bound to
2830     *         this object.
2831     *
2832     * @param  {string|Array|Function} typeOrListener
2833     *         If the first argument was a string or array, this should be the
2834     *         listener function. Otherwise, this is a string or array of event
2835     *         type(s).
2836     *
2837     * @param  {Function} [listener]
2838     *         If the first argument was another evented object, this will be
2839     *         the listener function.
2840     */
2841    any: function any() {
2842      var _this3 = this;
2843
2844      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
2845        args[_key4] = arguments[_key4];
2846      }
2847
2848      var _normalizeListenArgs3 = normalizeListenArgs(this, args),
2849          isTargetingSelf = _normalizeListenArgs3.isTargetingSelf,
2850          target = _normalizeListenArgs3.target,
2851          type = _normalizeListenArgs3.type,
2852          listener = _normalizeListenArgs3.listener; // Targeting this evented object.
2853
2854
2855      if (isTargetingSelf) {
2856        listen(target, 'any', type, listener); // Targeting another evented object.
2857      } else {
2858        var wrapper = function wrapper() {
2859          _this3.off(target, type, wrapper);
2860
2861          for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
2862            largs[_key5] = arguments[_key5];
2863          }
2864
2865          listener.apply(null, largs);
2866        }; // Use the same function ID as the listener so we can remove it later
2867        // it using the ID of the original listener.
2868
2869
2870        wrapper.guid = listener.guid;
2871        listen(target, 'any', type, wrapper);
2872      }
2873    },
2874
2875    /**
2876     * Removes listener(s) from event(s) on an evented object.
2877     *
2878     * @param  {string|Array|Element|Object} [targetOrType]
2879     *         If this is a string or array, it represents the event type(s).
2880     *
2881     *         Another evented object can be passed here instead, in which case
2882     *         ALL 3 arguments are _required_.
2883     *
2884     * @param  {string|Array|Function} [typeOrListener]
2885     *         If the first argument was a string or array, this may be the
2886     *         listener function. Otherwise, this is a string or array of event
2887     *         type(s).
2888     *
2889     * @param  {Function} [listener]
2890     *         If the first argument was another evented object, this will be
2891     *         the listener function; otherwise, _all_ listeners bound to the
2892     *         event type(s) will be removed.
2893     */
2894    off: function off$1(targetOrType, typeOrListener, listener) {
2895      // Targeting this evented object.
2896      if (!targetOrType || isValidEventType(targetOrType)) {
2897        off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
2898      } else {
2899        var target = targetOrType;
2900        var type = typeOrListener; // Fail fast and in a meaningful way!
2901
2902        validateTarget(target);
2903        validateEventType(type);
2904        validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
2905
2906        listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
2907        // the same guid as the event listener in on().
2908
2909        this.off('dispose', listener);
2910
2911        if (target.nodeName) {
2912          off(target, type, listener);
2913          off(target, 'dispose', listener);
2914        } else if (isEvented(target)) {
2915          target.off(type, listener);
2916          target.off('dispose', listener);
2917        }
2918      }
2919    },
2920
2921    /**
2922     * Fire an event on this evented object, causing its listeners to be called.
2923     *
2924     * @param   {string|Object} event
2925     *          An event type or an object with a type property.
2926     *
2927     * @param   {Object} [hash]
2928     *          An additional object to pass along to listeners.
2929     *
2930     * @return {boolean}
2931     *          Whether or not the default behavior was prevented.
2932     */
2933    trigger: function trigger$1(event, hash) {
2934      return trigger(this.eventBusEl_, event, hash);
2935    }
2936  };
2937  /**
2938   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
2939   *
2940   * @param  {Object} target
2941   *         The object to which to add event methods.
2942   *
2943   * @param  {Object} [options={}]
2944   *         Options for customizing the mixin behavior.
2945   *
2946   * @param  {string} [options.eventBusKey]
2947   *         By default, adds a `eventBusEl_` DOM element to the target object,
2948   *         which is used as an event bus. If the target object already has a
2949   *         DOM element that should be used, pass its key here.
2950   *
2951   * @return {Object}
2952   *         The target object.
2953   */
2954
2955  function evented(target, options) {
2956    if (options === void 0) {
2957      options = {};
2958    }
2959
2960    var _options = options,
2961        eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
2962
vendor: 4,933 bytes, lines 2963-3125
2963    if (eventBusKey) {
2964      if (!target[eventBusKey].nodeName) {
2965        throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
2966      }
2967
2968      target.eventBusEl_ = target[eventBusKey];
2969    } else {
2970      target.eventBusEl_ = createEl('span', {
2971        className: 'vjs-event-bus'
2972      });
2973    }
2974
2975    assign(target, EventedMixin);
2976
2977    if (target.eventedCallbacks) {
2978      target.eventedCallbacks.forEach(function (callback) {
2979        callback();
2980      });
2981    } // When any evented object is disposed, it removes all its listeners.
2982
2983
2984    target.on('dispose', function () {
2985      target.off();
2986      window$1.setTimeout(function () {
2987        target.eventBusEl_ = null;
2988      }, 0);
2989    });
2990    return target;
2991  }
2992
2993  /**
2994   * @file mixins/stateful.js
2995   * @module stateful
2996   */
2997  /**
2998   * Contains methods that provide statefulness to an object which is passed
2999   * to {@link module:stateful}.
3000   *
3001   * @mixin StatefulMixin
3002   */
3003
3004  var StatefulMixin = {
3005    /**
3006     * A hash containing arbitrary keys and values representing the state of
3007     * the object.
3008     *
3009     * @type {Object}
3010     */
3011    state: {},
3012
3013    /**
3014     * Set the state of an object by mutating its
3015     * {@link module:stateful~StatefulMixin.state|state} object in place.
3016     *
3017     * @fires   module:stateful~StatefulMixin#statechanged
3018     * @param   {Object|Function} stateUpdates
3019     *          A new set of properties to shallow-merge into the plugin state.
3020     *          Can be a plain object or a function returning a plain object.
3021     *
3022     * @return {Object|undefined}
3023     *          An object containing changes that occurred. If no changes
3024     *          occurred, returns `undefined`.
3025     */
3026    setState: function setState(stateUpdates) {
3027      var _this = this;
3028
3029      // Support providing the `stateUpdates` state as a function.
3030      if (typeof stateUpdates === 'function') {
3031        stateUpdates = stateUpdates();
3032      }
3033
3034      var changes;
3035      each(stateUpdates, function (value, key) {
3036        // Record the change if the value is different from what's in the
3037        // current state.
3038        if (_this.state[key] !== value) {
3039          changes = changes || {};
3040          changes[key] = {
3041            from: _this.state[key],
3042            to: value
3043          };
3044        }
3045
3046        _this.state[key] = value;
3047      }); // Only trigger "statechange" if there were changes AND we have a trigger
3048      // function. This allows us to not require that the target object be an
3049      // evented object.
3050
3051      if (changes && isEvented(this)) {
3052        /**
3053         * An event triggered on an object that is both
3054         * {@link module:stateful|stateful} and {@link module:evented|evented}
3055         * indicating that its state has changed.
3056         *
3057         * @event    module:stateful~StatefulMixin#statechanged
3058         * @type     {Object}
3059         * @property {Object} changes
3060         *           A hash containing the properties that were changed and
3061         *           the values they were changed `from` and `to`.
3062         */
3063        this.trigger({
3064          changes: changes,
3065          type: 'statechanged'
3066        });
3067      }
3068
3069      return changes;
3070    }
3071  };
3072  /**
3073   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3074   * object.
3075   *
3076   * If the target object is {@link module:evented|evented} and has a
3077   * `handleStateChanged` method, that method will be automatically bound to the
3078   * `statechanged` event on itself.
3079   *
3080   * @param   {Object} target
3081   *          The object to be made stateful.
3082   *
3083   * @param   {Object} [defaultState]
3084   *          A default set of properties to populate the newly-stateful object's
3085   *          `state` property.
3086   *
3087   * @return {Object}
3088   *          Returns the `target`.
3089   */
3090
3091  function stateful(target, defaultState) {
3092    assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
3093    // added in that step.
3094
3095    target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
3096
3097    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3098      target.on('statechanged', target.handleStateChanged);
3099    }
3100
3101    return target;
3102  }
3103
3104  /**
3105   * @file string-cases.js
3106   * @module to-lower-case
3107   */
3108
3109  /**
3110   * Lowercase the first letter of a string.
3111   *
3112   * @param {string} string
3113   *        String to be lowercased
3114   *
3115   * @return {string}
3116   *         The string with a lowercased first letter
3117   */
3118  var toLowerCase = function toLowerCase(string) {
3119    if (typeof string !== 'string') {
3120      return string;
3121    }
3122
3123    return string.replace(/./, function (w) {
3124      return w.toLowerCase();
3125    });
3126  };
3127  /**
3128   * Uppercase the first letter of a string.
3129   *
3130   * @param {string} string
3131   *        String to be uppercased
3132   *
3133   * @return {string}
3134   *         The string with an uppercased first letter
3135   */
3136
3137  var toTitleCase = function toTitleCase(string) {
3138    if (typeof string !== 'string') {
3139      return string;
3140    }
3141
3142    return string.replace(/./, function (w) {
3143      return w.toUpperCase();
3144    });
3145  };
3146  /**
3147   * Compares the TitleCase versions of the two strings for equality.
3148   *
3149   * @param {string} str1
3150   *        The first string to compare
3151   *
3152   * @param {string} str2
3153   *        The second string to compare
3154   *
3155   * @return {boolean}
3156   *         Whether the TitleCase versions of the strings are equal
3157   */
3158
3159  var titleCaseEquals = function titleCaseEquals(str1, str2) {
3160    return toTitleCase(str1) === toTitleCase(str2);
3161  };
3162
3163  /**
3164   * @file merge-options.js
3165   * @module merge-options
3166   */
3167  /**
3168   * Merge two objects recursively.
3169   *
3170   * Performs a deep merge like
3171   * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
3172   * plain objects (not arrays, elements, or anything else).
3173   *
3174   * Non-plain object values will be copied directly from the right-most
3175   * argument.
3176   *
3177   * @static
3178   * @param   {Object[]} sources
3179   *          One or more objects to merge into a new object.
3180   *
3181   * @return {Object}
3182   *          A new object that is the merged result of all sources.
3183   */
3184
3185  function mergeOptions() {
3186    var result = {};
3187
3188    for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
3189      sources[_key] = arguments[_key];
3190    }
3191
3192    sources.forEach(function (source) {
3193      if (!source) {
3194        return;
3195      }
3196
3197      each(source, function (value, key) {
3198        if (!isPlain(value)) {
3199          result[key] = value;
3200          return;
3201        }
3202
3203        if (!isPlain(result[key])) {
3204          result[key] = {};
3205        }
3206
3207        result[key] = mergeOptions(result[key], value);
3208      });
3209    });
3210    return result;
3211  }
3212
3213  /**
3214   * Player Component - Base class for all UI objects
3215   *
3216   * @file component.js
3217   */
3218  /**
3219   * Base class for all UI Components.
3220   * Components are UI objects which represent both a javascript object and an element
3221   * in the DOM. They can be children of other components, and can have
3222   * children themselves.
3223   *
3224   * Components can also use methods from {@link EventTarget}
3225   */
3226
3227  var Component =
3228  /*#__PURE__*/
3229  function () {
3230    /**
3231     * A callback that is called when a component is ready. Does not have any
3232     * paramters and any callback value will be ignored.
3233     *
3234     * @callback Component~ReadyCallback
3235     * @this Component
3236     */
3237
3238    /**
3239     * Creates an instance of this class.
3240     *
3241     * @param {Player} player
3242     *        The `Player` that this class should be attached to.
3243     *
3244     * @param {Object} [options]
3245     *        The key/value store of player options.
3246     *
3247     * @param {Object[]} [options.children]
3248     *        An array of children objects to intialize this component with. Children objects have
3249     *        a name property that will be used if more than one component of the same type needs to be
3250     *        added.
3251     *
3252     * @param {Component~ReadyCallback} [ready]
3253     *        Function that gets called when the `Component` is ready.
3254     */
3255    function Component(player, options, ready) {
3256      // The component might be the player itself and we can't pass `this` to super
3257      if (!player && this.play) {
3258        this.player_ = player = this; // eslint-disable-line
3259      } else {
3260        this.player_ = player;
3261      }
3262
3263      this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method
3264
3265      this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
3266
3267      this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
3268
3269      options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
3270
3271      this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
3272
3273      if (!this.id_) {
3274        // Don't require the player ID function in the case of mock players
3275        var id = player && player.id && player.id() || 'no_player';
3276        this.id_ = id + "_component_" + newGUID();
3277      }
3278
3279      this.name_ = options.name || null; // Create element if one wasn't provided in options
3280
3281      if (options.el) {
3282        this.el_ = options.el;
3283      } else if (options.createEl !== false) {
3284        this.el_ = this.createEl();
3285      } // if evented is anything except false, we want to mixin in evented
3286
3287
3288      if (options.evented !== false) {
3289        // Make this an evented object and use `el_`, if available, as its event bus
3290        evented(this, {
3291          eventBusKey: this.el_ ? 'el_' : null
3292        });
3293      }
3294
3295      stateful(this, this.constructor.defaultState);
3296      this.children_ = [];
3297      this.childIndex_ = {};
3298      this.childNameIndex_ = {};
3299      var SetSham;
3300
3301      if (!window$1.Set) {
3302        SetSham =
3303        /*#__PURE__*/
3304        function () {
3305          function SetSham() {
3306            this.set_ = {};
3307          }
3308
3309          var _proto2 = SetSham.prototype;
3310
3311          _proto2.has = function has(key) {
3312            return key in this.set_;
3313          };
3314
3315          _proto2["delete"] = function _delete(key) {
3316            var has = this.has(key);
3317            delete this.set_[key];
3318            return has;
3319          };
3320
3321          _proto2.add = function add(key) {
3322            this.set_[key] = 1;
3323            return this;
3324          };
3325
3326          _proto2.forEach = function forEach(callback, thisArg) {
3327            for (var key in this.set_) {
3328              callback.call(thisArg, key, key, this);
3329            }
3330          };
3331
3332          return SetSham;
3333        }();
3334      }
3335
3336      this.setTimeoutIds_ = window$1.Set ? new Set() : new SetSham();
3337      this.setIntervalIds_ = window$1.Set ? new Set() : new SetSham();
3338      this.rafIds_ = window$1.Set ? new Set() : new SetSham();
3339      this.clearingTimersOnDispose_ = false; // Add any child components in options
3340
3341      if (options.initChildren !== false) {
3342        this.initChildren();
3343      }
3344
3345      this.ready(ready); // Don't want to trigger ready here or it will before init is actually
3346      // finished for all children that run this constructor
3347
3348      if (options.reportTouchActivity !== false) {
3349        this.enableTouchActivity();
3350      }
3351    }
3352    /**
3353     * Dispose of the `Component` and all child components.
3354     *
3355     * @fires Component#dispose
3356     */
3357
3358
3359    var _proto = Component.prototype;
3360
3361    _proto.dispose = function dispose() {
3362      // Bail out if the component has already been disposed.
3363      if (this.isDisposed_) {
3364        return;
3365      }
3366      /**
3367       * Triggered when a `Component` is disposed.
3368       *
3369       * @event Component#dispose
3370       * @type {EventTarget~Event}
3371       *
3372       * @property {boolean} [bubbles=false]
3373       *           set to false so that the dispose event does not
3374       *           bubble up
3375       */
3376
3377
3378      this.trigger({
3379        type: 'dispose',
3380        bubbles: false
3381      });
3382      this.isDisposed_ = true; // Dispose all children.
3383
3384      if (this.children_) {
3385        for (var i = this.children_.length - 1; i >= 0; i--) {
3386          if (this.children_[i].dispose) {
3387            this.children_[i].dispose();
3388          }
3389        }
3390      } // Delete child references
3391
3392
3393      this.children_ = null;
3394      this.childIndex_ = null;
3395      this.childNameIndex_ = null;
3396      this.parentComponent_ = null;
3397
3398      if (this.el_) {
3399        // Remove element from DOM
3400        if (this.el_.parentNode) {
3401          this.el_.parentNode.removeChild(this.el_);
3402        }
3403
3404        if (DomData.has(this.el_)) {
3405          DomData["delete"](this.el_);
3406        }
3407
3408        this.el_ = null;
3409      } // remove reference to the player after disposing of the element
3410
3411
3412      this.player_ = null;
3413    }
3414    /**
3415     * Determine whether or not this component has been disposed.
3416     *
3417     * @return {boolean}
3418     *         If the component has been disposed, will be `true`. Otherwise, `false`.
3419     */
3420    ;
3421
3422    _proto.isDisposed = function isDisposed() {
3423      return Boolean(this.isDisposed_);
3424    }
3425    /**
3426     * Return the {@link Player} that the `Component` has attached to.
3427     *
3428     * @return {Player}
3429     *         The player that this `Component` has attached to.
3430     */
3431    ;
3432
3433    _proto.player = function player() {
3434      return this.player_;
3435    }
3436    /**
3437     * Deep merge of options objects with new options.
3438     * > Note: When both `obj` and `options` contain properties whose values are objects.
3439     *         The two properties get merged using {@link module:mergeOptions}
3440     *
3441     * @param {Object} obj
3442     *        The object that contains new options.
3443     *
3444     * @return {Object}
3445     *         A new object of `this.options_` and `obj` merged together.
3446     */
3447    ;
3448
3449    _proto.options = function options(obj) {
3450      if (!obj) {
3451        return this.options_;
3452      }
3453
3454      this.options_ = mergeOptions(this.options_, obj);
3455      return this.options_;
3456    }
3457    /**
3458     * Get the `Component`s DOM element
3459     *
3460     * @return {Element}
3461     *         The DOM element for this `Component`.
3462     */
3463    ;
3464
3465    _proto.el = function el() {
3466      return this.el_;
3467    }
3468    /**
3469     * Create the `Component`s DOM element.
3470     *
3471     * @param {string} [tagName]
3472     *        Element's DOM node type. e.g. 'div'
3473     *
3474     * @param {Object} [properties]
3475     *        An object of properties that should be set.
3476     *
3477     * @param {Object} [attributes]
3478     *        An object of attributes that should be set.
3479     *
3480     * @return {Element}
3481     *         The element that gets created.
3482     */
3483    ;
3484
3485    _proto.createEl = function createEl$1(tagName, properties, attributes) {
3486      return createEl(tagName, properties, attributes);
3487    }
3488    /**
3489     * Localize a string given the string in english.
3490     *
3491     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3492     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3493     *
3494     * If a `defaultValue` is provided, it'll use that over `string`,
3495     * if a value isn't found in provided language files.
3496     * This is useful if you want to have a descriptive key for token replacement
3497     * but have a succinct localized string and not require `en.json` to be included.
3498     *
3499     * Currently, it is used for the progress bar timing.
3500     * ```js
3501     * {
3502     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3503     * }
3504     * ```
3505     * It is then used like so:
3506     * ```js
3507     * this.localize('progress bar timing: currentTime={1} duration{2}',
3508     *               [this.player_.currentTime(), this.player_.duration()],
3509     *               '{1} of {2}');
3510     * ```
3511     *
3512     * Which outputs something like: `01:23 of 24:56`.
3513     *
3514     *
3515     * @param {string} string
3516     *        The string to localize and the key to lookup in the language files.
3517     * @param {string[]} [tokens]
3518     *        If the current item has token replacements, provide the tokens here.
3519     * @param {string} [defaultValue]
3520     *        Defaults to `string`. Can be a default value to use for token replacement
3521     *        if the lookup key is needed to be separate.
3522     *
3523     * @return {string}
3524     *         The localized string or if no localization exists the english string.
3525     */
3526    ;
3527
3528    _proto.localize = function localize(string, tokens, defaultValue) {
3529      if (defaultValue === void 0) {
3530        defaultValue = string;
3531      }
3532
3533      var code = this.player_.language && this.player_.language();
3534      var languages = this.player_.languages && this.player_.languages();
3535      var language = languages && languages[code];
3536      var primaryCode = code && code.split('-')[0];
3537      var primaryLang = languages && languages[primaryCode];
3538      var localizedString = defaultValue;
3539
3540      if (language && language[string]) {
3541        localizedString = language[string];
3542      } else if (primaryLang && primaryLang[string]) {
3543        localizedString = primaryLang[string];
3544      }
3545
3546      if (tokens) {
3547        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3548          var value = tokens[index - 1];
3549          var ret = value;
3550
3551          if (typeof value === 'undefined') {
3552            ret = match;
3553          }
3554
3555          return ret;
3556        });
3557      }
3558
3559      return localizedString;
3560    }
3561    /**
3562     * Return the `Component`s DOM element. This is where children get inserted.
3563     * This will usually be the the same as the element returned in {@link Component#el}.
3564     *
3565     * @return {Element}
3566     *         The content element for this `Component`.
3567     */
3568    ;
3569
3570    _proto.contentEl = function contentEl() {
3571      return this.contentEl_ || this.el_;
3572    }
3573    /**
3574     * Get this `Component`s ID
3575     *
3576     * @return {string}
3577     *         The id of this `Component`
3578     */
3579    ;
3580
3581    _proto.id = function id() {
3582      return this.id_;
3583    }
3584    /**
3585     * Get the `Component`s name. The name gets used to reference the `Component`
3586     * and is set during registration.
3587     *
3588     * @return {string}
3589     *         The name of this `Component`.
3590     */
3591    ;
3592
3593    _proto.name = function name() {
3594      return this.name_;
3595    }
3596    /**
3597     * Get an array of all child components
3598     *
3599     * @return {Array}
3600     *         The children
3601     */
3602    ;
3603
3604    _proto.children = function children() {
3605      return this.children_;
3606    }
3607    /**
3608     * Returns the child `Component` with the given `id`.
3609     *
3610     * @param {string} id
3611     *        The id of the child `Component` to get.
3612     *
3613     * @return {Component|undefined}
3614     *         The child `Component` with the given `id` or undefined.
3615     */
3616    ;
3617
3618    _proto.getChildById = function getChildById(id) {
3619      return this.childIndex_[id];
3620    }
3621    /**
3622     * Returns the child `Component` with the given `name`.
3623     *
3624     * @param {string} name
3625     *        The name of the child `Component` to get.
3626     *
3627     * @return {Component|undefined}
3628     *         The child `Component` with the given `name` or undefined.
3629     */
3630    ;
3631
3632    _proto.getChild = function getChild(name) {
3633      if (!name) {
3634        return;
3635      }
3636
3637      return this.childNameIndex_[name];
3638    }
3639    /**
3640     * Add a child `Component` inside the current `Component`.
3641     *
3642     *
3643     * @param {string|Component} child
3644     *        The name or instance of a child to add.
3645     *
3646     * @param {Object} [options={}]
3647     *        The key/value store of options that will get passed to children of
3648     *        the child.
3649     *
3650     * @param {number} [index=this.children_.length]
3651     *        The index to attempt to add a child into.
3652     *
3653     * @return {Component}
3654     *         The `Component` that gets added as a child. When using a string the
3655     *         `Component` will get created by this process.
3656     */
3657    ;
3658
3659    _proto.addChild = function addChild(child, options, index) {
3660      if (options === void 0) {
3661        options = {};
3662      }
3663
3664      if (index === void 0) {
3665        index = this.children_.length;
3666      }
3667
3668      var component;
3669      var componentName; // If child is a string, create component with options
3670
3671      if (typeof child === 'string') {
3672        componentName = toTitleCase(child);
3673        var componentClassName = options.componentClass || componentName; // Set name through options
3674
3675        options.name = componentName; // Create a new object & element for this controls set
3676        // If there's no .player_, this is a player
3677
3678        var ComponentClass = Component.getComponent(componentClassName);
3679
3680        if (!ComponentClass) {
3681          throw new Error("Component " + componentClassName + " does not exist");
3682        } // data stored directly on the videojs object may be
3683        // misidentified as a component to retain
3684        // backwards-compatibility with 4.x. check to make sure the
3685        // component class can be instantiated.
3686
3687
3688        if (typeof ComponentClass !== 'function') {
3689          return null;
3690        }
3691
3692        component = new ComponentClass(this.player_ || this, options); // child is a component instance
3693      } else {
3694        component = child;
3695      }
3696
3697      if (component.parentComponent_) {
3698        component.parentComponent_.removeChild(component);
3699      }
3700
3701      this.children_.splice(index, 0, component);
3702      component.parentComponent_ = this;
3703
3704      if (typeof component.id === 'function') {
3705        this.childIndex_[component.id()] = component;
3706      } // If a name wasn't used to create the component, check if we can use the
3707      // name function of the component
3708
3709
3710      componentName = componentName || component.name && toTitleCase(component.name());
3711
3712      if (componentName) {
3713        this.childNameIndex_[componentName] = component;
3714        this.childNameIndex_[toLowerCase(componentName)] = component;
3715      } // Add the UI object's element to the container div (box)
3716      // Having an element is not required
3717
3718
3719      if (typeof component.el === 'function' && component.el()) {
3720        // If inserting before a component, insert before that component's element
vendor: 9,093 bytes, lines 3721-4002
3721        var refNode = null;
3722
3723        if (this.children_[index + 1] && this.children_[index + 1].el_) {
3724          refNode = this.children_[index + 1].el_;
3725        }
3726
3727        this.contentEl().insertBefore(component.el(), refNode);
3728      } // Return so it can stored on parent object if desired.
3729
3730
3731      return component;
3732    }
3733    /**
3734     * Remove a child `Component` from this `Component`s list of children. Also removes
3735     * the child `Component`s element from this `Component`s element.
3736     *
3737     * @param {Component} component
3738     *        The child `Component` to remove.
3739     */
3740    ;
3741
3742    _proto.removeChild = function removeChild(component) {
3743      if (typeof component === 'string') {
3744        component = this.getChild(component);
3745      }
3746
3747      if (!component || !this.children_) {
3748        return;
3749      }
3750
3751      var childFound = false;
3752
3753      for (var i = this.children_.length - 1; i >= 0; i--) {
3754        if (this.children_[i] === component) {
3755          childFound = true;
3756          this.children_.splice(i, 1);
3757          break;
3758        }
3759      }
3760
3761      if (!childFound) {
3762        return;
3763      }
3764
3765      component.parentComponent_ = null;
3766      this.childIndex_[component.id()] = null;
3767      this.childNameIndex_[toTitleCase(component.name())] = null;
3768      this.childNameIndex_[toLowerCase(component.name())] = null;
3769      var compEl = component.el();
3770
3771      if (compEl && compEl.parentNode === this.contentEl()) {
3772        this.contentEl().removeChild(component.el());
3773      }
3774    }
3775    /**
3776     * Add and initialize default child `Component`s based upon options.
3777     */
3778    ;
3779
3780    _proto.initChildren = function initChildren() {
3781      var _this = this;
3782
3783      var children = this.options_.children;
3784
3785      if (children) {
3786        // `this` is `parent`
3787        var parentOptions = this.options_;
3788
3789        var handleAdd = function handleAdd(child) {
3790          var name = child.name;
3791          var opts = child.opts; // Allow options for children to be set at the parent options
3792          // e.g. videojs(id, { controlBar: false });
3793          // instead of videojs(id, { children: { controlBar: false });
3794
3795          if (parentOptions[name] !== undefined) {
3796            opts = parentOptions[name];
3797          } // Allow for disabling default components
3798          // e.g. options['children']['posterImage'] = false
3799
3800
3801          if (opts === false) {
3802            return;
3803          } // Allow options to be passed as a simple boolean if no configuration
3804          // is necessary.
3805
3806
3807          if (opts === true) {
3808            opts = {};
3809          } // We also want to pass the original player options
3810          // to each component as well so they don't need to
3811          // reach back into the player for options later.
3812
3813
3814          opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
3815          // Add a direct reference to the child by name on the parent instance.
3816          // If two of the same component are used, different names should be supplied
3817          // for each
3818
3819          var newChild = _this.addChild(name, opts);
3820
3821          if (newChild) {
3822            _this[name] = newChild;
3823          }
3824        }; // Allow for an array of children details to passed in the options
3825
3826
3827        var workingChildren;
3828        var Tech = Component.getComponent('Tech');
3829
3830        if (Array.isArray(children)) {
3831          workingChildren = children;
3832        } else {
3833          workingChildren = Object.keys(children);
3834        }
3835
3836        workingChildren // children that are in this.options_ but also in workingChildren  would
3837        // give us extra children we do not want. So, we want to filter them out.
3838        .concat(Object.keys(this.options_).filter(function (child) {
3839          return !workingChildren.some(function (wchild) {
3840            if (typeof wchild === 'string') {
3841              return child === wchild;
3842            }
3843
3844            return child === wchild.name;
3845          });
3846        })).map(function (child) {
3847          var name;
3848          var opts;
3849
3850          if (typeof child === 'string') {
3851            name = child;
3852            opts = children[name] || _this.options_[name] || {};
3853          } else {
3854            name = child.name;
3855            opts = child;
3856          }
3857
3858          return {
3859            name: name,
3860            opts: opts
3861          };
3862        }).filter(function (child) {
3863          // we have to make sure that child.name isn't in the techOrder since
3864          // techs are registerd as Components but can't aren't compatible
3865          // See https://github.com/videojs/video.js/issues/2772
3866          var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
3867          return c && !Tech.isTech(c);
3868        }).forEach(handleAdd);
3869      }
3870    }
3871    /**
3872     * Builds the default DOM class name. Should be overriden by sub-components.
3873     *
3874     * @return {string}
3875     *         The DOM class name for this object.
3876     *
3877     * @abstract
3878     */
3879    ;
3880
3881    _proto.buildCSSClass = function buildCSSClass() {
3882      // Child classes can include a function that does:
3883      // return 'CLASS NAME' + this._super();
3884      return '';
3885    }
3886    /**
3887     * Bind a listener to the component's ready state.
3888     * Different from event listeners in that if the ready event has already happened
3889     * it will trigger the function immediately.
3890     *
3891     * @return {Component}
3892     *         Returns itself; method can be chained.
3893     */
3894    ;
3895
3896    _proto.ready = function ready(fn, sync) {
3897      if (sync === void 0) {
3898        sync = false;
3899      }
3900
3901      if (!fn) {
3902        return;
3903      }
3904
3905      if (!this.isReady_) {
3906        this.readyQueue_ = this.readyQueue_ || [];
3907        this.readyQueue_.push(fn);
3908        return;
3909      }
3910
3911      if (sync) {
3912        fn.call(this);
3913      } else {
3914        // Call the function asynchronously by default for consistency
3915        this.setTimeout(fn, 1);
3916      }
3917    }
3918    /**
3919     * Trigger all the ready listeners for this `Component`.
3920     *
3921     * @fires Component#ready
3922     */
3923    ;
3924
3925    _proto.triggerReady = function triggerReady() {
3926      this.isReady_ = true; // Ensure ready is triggered asynchronously
3927
3928      this.setTimeout(function () {
3929        var readyQueue = this.readyQueue_; // Reset Ready Queue
3930
3931        this.readyQueue_ = [];
3932
3933        if (readyQueue && readyQueue.length > 0) {
3934          readyQueue.forEach(function (fn) {
3935            fn.call(this);
3936          }, this);
3937        } // Allow for using event listeners also
3938
3939        /**
3940         * Triggered when a `Component` is ready.
3941         *
3942         * @event Component#ready
3943         * @type {EventTarget~Event}
3944         */
3945
3946
3947        this.trigger('ready');
3948      }, 1);
3949    }
3950    /**
3951     * Find a single DOM element matching a `selector`. This can be within the `Component`s
3952     * `contentEl()` or another custom context.
3953     *
3954     * @param {string} selector
3955     *        A valid CSS selector, which will be passed to `querySelector`.
3956     *
3957     * @param {Element|string} [context=this.contentEl()]
3958     *        A DOM element within which to query. Can also be a selector string in
3959     *        which case the first matching element will get used as context. If
3960     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3961     *        nothing it falls back to `document`.
3962     *
3963     * @return {Element|null}
3964     *         the dom element that was found, or null
3965     *
3966     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3967     */
3968    ;
3969
3970    _proto.$ = function $$1(selector, context) {
3971      return $(selector, context || this.contentEl());
3972    }
3973    /**
3974     * Finds all DOM element matching a `selector`. This can be within the `Component`s
3975     * `contentEl()` or another custom context.
3976     *
3977     * @param {string} selector
3978     *        A valid CSS selector, which will be passed to `querySelectorAll`.
3979     *
3980     * @param {Element|string} [context=this.contentEl()]
3981     *        A DOM element within which to query. Can also be a selector string in
3982     *        which case the first matching element will get used as context. If
3983     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3984     *        nothing it falls back to `document`.
3985     *
3986     * @return {NodeList}
3987     *         a list of dom elements that were found
3988     *
3989     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3990     */
3991    ;
3992
3993    _proto.$$ = function $$$1(selector, context) {
3994      return $$(selector, context || this.contentEl());
3995    }
3996    /**
3997     * Check if a component's element has a CSS class name.
3998     *
3999     * @param {string} classToCheck
4000     *        CSS class name to check.
4001     *
4002     * @return {boolean}
4003     *         - True if the `Component` has the class.
4004     *         - False if the `Component` does not have the class`
4005     */
4006    ;
4007
4008    _proto.hasClass = function hasClass$1(classToCheck) {
4009      return hasClass(this.el_, classToCheck);
4010    }
4011    /**
4012     * Add a CSS class name to the `Component`s element.
4013     *
4014     * @param {string} classToAdd
4015     *        CSS class name to add
4016     */
4017    ;
4018
4019    _proto.addClass = function addClass$1(classToAdd) {
4020      addClass(this.el_, classToAdd);
4021    }
4022    /**
4023     * Remove a CSS class name from the `Component`s element.
4024     *
4025     * @param {string} classToRemove
4026     *        CSS class name to remove
4027     */
4028    ;
4029
4030    _proto.removeClass = function removeClass$1(classToRemove) {
4031      removeClass(this.el_, classToRemove);
4032    }
4033    /**
4034     * Add or remove a CSS class name from the component's element.
4035     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4036     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4037     *
4038     * @param  {string} classToToggle
4039     *         The class to add or remove based on (@link Component#hasClass}
4040     *
4041     * @param  {boolean|Dom~predicate} [predicate]
4042     *         An {@link Dom~predicate} function or a boolean
4043     */
4044    ;
4045
4046    _proto.toggleClass = function toggleClass$1(classToToggle, predicate) {
4047      toggleClass(this.el_, classToToggle, predicate);
4048    }
4049    /**
4050     * Show the `Component`s element if it is hidden by removing the
4051     * 'vjs-hidden' class name from it.
4052     */
4053    ;
4054
4055    _proto.show = function show() {
4056      this.removeClass('vjs-hidden');
4057    }
4058    /**
4059     * Hide the `Component`s element if it is currently showing by adding the
4060     * 'vjs-hidden` class name to it.
4061     */
4062    ;
4063
4064    _proto.hide = function hide() {
4065      this.addClass('vjs-hidden');
4066    }
4067    /**
4068     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4069     * class name to it. Used during fadeIn/fadeOut.
4070     *
4071     * @private
4072     */
4073    ;
4074
4075    _proto.lockShowing = function lockShowing() {
4076      this.addClass('vjs-lock-showing');
4077    }
4078    /**
4079     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4080     * class name from it. Used during fadeIn/fadeOut.
4081     *
4082     * @private
4083     */
4084    ;
4085
4086    _proto.unlockShowing = function unlockShowing() {
4087      this.removeClass('vjs-lock-showing');
4088    }
4089    /**
4090     * Get the value of an attribute on the `Component`s element.
4091     *
4092     * @param {string} attribute
4093     *        Name of the attribute to get the value from.
4094     *
4095     * @return {string|null}
4096     *         - The value of the attribute that was asked for.
4097     *         - Can be an empty string on some browsers if the attribute does not exist
4098     *           or has no value
4099     *         - Most browsers will return null if the attibute does not exist or has
4100     *           no value.
4101     *
4102     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4103     */
4104    ;
4105
4106    _proto.getAttribute = function getAttribute$1(attribute) {
4107      return getAttribute(this.el_, attribute);
4108    }
4109    /**
4110     * Set the value of an attribute on the `Component`'s element
4111     *
4112     * @param {string} attribute
4113     *        Name of the attribute to set.
4114     *
4115     * @param {string} value
4116     *        Value to set the attribute to.
4117     *
4118     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4119     */
4120    ;
4121
4122    _proto.setAttribute = function setAttribute$1(attribute, value) {
4123      setAttribute(this.el_, attribute, value);
4124    }
4125    /**
4126     * Remove an attribute from the `Component`s element.
4127     *
4128     * @param {string} attribute
4129     *        Name of the attribute to remove.
4130     *
4131     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4132     */
4133    ;
4134
4135    _proto.removeAttribute = function removeAttribute$1(attribute) {
4136      removeAttribute(this.el_, attribute);
4137    }
4138    /**
4139     * Get or set the width of the component based upon the CSS styles.
4140     * See {@link Component#dimension} for more detailed information.
4141     *
4142     * @param {number|string} [num]
4143     *        The width that you want to set postfixed with '%', 'px' or nothing.
4144     *
4145     * @param {boolean} [skipListeners]
4146     *        Skip the componentresize event trigger
4147     *
4148     * @return {number|string}
4149     *         The width when getting, zero if there is no width. Can be a string
4150     *           postpixed with '%' or 'px'.
4151     */
4152    ;
4153
4154    _proto.width = function width(num, skipListeners) {
4155      return this.dimension('width', num, skipListeners);
4156    }
4157    /**
4158     * Get or set the height of the component based upon the CSS styles.
4159     * See {@link Component#dimension} for more detailed information.
4160     *
4161     * @param {number|string} [num]
4162     *        The height that you want to set postfixed with '%', 'px' or nothing.
4163     *
4164     * @param {boolean} [skipListeners]
4165     *        Skip the componentresize event trigger
4166     *
4167     * @return {number|string}
4168     *         The width when getting, zero if there is no width. Can be a string
4169     *         postpixed with '%' or 'px'.
4170     */
4171    ;
4172
4173    _proto.height = function height(num, skipListeners) {
4174      return this.dimension('height', num, skipListeners);
4175    }
4176    /**
4177     * Set both the width and height of the `Component` element at the same time.
4178     *
4179     * @param  {number|string} width
4180     *         Width to set the `Component`s element to.
4181     *
4182     * @param  {number|string} height
4183     *         Height to set the `Component`s element to.
4184     */
4185    ;
4186
4187    _proto.dimensions = function dimensions(width, height) {
4188      // Skip componentresize listeners on width for optimization
4189      this.width(width, true);
4190      this.height(height);
4191    }
4192    /**
4193     * Get or set width or height of the `Component` element. This is the shared code
4194     * for the {@link Component#width} and {@link Component#height}.
4195     *
4196     * Things to know:
4197     * - If the width or height in an number this will return the number postfixed with 'px'.
4198     * - If the width/height is a percent this will return the percent postfixed with '%'
4199     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4200     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4201     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4201sing-width/}
4202     *   for more information
4203     * - If you want the computed style of the component, use {@link Component#currentWidth}
4204     *   and {@link {Component#currentHeight}
4205     *
4206     * @fires Component#componentresize
4207     *
4208     * @param {string} widthOrHeight
4209     8        'width' or 'height'
4210     *
4211     * @param  {number|string} [num]
4212     8         New dimension
4213     *
4214     * @param  {boolean} [skipListeners]
4215     *         Skip componentresize event trigger
4216     *
4217     * @return {number}
4218     *         The dimension when getting or 0 if unset
4219     */
4220    ;
4221
4222    _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
4223      if (num !== undefined) {
4224        // Set to zero if null or literally NaN (NaN !== NaN)
4225        if (num === null || num !== num) {
4226          num = 0;
4227        } // Check if using css width/height (% or px) and adjust
4228
4229
4230        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4231          this.el_.style[widthOrHeight] = num;
4232        } else if (num === 'auto') {
4233          this.el_.style[widthOrHeight] = '';
4234        } else {
4235          this.el_.style[widthOrHeight] = num + 'px';
4236        } // skipListeners allows us to avoid triggering the resize event when setting both width and height
4237
4238
4239        if (!skipListeners) {
4240          /**
4241           * Triggered when a component is resized.
4242           *
4243           * @event Component#componentresize
4244           * @type {EventTarget~Event}
4245           */
4246          this.trigger('componentresize');
4247        }
4248
4249        return;
4250      } // Not setting a value, so getting it
4251      // Make sure element exists
4252
4253
4254      if (!this.el_) {
4255        return 0;
4256      } // Get dimension value from style
4257
4258
4259      var val = this.el_.style[widthOrHeight];
4260      var pxIndex = val.indexOf('px');
4261
4262      if (pxIndex !== -1) {
4263        // Return the pixel value with no 'px'
4264        return parseInt(val.slice(0, pxIndex), 10);
4265      } // No px so using % or no style was set, so falling back to offsetWidth/height
4266      // If component has display:none, offset will return 0
4267      // TODO: handle display:none and no dimension style using px
4268
4269
4270      return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
4271    }
4272    /**
4273     * Get the computed width or the height of the component's element.
4274     *
4275     * Uses `window.getComputedStyle`.
4276     *
4277     * @param {string} widthOrHeight
4278     *        A string containing 'width' or 'height'. Whichever one you want to get.
4279     *
4280     * @return {number}
4281     *         The dimension that gets asked for or 0 if nothing was set
4282     *         for that dimension.
4283     */
4284    ;
4285
4286    _proto.currentDimension = function currentDimension(widthOrHeight) {
4287      var computedWidthOrHeight = 0;
4288
4289      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4290        throw new Error('currentDimension only accepts width or height value');
4291      }
4292
4293      computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer
4294
4295      computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
4296      // and we want to check the offset values.
4297      // This code also runs wherever getComputedStyle doesn't exist.
4298
4299      if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4300        var rule = "offset" + toTitleCase(widthOrHeight);
4301        computedWidthOrHeight = this.el_[rule];
4302      }
4303
4304      return computedWidthOrHeight;
4305    }
4306    /**
4307     * An object that contains width and height values of the `Component`s
4308     * computed style. Uses `window.getComputedStyle`.
4309     *
4310     * @typedef {Object} Component~DimensionObject
4311     *
4312     * @property {number} width
4313     *           The width of the `Component`s computed style.
4314     *
4315     * @property {number} height
4316     *           The height of the `Component`s computed style.
4317     */
4318
4319    /**
4320     * Get an object that contains computed width and height values of the
4321     * component's element.
4322     *
4323     * Uses `window.getComputedStyle`.
4324     *
4325     * @return {Component~DimensionObject}
4326     *         The computed dimensions of the component's element.
4327     */
4328    ;
4329
4330    _proto.currentDimensions = function currentDimensions() {
4331      return {
4332        width: this.currentDimension('width'),
4333        height: this.currentDimension('height')
4334      };
4335    }
4336    /**
4337     * Get the computed width of the component's element.
4338     *
4339     * Uses `window.getComputedStyle`.
4340     *
4341     * @return {number}
4342     *         The computed width of the component's element.
4343     */
4344    ;
4345
4346    _proto.currentWidth = function currentWidth() {
4347      return this.currentDimension('width');
4348    }
4349    /**
4350     * Get the computed height of the component's element.
4351     *
4352     * Uses `window.getComputedStyle`.
4353     *
4354     * @return {number}
4355     *         The computed height of the component's element.
4356     */
4357    ;
4358
4359    _proto.currentHeight = function currentHeight() {
4360      return this.currentDimension('height');
4361    }
4362    /**
4363     * Set the focus to this component
4364     */
4365    ;
4366
4367    _proto.focus = function focus() {
4368      this.el_.focus();
4369    }
4370    /**
4371     * Remove the focus from this component
4372     */
4373    ;
4374
4375    _proto.blur = function blur() {
4376      this.el_.blur();
4377    }
4378    /**
4379     * When this Component receives a `keydown` event which it does not process,
4380     *  it passes the event to the Player for handling.
4381     *
4382     * @param {EventTarget~Event} event
vendor: 10,781 bytes, lines 4383-4644
4383     *        The `keydown` event that caused this function to be called.
4384     */
4385    ;
4386
4387    _proto.handleKeyDown = function handleKeyDown(event) {
4388      if (this.player_) {
4389        // We only stop propagation here because we want unhandled events to fall
4390        // back to the browser.
4391        event.stopPropagation();
4392        this.player_.handleKeyDown(event);
4393      }
4394    }
4395    /**
4396     * Many components used to have a `handleKeyPress` method, which was poorly
4397     * named because it listened to a `keydown` event. This method name now
4398     * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4399     * will not see their method calls stop working.
4400     *
4401     * @param {EventTarget~Event} event
4402     *        The event that caused this function to be called.
4403     */
4404    ;
4405
4406    _proto.handleKeyPress = function handleKeyPress(event) {
4407      this.handleKeyDown(event);
4408    }
4409    /**
4410     * Emit a 'tap' events when touch event support gets detected. This gets used to
4411     * support toggling the controls through a tap on the video. They get enabled
4412     * because every sub-component would have extra overhead otherwise.
4413     *
4414     * @private
4415     * @fires Component#tap
4416     * @listens Component#touchstart
4417     * @listens Component#touchmove
4418     * @listens Component#touchleave
4419     * @listens Component#touchcancel
4420     * @listens Component#touchend
4421      */
4422    ;
4423
4424    _proto.emitTapEvents = function emitTapEvents() {
4425      // Track the start time so we can determine how long the touch lasted
4426      var touchStart = 0;
4427      var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
4428      // Other popular libs use anywhere from 2 (hammer.js) to 15,
4429      // so 10 seems like a nice, round number.
4430
4431      var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
4432
4433      var touchTimeThreshold = 200;
4434      var couldBeTap;
4435      this.on('touchstart', function (event) {
4436        // If more than one finger, don't consider treating this as a click
4437        if (event.touches.length === 1) {
4438          // Copy pageX/pageY from the object
4439          firstTouch = {
4440            pageX: event.touches[0].pageX,
4441            pageY: event.touches[0].pageY
4442          }; // Record start time so we can detect a tap vs. "touch and hold"
4443
4444          touchStart = window$1.performance.now(); // Reset couldBeTap tracking
4445
4446          couldBeTap = true;
4447        }
4448      });
4449      this.on('touchmove', function (event) {
4450        // If more than one finger, don't consider treating this as a click
4451        if (event.touches.length > 1) {
4452          couldBeTap = false;
4453        } else if (firstTouch) {
4454          // Some devices will throw touchmoves for all but the slightest of taps.
4455          // So, if we moved only a small distance, this could still be a tap
4456          var xdiff = event.touches[0].pageX - firstTouch.pageX;
4457          var ydiff = event.touches[0].pageY - firstTouch.pageY;
4458          var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4459
4460          if (touchDistance > tapMovementThreshold) {
4461            couldBeTap = false;
4462          }
4463        }
4464      });
4465
4466      var noTap = function noTap() {
4467        couldBeTap = false;
4468      }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4469
4470
4471      this.on('touchleave', noTap);
4472      this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
4473      // event
4474
4475      this.on('touchend', function (event) {
4476        firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
4477
4478        if (couldBeTap === true) {
4479          // Measure how long the touch lasted
4480          var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
4481
4482          if (touchTime < touchTimeThreshold) {
4483            // Don't let browser turn this into a click
4484            event.preventDefault();
4485            /**
4486             * Triggered when a `Component` is tapped.
4487             *
4488             * @event Component#tap
4489             * @type {EventTarget~Event}
4490             */
4491
4492            this.trigger('tap'); // It may be good to copy the touchend event object and change the
4493            // type to tap, if the other event properties aren't exact after
4494            // Events.fixEvent runs (e.g. event.target)
4495          }
4496        }
4497      });
4498    }
4499    /**
4500     * This function reports user activity whenever touch events happen. This can get
4501     * turned off by any sub-components that wants touch events to act another way.
4502     *
4503     * Report user touch activity when touch events occur. User activity gets used to
4504     * determine when controls should show/hide. It is simple when it comes to mouse
4505     * events, because any mouse event should show the controls. So we capture mouse
4506     * events that bubble up to the player and report activity when that happens.
4507     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4508     * controls. So touch events can't help us at the player level either.
4509     *
4510     * User activity gets checked asynchronously. So what could happen is a tap event
4511     * on the video turns the controls off. Then the `touchend` event bubbles up to
4512     * the player. Which, if it reported user activity, would turn the controls right
4513     * back on. We also don't want to completely block touch events from bubbling up.
4514     * Furthermore a `touchmove` event and anything other than a tap, should not turn
4515     * controls back on.
4516     *
4517     * @listens Component#touchstart
4518     * @listens Component#touchmove
4519     * @listens Component#touchend
4520     * @listens Component#touchcancel
4521     */
4522    ;
4523
4524    _proto.enableTouchActivity = function enableTouchActivity() {
4525      // Don't continue if the root player doesn't support reporting user activity
4526      if (!this.player() || !this.player().reportUserActivity) {
4527        return;
4528      } // listener for reporting that the user is active
4529
4530
4531      var report = bind(this.player(), this.player().reportUserActivity);
4532      var touchHolding;
4533      this.on('touchstart', function () {
4534        report(); // For as long as the they are touching the device or have their mouse down,
4535        // we consider them active even if they're not moving their finger or mouse.
4536        // So we want to continue to update that they are active
4537
4538        this.clearInterval(touchHolding); // report at the same interval as activityCheck
4539
4540        touchHolding = this.setInterval(report, 250);
4541      });
4542
4543      var touchEnd = function touchEnd(event) {
4544        report(); // stop the interval that maintains activity if the touch is holding
4545
4546        this.clearInterval(touchHolding);
4547      };
4548
4549      this.on('touchmove', report);
4550      this.on('touchend', touchEnd);
4551      this.on('touchcancel', touchEnd);
4552    }
4553    /**
4554     * A callback that has no parameters and is bound into `Component`s context.
4555     *
4556     * @callback Component~GenericCallback
4557     * @this Component
4558     */
4559
4560    /**
4561     * Creates a function that runs after an `x` millisecond timeout. This function is a
4562     * wrapper around `window.setTimeout`. There are a few reasons to use this one
4563     * instead though:
4564     * 1. It gets cleared via  {@link Component#clearTimeout} when
4565     *    {@link Component#dispose} gets called.
4566     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
4567     *
4568     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
4569     *         will cause its dispose listener not to get cleaned up! Please use
4570     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
4571     *
4572     * @param {Component~GenericCallback} fn
4573     *        The function that will be run after `timeout`.
4574     *
4575     * @param {number} timeout
4576     *        Timeout in milliseconds to delay before executing the specified function.
4577     *
4578     * @return {number}
4579     *         Returns a timeout ID that gets used to identify the timeout. It can also
4580     *         get used in {@link Component#clearTimeout} to clear the timeout that
4581     *         was set.
4582     *
4583     * @listens Component#dispose
4584     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
4585     */
4586    ;
4587
4588    _proto.setTimeout = function setTimeout(fn, timeout) {
4589      var _this2 = this;
4590
4591      // declare as variables so they are properly available in timeout function
4592      // eslint-disable-next-line
4593      var timeoutId;
4594      fn = bind(this, fn);
4595      this.clearTimersOnDispose_();
4596      timeoutId = window$1.setTimeout(function () {
4597        if (_this2.setTimeoutIds_.has(timeoutId)) {
4598          _this2.setTimeoutIds_["delete"](timeoutId);
4599        }
4600
4601        fn();
4602      }, timeout);
4603      this.setTimeoutIds_.add(timeoutId);
4604      return timeoutId;
4605    }
4606    /**
4607     * Clears a timeout that gets created via `window.setTimeout` or
4608     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4609     * use this function instead of `window.clearTimout`. If you don't your dispose
4610     * listener will not get cleaned up until {@link Component#dispose}!
4611     *
4612     * @param {number} timeoutId
4613     *        The id of the timeout to clear. The return value of
4614     *        {@link Component#setTimeout} or `window.setTimeout`.
4615     *
4616     * @return {number}
4617     *         Returns the timeout id that was cleared.
4618     *
4619     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4620     */
4621    ;
4622
4623    _proto.clearTimeout = function clearTimeout(timeoutId) {
4624      if (this.setTimeoutIds_.has(timeoutId)) {
4625        this.setTimeoutIds_["delete"](timeoutId);
4626        window$1.clearTimeout(timeoutId);
4627      }
4628
4629      return timeoutId;
4630    }
4631    /**
4632     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4633     * around `window.setInterval`. There are a few reasons to use this one instead though.
4634     * 1. It gets cleared via  {@link Component#clearInterval} when
4635     *    {@link Component#dispose} gets called.
4636     * 2. The function callback will be a {@link Component~GenericCallback}
4637     *
4638     * @param {Component~GenericCallback} fn
4639     *        The function to run every `x` seconds.
4640     *
4641     * @param {number} interval
4642     *        Execute the specified function every `x` milliseconds.
4643     *
4644     * @return {number}
4645     *         Returns an id that can be used to identify the interval. It can also be be used in
4646     *         {@link Component#clearInterval} to clear the interval.
4647     *
4648     * @listens Component#dispose
4649     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4650     */
4651    ;
4652
4653    _proto.setInterval = function setInterval(fn, interval) {
4654      fn = bind(this, fn);
4655      this.clearTimersOnDispose_();
4656      var intervalId = window$1.setInterval(fn, interval);
4657      this.setIntervalIds_.add(intervalId);
4658      return intervalId;
4659    }
4660    /**
4661     * Clears an interval that gets created via `window.setInterval` or
4662     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4663     * use this function instead of `window.clearInterval`. If you don't your dispose
4664     * listener will not get cleaned up until {@link Component#dispose}!
4665     *
4666     * @param {number} intervalId
4667     *        The id of the interval to clear. The return value of
4668     *        {@link Component#setInterval} or `window.setInterval`.
4669     *
4670     * @return {number}
4671     *         Returns the interval id that was cleared.
4672     *
4673     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4674     */
4675    ;
4676
4677    _proto.clearInterval = function clearInterval(intervalId) {
4678      if (this.setIntervalIds_.has(intervalId)) {
4679        this.setIntervalIds_["delete"](intervalId);
4680        window$1.clearInterval(intervalId);
4681      }
4682
4683      return intervalId;
4684    }
4685    /**
4686     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4687     * with a few extra bonuses:
4688     *
4689     * - Supports browsers that do not support rAF by falling back to
4690     *   {@link Component#setTimeout}.
4691     *
4692     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4693     *   bound to the component).
4694     *
4695     * - Automatic cancellation of the rAF callback is handled if the component
4696     *   is disposed before it is called.
4697     *
4698     * @param  {Component~GenericCallback} fn
4699     *         A function that will be bound to this component and executed just
4700     *         before the browser's next repaint.
4701     *
4702     * @return {number}
4703     *         Returns an rAF ID that gets used to identify the timeout. It can
4704     *         also be used in {@link Component#cancelAnimationFrame} to cancel
4705     *         the animation frame callback.
4706     *
4707     * @listens Component#dispose
4708     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4709     */
4710    ;
4711
4712    _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
4713      var _this3 = this;
4714
4715      // Fall back to using a timer.
4716      if (!this.supportsRaf_) {
4717        return this.setTimeout(fn, 1000 / 60);
4718      }
4719
4720      this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function
4721      // eslint-disable-next-line
4722
4723      var id;
4724      fn = bind(this, fn);
4725      id = window$1.requestAnimationFrame(function () {
4726        if (_this3.rafIds_.has(id)) {
4727          _this3.rafIds_["delete"](id);
4728        }
4729
4730        fn();
4731      });
4732      this.rafIds_.add(id);
4733      return id;
4734    }
4735    /**
4736     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4737     * (rAF).
4738     *
4739     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4740     * use this function instead of `window.cancelAnimationFrame`. If you don't,
4741     * your dispose listener will not get cleaned up until {@link Component#dispose}!
4742     *
4743     * @param {number} id
4744     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4745     *
4746     * @return {number}
4747     *         Returns the rAF ID that was cleared.
4748     *
4749     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4750     */
4751    ;
4752
4753    _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
4754      // Fall back to using a timer.
4755      if (!this.supportsRaf_) {
4756        return this.clearTimeout(id);
4757      }
4758
4759      if (this.rafIds_.has(id)) {
4760        this.rafIds_["delete"](id);
4761        window$1.cancelAnimationFrame(id);
4762      }
4763
4764      return id;
4765    }
4766    /**
4767     * A function to setup `requestAnimationFrame`, `setTimeout`,
4768     * and `setInterval`, clearing on dispose.
4769     *
4770     * > Previously each timer added and removed dispose listeners on it's own.
4771     * For better performance it was decided to batch them all, and use `Set`s
4772     * to track outstanding timer ids.
4773     *
4774     * @private
4775     */
4776    ;
4777
4778    _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() {
4779      var _this4 = this;
4780
4781      if (this.clearingTimersOnDispose_) {
4782        return;
4783      }
4784
4785      this.clearingTimersOnDispose_ = true;
4786      this.one('dispose', function () {
4787        [['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
4787h(function (_ref) {
4788          var idName = _ref[0],
4789              cancelName = _ref[1];
4790
4791          _this4[idName].forEach(_this4[cancelName], _this4);
4792        });
4793        _this4.clearingTimersOnDispose_ = false;
4794      });
4795    }
4796    /**
4797     * Register a `Component` with `videojs` given the name and the component.
4798     *
4799     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4800     *         should be registered using {@link Tech.registerTech} or
4801     *         {@link videojs:videojs.registerTech}.
4802     *
4803     * > NOTE: This function can also be seen on videojs as
4804     *         {@link videojs:videojs.registerComponent}.
4805     *
4806     * @param {string} name
4807     *        The name of the `Component` to register.
4808     *
4809     * @param {Component} ComponentToRegister
4810     *        The `Component` class to register.
4811     *
4812     * @return {Component}
4813     *         The `Component` that was registered.
4814     */
4815    ;
4816
4817    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4818      if (typeof name !== 'string' || !name) {
4819        throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
4820      }
4821
4822      var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
4823
4824      var isTech = Tech && Tech.isTech(ComponentToRegister);
4825      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4826
4827      if (isTech || !isComp) {
4828        var reason;
4829
4830        if (isTech) {
4831          reason = 'techs must be registered using Tech.registerTech()';
4832        } else {
4833          reason = 'must be a Component subclass';
4834        }
4835
4836        throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
4837      }
4838
4839      name = toTitleCase(name);
4840
4841      if (!Component.components_) {
4842        Component.components_ = {};
4843      }
4844
4845      var Player = Component.getComponent('Player');
4846
4847      if (name === 'Player' && Player && Player.players) {
4848        var players = Player.players;
4849        var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
4850        // in Players.players. So, we must loop through and verify that the value
4851        // for each item is not null. This allows registration of the Player component
4852        // after all players have been disposed or before any were created.
4853
4854        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4855          return players[pname];
4856        }).every(Boolean)) {
4857          throw new Error('Can not register Player component after player has been created.');
4858        }
4859      }
4860
4861      Component.components_[name] = ComponentToRegister;
4862      Component.components_[toLowerCase(name)] = ComponentToRegister;
4863      return ComponentToRegister;
4864    }
4865    /**
4866     * Get a `Component` based on the name it was registered with.
4867     *
4868     * @param {string} name
4869     *        The Name of the component to get.
4870     *
4871     * @return {Component}
4872     *         The `Component` that got registered under the given name.
4873     *
4874     * @deprecated In `videojs` 6 this will not return `Component`s that were not
4875     *             registered using {@link Component.registerComponent}. Currently we
4876     *             check the global `videojs` object for a `Component` name and
4877     *             return that if it exists.
4878     */
4879    ;
4880
4881    Component.getComponent = function getComponent(name) {
4882      if (!name || !Component.components_) {
4883        return;
4884      }
4885
4886      return Component.components_[name];
4887    };
4888
4889    return Component;
4890  }();
4891  /**
4892   * Whether or not this component supports `requestAnimationFrame`.
4893   *
4894   * This is exposed primarily for testing purposes.
4895   *
4896   * @private
4897   * @type {Boolean}
4898   */
4899
4900
4901  Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
4902  Component.registerComponent('Component', Component);
4903
4904  function _assertThisInitialized(self) {
4905    if (self === void 0) {
4906      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
4907    }
4908
4909    return self;
4910  }
4911
4912  var assertThisInitialized = _assertThisInitialized;
4913
4914  function _inheritsLoose(subClass, superClass) {
4915    subClass.prototype = Object.create(superClass.prototype);
4916    subClass.prototype.constructor = subClass;
4917    subClass.__proto__ = superClass;
4918  }
4919
4920  var inheritsLoose = _inheritsLoose;
4921
4922  /**
4923   * @file browser.js
4924   * @module browser
4925   */
4926  var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
vendor: 18,836 bytes, lines 4927-5567
4927  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
4928  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
4929  /**
4930   * Whether or not this device is an iPod.
4931   *
4932   * @static
4933   * @const
4934   * @type {Boolean}
4935   */
4936
4937  var IS_IPOD = /iPod/i.test(USER_AGENT);
4938  /**
4939   * The detected iOS version - or `null`.
4940   *
4941   * @static
4942   * @const
4943   * @type {string|null}
4944   */
4945
4946  var IOS_VERSION = function () {
4947    var match = USER_AGENT.match(/OS (\d+)_/i);
4948
4949    if (match && match[1]) {
4950      return match[1];
4951    }
4952
4953    return null;
4954  }();
4955  /**
4956   * Whether or not this is an Android device.
4957   *
4958   * @static
4959   * @const
4960   * @type {Boolean}
4961   */
4962
4963  var IS_ANDROID = /Android/i.test(USER_AGENT);
4964  /**
4965   * The detected Android version - or `null`.
4966   *
4967   * @static
4968   * @const
4969   * @type {number|string|null}
4970   */
4971
4972  var ANDROID_VERSION = function () {
4973    // This matches Android Major.Minor.Patch versions
4974    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
4975    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
4976
4977    if (!match) {
4978      return null;
4979    }
4980
4981    var major = match[1] && parseFloat(match[1]);
4982    var minor = match[2] && parseFloat(match[2]);
4983
4984    if (major && minor) {
4985      return parseFloat(match[1] + '.' + match[2]);
4986    } else if (major) {
4987      return major;
4988    }
4989
4990    return null;
4991  }();
4992  /**
4993   * Whether or not this is a native Android browser.
4994   *
4995   * @static
4996   * @const
4997   * @type {Boolean}
4998   */
4999
5000  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
5001  /**
5002   * Whether or not this is Mozilla Firefox.
5003   *
5004   * @static
5005   * @const
5006   * @type {Boolean}
5007   */
5008
5009  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
5010  /**
5011   * Whether or not this is Microsoft Edge.
5012   *
5013   * @static
5014   * @const
5015   * @type {Boolean}
5016   */
5017
5018  var IS_EDGE = /Edge/i.test(USER_AGENT);
5019  /**
5020   * Whether or not this is Google Chrome.
5021   *
5022   * This will also be `true` for Chrome on iOS, which will have different support
5023   * as it is actually Safari under the hood.
5024   *
5025   * @static
5026   * @const
5027   * @type {Boolean}
5028   */
5029
5030  var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
5031  /**
5032   * The detected Google Chrome version - or `null`.
5033   *
5034   * @static
5035   * @const
5036   * @type {number|null}
5037   */
5038
5039  var CHROME_VERSION = function () {
5040    var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
5041
5042    if (match && match[2]) {
5043      return parseFloat(match[2]);
5044    }
5045
5046    return null;
5047  }();
5048  /**
5049   * The detected Internet Explorer version - or `null`.
5050   *
5051   * @static
5052   * @const
5053   * @type {number|null}
5054   */
5055
5056  var IE_VERSION = function () {
5057    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
5058    var version = result && parseFloat(result[1]);
5059
5060    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
5061      // IE 11 has a different user agent string than other IE versions
5062      version = 11.0;
5063    }
5064
5065    return version;
5066  }();
5067  /**
5068   * Whether or not this is desktop Safari.
5069   *
5070   * @static
5071   * @const
5072   * @type {Boolean}
5073   */
5074
5075  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
5076  /**
5077   * Whether or not this is a Windows machine.
5078   *
5079   * @static
5080   * @const
5081   * @type {Boolean}
5082   */
5083
5084  var IS_WINDOWS = /Windows/i.test(USER_AGENT);
5085  /**
5086   * Whether or not this device is touch-enabled.
5087   *
5088   * @static
5089   * @const
5090   * @type {Boolean}
5091   */
5092
5093  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
5094  /**
5095   * Whether or not this device is an iPad.
5096   *
5097   * @static
5098   * @const
5099   * @type {Boolean}
5100   */
5101
5102  var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
5103  /**
5104   * Whether or not this device is an iPhone.
5105   *
5106   * @static
5107   * @const
5108   * @type {Boolean}
5109   */
5110  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
5111  // to identify iPhones, we need to exclude iPads.
5112  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
5113
5114  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
5115  /**
5116   * Whether or not this is an iOS device.
5117   *
5118   * @static
5119   * @const
5120   * @type {Boolean}
5121   */
5122
5123  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
5124  /**
5125   * Whether or not this is any flavor of Safari - including iOS.
5126   *
5127   * @static
5128   * @const
5129   * @type {Boolean}
5130   */
5131
5132  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
5133
5134  var browser = /*#__PURE__*/Object.freeze({
5135    IS_IPOD: IS_IPOD,
5136    IOS_VERSION: IOS_VERSION,
5137    IS_ANDROID: IS_ANDROID,
5138    ANDROID_VERSION: ANDROID_VERSION,
5139    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
5140    IS_FIREFOX: IS_FIREFOX,
5141    IS_EDGE: IS_EDGE,
5142    IS_CHROME: IS_CHROME,
5143    CHROME_VERSION: CHROME_VERSION,
5144    IE_VERSION: IE_VERSION,
5145    IS_SAFARI: IS_SAFARI,
5146    IS_WINDOWS: IS_WINDOWS,
5147    TOUCH_ENABLED: TOUCH_ENABLED,
5148    IS_IPAD: IS_IPAD,
5149    IS_IPHONE: IS_IPHONE,
5150    IS_IOS: IS_IOS,
5151    IS_ANY_SAFARI: IS_ANY_SAFARI
5152  });
5153
5154  /**
5155   * @file time-ranges.js
5156   * @module time-ranges
5157   */
5158
5159  /**
5160   * Returns the time for the specified index at the start or end
5161   * of a TimeRange object.
5162   *
5163   * @typedef    {Function} TimeRangeIndex
5164   *
5165   * @param      {number} [index=0]
5166   *             The range number to return the time for.
5167   *
5168   * @return     {number}
5169   *             The time offset at the specified index.
5170   *
5171   * @deprecated The index argument must be provided.
5172   *             In the future, leaving it out will throw an error.
5173   */
5174
5175  /**
5176   * An object that contains ranges of time.
5177   *
5178   * @typedef  {Object} TimeRange
5179   *
5180   * @property {number} length
5181   *           The number of time ranges represented by this object.
5182   *
5183   * @property {module:time-ranges~TimeRangeIndex} start
5184   *           Returns the time offset at which a specified time range begins.
5185   *
5186   * @property {module:time-ranges~TimeRangeIndex} end
5187   *           Returns the time offset at which a specified time range ends.
5188   *
5189   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5190   */
5191
5192  /**
5193   * Check if any of the time ranges are over the maximum index.
5194   *
5195   * @private
5196   * @param   {string} fnName
5197   *          The function name to use for logging
5198   *
5199   * @param   {number} index
5200   *          The index to check
5201   *
5202   * @param   {number} maxIndex
5203   *          The maximum possible index
5204   *
5205   * @throws  {Error} if the timeRanges provided are over the maxIndex
5206   */
5207  function rangeCheck(fnName, index, maxIndex) {
5208    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5209      throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
5210    }
5211  }
5212  /**
5213   * Get the time for the specified index at the start or end
5214   * of a TimeRange object.
5215   *
5216   * @private
5217   * @param      {string} fnName
5218   *             The function name to use for logging
5219   *
5220   * @param      {string} valueIndex
5221   *             The property that should be used to get the time. should be
5222   *             'start' or 'end'
5223   *
5224   * @param      {Array} ranges
5225   *             An array of time ranges
5226   *
5227   * @param      {Array} [rangeIndex=0]
5228   *             The index to start the search at
5229   *
5230   * @return     {number}
5231   *             The time that offset at the specified index.
5232   *
5233   * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5234   * @throws     {Error} if rangeIndex is more than the length of ranges
5235   */
5236
5237
5238  function getRange(fnName, valueIndex, ranges, rangeIndex) {
5239    rangeCheck(fnName, rangeIndex, ranges.length - 1);
5240    return ranges[rangeIndex][valueIndex];
5241  }
5242  /**
5243   * Create a time range object given ranges of time.
5244   *
5245   * @private
5246   * @param   {Array} [ranges]
5247   *          An array of time ranges.
5248   */
5249
5250
5251  function createTimeRangesObj(ranges) {
5252    if (ranges === undefined || ranges.length === 0) {
5253      return {
5254        length: 0,
5255        start: function start() {
5256          throw new Error('This TimeRanges object is empty');
5257        },
5258        end: function end() {
5259          throw new Error('This TimeRanges object is empty');
5260        }
5261      };
5262    }
5263
5264    return {
5265      length: ranges.length,
5266      start: getRange.bind(null, 'start', 0, ranges),
5267      end: getRange.bind(null, 'end', 1, ranges)
5268    };
5269  }
5270  /**
5271   * Create a `TimeRange` object which mimics an
5272   * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5273   *
5274   * @param {number|Array[]} start
5275   *        The start of a single range (a number) or an array of ranges (an
5276   *        array of arrays of two numbers each).
5277   *
5278   * @param {number} end
5279   *        The end of a single range. Cannot be used with the array form of
5280   *        the `start` argument.
5281   */
5282
5283
5284  function createTimeRanges(start, end) {
5285    if (Array.isArray(start)) {
5286      return createTimeRangesObj(start);
5287    } else if (start === undefined || end === undefined) {
5288      return createTimeRangesObj();
5289    }
5290
5291    return createTimeRangesObj([[start, end]]);
5292  }
5293
5294  /**
5295   * @file buffer.js
5296   * @module buffer
5297   */
5298  /**
5299   * Compute the percentage of the media that has been buffered.
5300   *
5301   * @param {TimeRange} buffered
5302   *        The current `TimeRange` object representing buffered time ranges
5303   *
5304   * @param {number} duration
5305   *        Total duration of the media
5306   *
5307   * @return {number}
5308   *         Percent buffered of the total duration in decimal form.
5309   */
5310
5311  function bufferedPercent(buffered, duration) {
5312    var bufferedDuration = 0;
5313    var start;
5314    var end;
5315
5316    if (!duration) {
5317      return 0;
5318    }
5319
5320    if (!buffered || !buffered.length) {
5321      buffered = createTimeRanges(0, 0);
5322    }
5323
5324    for (var i = 0; i < buffered.length; i++) {
5325      start = buffered.start(i);
5326      end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
5327
5328      if (end > duration) {
5329        end = duration;
5330      }
5331
5332      bufferedDuration += end - start;
5333    }
5334
5335    return bufferedDuration / duration;
5336  }
5337
5338  /**
5339   * @file fullscreen-api.js
5340   * @module fullscreen-api
5341   * @private
5342   */
5343  /**
5344   * Store the browser-specific methods for the fullscreen API.
5345   *
5346   * @type {Object}
5347   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
5348   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
5349   */
5350
5351  var FullscreenApi = {
5352    prefixed: true
5353  }; // browser API methods
5354
5355  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
5356  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
5357  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
5358  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
5359  var specApi = apiMap[0];
5360  var browserApi; // determine the supported set of functions
5361
5362  for (var i = 0; i < apiMap.length; i++) {
5363    // check for exitFullscreen function
5364    if (apiMap[i][1] in document) {
5365      browserApi = apiMap[i];
5366      break;
5367    }
5368  } // map the browser API names to the spec API names
5369
5370
5371  if (browserApi) {
5372    for (var _i = 0; _i < browserApi.length; _i++) {
5373      FullscreenApi[specApi[_i]] = browserApi[_i];
5374    }
5375
5376    FullscreenApi.prefixed = browserApi[0] !== specApi[0];
5377  }
5378
5379  /**
5380   * @file media-error.js
5381   */
5382  /**
5383   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5384   *
5385   * @param {number|string|Object|MediaError} value
5386   *        This can be of multiple types:
5387   *        - number: should be a standard error code
5388   *        - string: an error message (the code will be 0)
5389   *        - Object: arbitrary properties
5390   *        - `MediaError` (native): used to populate a video.js `MediaError` object
5391   *        - `MediaError` (video.js): will return itself if it's already a
5392   *          video.js `MediaError` object.
5393   *
5394   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5395   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5396   *
5397   * @class MediaError
5398   */
5399
5400  function MediaError(value) {
5401    // Allow redundant calls to this constructor to avoid having `instanceof`
5402    // checks peppered around the code.
5403    if (value instanceof MediaError) {
5404      return value;
5405    }
5406
5407    if (typeof value === 'number') {
5408      this.code = value;
5409    } else if (typeof value === 'string') {
5410      // default code is zero, so this is a custom error
5411      this.message = value;
5412    } else if (isObject(value)) {
5413      // We assign the `code` property manually because native `MediaError` objects
5414      // do not expose it as an own/enumerable property of the object.
5415      if (typeof value.code === 'number') {
5416        this.code = value.code;
5417      }
5418
5419      assign(this, value);
5420    }
5421
5422    if (!this.message) {
5423      this.message = MediaError.defaultMessages[this.code] || '';
5424    }
5425  }
5426  /**
5427   * The error code that refers two one of the defined `MediaError` types
5428   *
5429   * @type {Number}
5430   */
5431
5432
5433  MediaError.prototype.code = 0;
5434  /**
5435   * An optional message that to show with the error. Message is not part of the HTML5
5436   * video spec but allows for more informative custom errors.
5437   *
5438   * @type {String}
5439   */
5440
5441  MediaError.prototype.message = '';
5442  /**
5443   * An optional status code that can be set by plugins to allow even more detail about
5444   * the error. For example a plugin might provide a specific HTTP status code and an
5445   * error message for that code. Then when the plugin gets that error this class will
5446   * know how to display an error message for it. This allows a custom message to show
5447   * up on the `Player` error overlay.
5448   *
5449   * @type {Array}
5450   */
5451
5452  MediaError.prototype.status = null;
5453  /**
5454   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5455   * specification listed under {@link MediaError} for more information.
5456   *
5457   * @enum {array}
5458   * @readonly
5459   * @property {string} 0 - MEDIA_ERR_CUSTOM
5460   * @property {string} 1 - MEDIA_ERR_ABORTED
5461   * @property {string} 2 - MEDIA_ERR_NETWORK
5462   * @property {string} 3 - MEDIA_ERR_DECODE
5463   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5464   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5465   */
5466
5467  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5468  /**
5469   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5470   *
5471   * @type {Array}
5472   * @constant
5473   */
5474
5475  MediaError.defaultMessages = {
5476    1: 'You aborted the media playback',
5477    2: 'A network error caused the media download to fail part-way.',
5478    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5479    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5480    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5481  }; // Add types as properties on MediaError
5482  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5483
5484  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5485    MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
5486
5487    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5488  } // jsdocs for instance/static members added above
5489
5490  var tuple = SafeParseTuple;
5491
5492  function SafeParseTuple(obj, reviver) {
5493    var json;
5494    var error = null;
5495
5496    try {
5497      json = JSON.parse(obj, reviver);
5498    } catch (err) {
5499      error = err;
5500    }
5501
5502    return [error, json];
5503  }
5504
5505  /**
5506   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5507   *
5508   * @param  {Object}  value
5509   *         An object that may or may not be `Promise`-like.
5510   *
5511   * @return {boolean}
5512   *         Whether or not the object is `Promise`-like.
5513   */
5514  function isPromise(value) {
5515    return value !== undefined && value !== null && typeof value.then === 'function';
5516  }
5517  /**
5518   * Silence a Promise-like object.
5519   *
5520   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5521   * play promise" rejection error messages.
5522   *
5523   * @param  {Object} value
5524   *         An object that may or may not be `Promise`-like.
5525   */
5526
5527  function silencePromise(value) {
5528    if (isPromise(value)) {
5529      value.then(null, function (e) {});
5530    }
5531  }
5532
5533  /**
5534   * @file text-track-list-converter.js Utilities for capturing text track state and
5535   * re-creating tracks based on a capture.
5536   *
5537   * @module text-track-list-converter
5538   */
5539
5540  /**
5541   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5542   * represents the {@link TextTrack}'s state.
5543   *
5544   * @param {TextTrack} track
5545   *        The text track to query.
5546   *
5547   * @return {Object}
5548   *         A serializable javascript representation of the TextTrack.
5549   * @private
5550   */
5551  var trackToJson_ = function trackToJson_(track) {
5552    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5553      if (track[prop]) {
5554        acc[prop] = track[prop];
5555      }
5556
5557      return acc;
5558    }, {
5559      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5560        return {
5561          startTime: cue.startTime,
5562          endTime: cue.endTime,
5563          text: cue.text,
5564          id: cue.id
5565        };
5566      })
5567    });
5568    return ret;
5569  };
5570  /**
5571   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5572   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5573   * {@link text-track-list-converter:jsonToTextTracks}.
5574   *
5575   * @param {Tech} tech
5576   *        The tech object to query
5577   *
5578   * @return {Array}
5579   *         A serializable javascript representation of the {@link Tech}s
5580   *         {@link TextTrackList}.
5581   */
5582
5583
5584  var textTracksToJson = function textTracksToJson(tech) {
5585    var trackEls = tech.$$('track');
5586    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5587      return t.track;
5588    });
5589    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5590      var json = trackToJson_(trackEl.track);
5591
5592      if (trackEl.src) {
5593        json.src = trackEl.src;
5594      }
5595
5596      return json;
5597    });
5598    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5599      return trackObjs.indexOf(track) === -1;
5600    }).map(trackToJson_));
5601  };
5602  /**
5603   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5604   * object {@link TextTrack} representations.
5605   *
5606   * @param {Array} json
5607   *        An array of `TextTrack` representation objects, like those that would be
5608   *        produced by `textTracksToJson`.
5609   *
5610   * @param {Tech} tech
5611   *        The `Tech` to create the `TextTrack`s on.
5612   */
5613
5614
5615  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5616    json.forEach(function (track) {
5617      var addedTrack = tech.addRemoteTextTrack(track).track;
5618
5619      if (!track.src && track.cues) {
5620        track.cues.forEach(function (cue) {
5621          return addedTrack.addCue(cue);
5622        });
5623      }
5624    });
5625    return tech.textTracks();
5626  };
5627
5628  var textTrackConverter = {
5629    textTracksToJson: textTracksToJson,
5630    jsonToTextTracks: jsonToTextTracks,
5631    trackToJson_: trackToJson_
5632  };
5633
5634  var keycode = createCommonjsModule(function (module, exports) {
5635    // Source: http://jsfiddle.net/vWx8V/
5636    // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
5637
5638    /**
5639     * Conenience method returns corresponding value for given keyName or keyCode.
5640     *
5641     * @param {Mixed} keyCode {Number} or keyName {String}
5642     * @return {Mixed}
5643     * @api public
5644     */
5645    function keyCode(searchInput) {
5646      // Keyboard Events
5647      if (searchInput && 'object' === typeof searchInput) {
5648        var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
5649        if (hasKeyCode) searchInput = hasKeyCode;
5650      } // Numbers
5651
5652
5653      if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
5654
5655      var search = String(searchInput); // check codes
5656
5657      var foundNamedKey = codes[search.toLowerCase()];
5658      if (foundNamedKey) return foundNamedKey; // check aliases
5659
5660      var foundNamedKey = aliases[search.toLowerCase()];
5661      if (foundNamedKey) return foundNamedKey; // weird character?
5662
5663      if (search.length === 1) return search.charCodeAt(0);
5664      return undefined;
5665    }
5666    /**
5667     * Compares a keyboard event with a given keyCode or keyName.
5668     *
5669     * @param {Event} event Keyboard event that should be tested
5670     * @param {Mixed} keyCode {Number} or keyName {String}
5671     * @return {Boolean}
5672     * @api public
5673     */
5674
5675
5676    keyCode.isEventKey = function isEventKey(event, nameOrCode) {
5677      if (event && 'object' === typeof event) {
5678        var keyCode = event.which || event.keyCode || event.charCode;
5679
5680        if (keyCode === null || keyCode === undefined) {
5681          return false;
5682        }
5683
5684        if (typeof nameOrCode === 'string') {
5685          // check codes
5686          var foundNamedKey = codes[nameOrCode.toLowerCase()];
5687
5688          if (foundNamedKey) {
5689            return foundNamedKey === keyCode;
5690          } // check aliases
5691
5692
5693          var foundNamedKey = aliases[nameOrCode.toLowerCase()];
5694
5695          if (foundNamedKey) {
5696            return foundNamedKey === keyCode;
5697          }
5698        } else if (typeof nameOrCode === 'number') {
5699          return nameOrCode === keyCode;
5700        }
5701
5702        return false;
5703      }
5704    };
5705
5706    exports = module.exports = keyCode;
5707    /**
5708     * Get by name
5709     *
5710     *   exports.code['enter'] // => 13
5711     */
5712
5713    var codes = exports.code = exports.codes = {
5714      'backspace': 8,
5715      'tab': 9,
5716      'enter': 13,
5717      'shift': 16,
5718      'ctrl': 17,
5719      'alt': 18,
5720      'pause/break': 19,
5721      'caps lock': 20,
5722      'esc': 27,
5723      'space': 32,
5724      'page up': 33,
5725      'page down': 34,
5726      'end': 35,
5727      'home': 36,
5728      'left': 37,
5729      'up': 38,
5730      'right': 39,
5731      'down': 40,
5732      'insert': 45,
5733      'delete': 46,
5734      'command': 91,
5735      'left command': 91,
5736      'right command': 93,
5737      'numpad *': 106,
5738      'numpad +': 107,
5739      'numpad -': 109,
5740      'numpad .': 110,
5741      'numpad /': 111,
5742      'num lock': 144,
5743      'scroll lock': 145,
5744      'my computer': 182,
5745      'my calculator': 183,
5746      ';': 186,
5747      '=': 187,
5748      ',': 188,
5749      '-': 189,
5750      '.': 190,
5751      '/': 191,
5752      '`': 192,
5753      '[': 219,
5754      '\\': 220,
5755      ']': 221,
5756      "'": 222 // Helper aliases
5757
5758    };
5759    var aliases = exports.aliases = {
5760      'windows': 91,
5761      '⇧': 16,
5762      '⌥': 18,
5763      '⌃': 17,
5764      '⌘': 91,
5765      'ctl': 17,
5766      'control': 17,
5767      'option': 18,
5768      'pause': 19,
5769      'break': 19,
5770      'caps': 20,
5771      'return': 13,
5772      'escape': 27,
5773      'spc': 32,
5774      'spacebar': 32,
5775      'pgup': 33,
5776      'pgdn': 34,
5777      'ins': 45,
5778      'del': 46,
5779      'cmd': 91
5780      /*!
5781       * Programatically add the following
5782       */
5783      // lower case chars
5784
5785    };
5786
5787    for (i = 97; i < 123; i++) {
5788      codes[String.fromCharCode(i)] = i - 32;
5789    } // numbers
5790
5791
5792    for (var i = 48; i < 58; i++) {
5793      codes[i - 48] = i;
5794    } // function keys
5795
5796
5797    for (i = 1; i < 13; i++) {
5798      codes['f' + i] = i + 111;
5799    } // numpad keys
5800
5801
5802    for (i = 0; i < 10; i++) {
5803      codes['numpad ' + i] = i + 96;
5804    }
5805    /**
5806     * Get by code
5807     *
5808     *   exports.name[13] // => 'Enter'
5809     */
5810
5811
5812    var names = exports.names = exports.title = {}; // title for backward compat
5813    // Create reverse mapping
5814
5815    for (i in codes) {
5816      names[codes[i]] = i;
5817    } // Add aliases
5818
5819
5820    for (var alias in aliases) {
5821      codes[alias] = aliases[alias];
5822    }
5823  });
5824  var keycode_1 = keycode.code;
5825  var keycode_2 = keycode.codes;
5826  var keycode_3 = keycode.aliases;
5827  var keycode_4 = keycode.names;
5828  var keycode_5 = keycode.title;
5829
5830  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5831  /**
5832   * The `ModalDialog` displays over the video and its controls, which blocks
5833   * interaction with the player until it is closed.
5834   *
5835   * Modal dialogs include a "Close" button and will close when that button
5836   * is activated - or when ESC is pressed anywhere.
5837   *
5838   * @extends Component
5839   */
5840
5841  var ModalDialog =
5842  /*#__PURE__*/
5843  function (_Component) {
5844    inheritsLoose(ModalDialog, _Component);
5845
5846    /**
5847     * Create an instance of this class.
5848     *
5849     * @param {Player} player
5850     *        The `Player` that this class should be attached to.
5851     *
5852     * @param {Object} [options]
5853     *        The key/value store of player options.
5854     *
5855     * @param {Mixed} [options.content=undefined]
5856     *        Provide customized content for this modal.
5857     *
5858     * @param {string} [options.description]
5859     *        A text description for the modal, primarily for accessibility.
5860     *
5861     * @param {boolean} [options.fillAlways=false]
5862     *        Normally, modals are automatically filled only the first time
5863     *        they open. This tells the modal to refresh its content
5864     *        every time it opens.
5865     *
5866     * @param {string} [options.label]
5867     *        A text label for the modal, primarily for accessibility.
5868     *
5869     * @param {boolean} [options.pauseOnOpen=true]
5870     *        If `true`, playback will will be paused if playing when
5871     *        the modal opens, and resumed when it closes.
5872     *
5873     * @param {boolean} [options.temporary=true]
5874     *        If `true`, the modal can only be opened once; it will be
5875     *        disposed as soon as it's closed.
5876     *
5877     * @param {boolean} [options.uncloseable=false]
5878     *        If `true`, the user will not be able to close the modal
5879     *        through the UI in the normal ways. Programmatic closing is
5880     *        still possible.
5881     */
5882    function ModalDialog(player, options) {
5883      var _this;
5884
5885      _this = _Component.call(this, player, options) || this;
5886      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
5887
5888      _this.closeable(!_this.options_.uncloseable);
5889
5890      _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
5891      // because we only want the contents of the modal in the contentEl
5892      // (not the UI elements like the close button).
5893
5894
5895      _this.contentEl_ = createEl('div', {
5896        className: MODAL_CLASS_NAME + "-content"
5897      }, {
5898        role: 'document'
5899      });
5900      _this.descEl_ = createEl('p', {
5901        className: MODAL_CLASS_NAME + "-description vjs-control-text",
5902        id: _this.el().getAttribute('aria-describedby')
5903      });
5904      textContent(_this.descEl_, _this.description());
5905
5906      _this.el_.appendChild(_this.descEl_);
5907
5908      _this.el_.appendChild(_this.contentEl_);
5909
5910      return _this;
5911    }
5912    /**
5913     * Create the `ModalDialog`'s DOM element
5914     *
5915     * @return {Element}
5916     *         The DOM element that gets created.
5917     */
5918
5919
5920    var _proto = ModalDialog.prototype;
5921
5922    _proto.createEl = function createEl() {
5923      return _Component.prototype.createEl.call(this, 'div', {
5924        className: this.buildCSSClass(),
5925        tabIndex: -1
5926      }, {
5927        'aria-describedby': this.id() + "_description",
5928        'aria-hidden': 'true',
5929        'aria-label': this.label(),
5930        'role': 'dialog'
5931      });
5932    };
5933
5934    _proto.dispose = function dispose() {
5935      this.contentEl_ = null;
5936      this.descEl_ = null;
5937      this.previouslyActiveEl_ = null;
5938
5939      _Component.prototype.dispose.call(this);
5940    }
5941    /**
5942     * Builds the default DOM `className`.
5943     *
5944     * @return {string}
5945     *         The DOM `className` for this object.
5946     */
5947    ;
5948
5949    _proto.buildCSSClass = function buildCSSClass() {
5950      return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
5951    }
5952    /**
vendor: 32,228 bytes, lines 5953-7145
5953     * Returns the label string for this modal. Primarily used for accessibility.
5954     *
5955     * @return {string}
5956     *         the localized or raw label of this modal.
5957     */
5958    ;
5959
5960    _proto.label = function label() {
5961      return this.localize(this.options_.label || 'Modal Window');
5962    }
5963    /**
5964     * Returns the description string for this modal. Primarily used for
5965     * accessibility.
5966     *
5967     * @return {string}
5968     *         The localized or raw description of this modal.
5969     */
5970    ;
5971
5972    _proto.description = function description() {
5973      var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
5974
5975      if (this.closeable()) {
5976        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
5977      }
5978
5979      return desc;
5980    }
5981    /**
5982     * Opens the modal.
5983     *
5984     * @fires ModalDialog#beforemodalopen
5985     * @fires ModalDialog#modalopen
5986     */
5987    ;
5988
5989    _proto.open = function open() {
5990      if (!this.opened_) {
5991        var player = this.player();
5992        /**
5993          * Fired just before a `ModalDialog` is opened.
5994          *
5995          * @event ModalDialog#beforemodalopen
5996          * @type {EventTarget~Event}
5997          */
5998
5999        this.trigger('beforemodalopen');
6000        this.opened_ = true; // Fill content if the modal has never opened before and
6001        // never been filled.
6002
6003        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
6004          this.fill();
6005        } // If the player was playing, pause it and take note of its previously
6006        // playing state.
6007
6008
6009        this.wasPlaying_ = !player.paused();
6010
6011        if (this.options_.pauseOnOpen && this.wasPlaying_) {
6012          player.pause();
6013        }
6014
6015        this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled.
6016
6017        this.hadControls_ = player.controls();
6018        player.controls(false);
6019        this.show();
6020        this.conditionalFocus_();
6021        this.el().setAttribute('aria-hidden', 'false');
6022        /**
6023          * Fired just after a `ModalDialog` is opened.
6024          *
6025          * @event ModalDialog#modalopen
6026          * @type {EventTarget~Event}
6027          */
6028
6029        this.trigger('modalopen');
6030        this.hasBeenOpened_ = true;
6031      }
6032    }
6033    /**
6034     * If the `ModalDialog` is currently open or closed.
6035     *
6036     * @param  {boolean} [value]
6037     *         If given, it will open (`true`) or close (`false`) the modal.
6038     *
6039     * @return {boolean}
6040     *         the current open state of the modaldialog
6041     */
6042    ;
6043
6044    _proto.opened = function opened(value) {
6045      if (typeof value === 'boolean') {
6046        this[value ? 'open' : 'close']();
6047      }
6048
6049      return this.opened_;
6050    }
6051    /**
6052     * Closes the modal, does nothing if the `ModalDialog` is
6053     * not open.
6054     *
6055     * @fires ModalDialog#beforemodalclose
6056     * @fires ModalDialog#modalclose
6057     */
6058    ;
6059
6060    _proto.close = function close() {
6061      if (!this.opened_) {
6062        return;
6063      }
6064
6065      var player = this.player();
6066      /**
6067        * Fired just before a `ModalDialog` is closed.
6068        *
6069        * @event ModalDialog#beforemodalclose
6070        * @type {EventTarget~Event}
6071        */
6072
6073      this.trigger('beforemodalclose');
6074      this.opened_ = false;
6075
6076      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6077        player.play();
6078      }
6079
6080      this.off('keydown', this.handleKeyDown);
6081
6082      if (this.hadControls_) {
6083        player.controls(true);
6084      }
6085
6086      this.hide();
6087      this.el().setAttribute('aria-hidden', 'true');
6088      /**
6089        * Fired just after a `ModalDialog` is closed.
6090        *
6091        * @event ModalDialog#modalclose
6092        * @type {EventTarget~Event}
6093        */
6094
6095      this.trigger('modalclose');
6096      this.conditionalBlur_();
6097
6098      if (this.options_.temporary) {
6099        this.dispose();
6100      }
6101    }
6102    /**
6103     * Check to see if the `ModalDialog` is closeable via the UI.
6104     *
6105     * @param  {boolean} [value]
6106     *         If given as a boolean, it will set the `closeable` option.
6107     *
6108     * @return {boolean}
6109     *         Returns the final value of the closable option.
6110     */
6111    ;
6112
6113    _proto.closeable = function closeable(value) {
6114      if (typeof value === 'boolean') {
6115        var closeable = this.closeable_ = !!value;
6116        var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
6117
6118        if (closeable && !close) {
6119          // The close button should be a child of the modal - not its
6120          // content element, so temporarily change the content element.
6121          var temp = this.contentEl_;
6122          this.contentEl_ = this.el_;
6123          close = this.addChild('closeButton', {
6124            controlText: 'Close Modal Dialog'
6125          });
6126          this.contentEl_ = temp;
6127          this.on(close, 'close', this.close);
6128        } // If this is being made uncloseable and has a close button, remove it.
6129
6130
6131        if (!closeable && close) {
6132          this.off(close, 'close', this.close);
6133          this.removeChild(close);
6134          close.dispose();
6135        }
6136      }
6137
6138      return this.closeable_;
6139    }
6140    /**
6141     * Fill the modal's content element with the modal's "content" option.
6142     * The content element will be emptied before this change takes place.
6143     */
6144    ;
6145
6146    _proto.fill = function fill() {
6147      this.fillWith(this.content());
6148    }
6149    /**
6150     * Fill the modal's content element with arbitrary content.
6151     * The content element will be emptied before this change takes place.
6152     *
6153     * @fires ModalDialog#beforemodalfill
6154     * @fires ModalDialog#modalfill
6155     *
6156     * @param {Mixed} [content]
6157     *        The same rules apply to this as apply to the `content` option.
6158     */
6159    ;
6160
6161    _proto.fillWith = function fillWith(content) {
6162      var contentEl = this.contentEl();
6163      var parentEl = contentEl.parentNode;
6164      var nextSiblingEl = contentEl.nextSibling;
6165      /**
6166        * Fired just before a `ModalDialog` is filled with content.
6167        *
6168        * @event ModalDialog#beforemodalfill
6169        * @type {EventTarget~Event}
6170        */
6171
6172      this.trigger('beforemodalfill');
6173      this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
6174      // manipulation to avoid modifying the live DOM multiple times.
6175
6176      parentEl.removeChild(contentEl);
6177      this.empty();
6178      insertContent(contentEl, content);
6179      /**
6180       * Fired just after a `ModalDialog` is filled with content.
6181       *
6182       * @event ModalDialog#modalfill
6183       * @type {EventTarget~Event}
6184       */
6185
6186      this.trigger('modalfill'); // Re-inject the re-filled content element.
6187
6188      if (nextSiblingEl) {
6189        parentEl.insertBefore(contentEl, nextSiblingEl);
6190      } else {
6191        parentEl.appendChild(contentEl);
6192      } // make sure that the close button is last in the dialog DOM
6193
6194
6195      var closeButton = this.getChild('closeButton');
6196
6197      if (closeButton) {
6198        parentEl.appendChild(closeButton.el_);
6199      }
6200    }
6201    /**
6202     * Empties the content element. This happens anytime the modal is filled.
6203     *
6204     * @fires ModalDialog#beforemodalempty
6205     * @fires ModalDialog#modalempty
6206     */
6207    ;
6208
6209    _proto.empty = function empty() {
6210      /**
6211      * Fired just before a `ModalDialog` is emptied.
6212      *
6213      * @event ModalDialog#beforemodalempty
6214      * @type {EventTarget~Event}
6215      */
6216      this.trigger('beforemodalempty');
6217      emptyEl(this.contentEl());
6218      /**
6219      * Fired just after a `ModalDialog` is emptied.
6220      *
6221      * @event ModalDialog#modalempty
6222      * @type {EventTarget~Event}
6223      */
6224
6225      this.trigger('modalempty');
6226    }
6227    /**
6228     * Gets or sets the modal content, which gets normalized before being
6229     * rendered into the DOM.
6230     *
6231     * This does not update the DOM or fill the modal, but it is called during
6232     * that process.
6233     *
6234     * @param  {Mixed} [value]
6235     *         If defined, sets the internal content value to be used on the
6236     *         next call(s) to `fill`. This value is normalized before being
6237     *         inserted. To "clear" the internal content value, pass `null`.
6238     *
6239     * @return {Mixed}
6240     *         The current content of the modal dialog
6241     */
6242    ;
6243
6244    _proto.content = function content(value) {
6245      if (typeof value !== 'undefined') {
6246        this.content_ = value;
6247      }
6248
6249      return this.content_;
6250    }
6251    /**
6252     * conditionally focus the modal dialog if focus was previously on the player.
6253     *
6254     * @private
6255     */
6256    ;
6257
6258    _proto.conditionalFocus_ = function conditionalFocus_() {
6259      var activeEl = document.activeElement;
6260      var playerEl = this.player_.el_;
6261      this.previouslyActiveEl_ = null;
6262
6263      if (playerEl.contains(activeEl) || playerEl === activeEl) {
6264        this.previouslyActiveEl_ = activeEl;
6265        this.focus();
6266      }
6267    }
6268    /**
6269     * conditionally blur the element and refocus the last focused element
6270     *
6271     * @private
6272     */
6273    ;
6274
6275    _proto.conditionalBlur_ = function conditionalBlur_() {
6276      if (this.previouslyActiveEl_) {
6277        this.previouslyActiveEl_.focus();
6278        this.previouslyActiveEl_ = null;
6279      }
6280    }
6281    /**
6282     * Keydown handler. Attached when modal is focused.
6283     *
6284     * @listens keydown
6285     */
6286    ;
6287
6288    _proto.handleKeyDown = function handleKeyDown(event) {
6289      // Do not allow keydowns to reach out of the modal dialog.
6290      event.stopPropagation();
6291
6292      if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6293        event.preventDefault();
6294        this.close();
6295        return;
6296      } // exit early if it isn't a tab key
6297
6298
6299      if (!keycode.isEventKey(event, 'Tab')) {
6300        return;
6301      }
6302
6303      var focusableEls = this.focusableEls_();
6304      var activeEl = this.el_.querySelector(':focus');
6305      var focusIndex;
6306
6307      for (var i = 0; i < focusableEls.length; i++) {
6308        if (activeEl === focusableEls[i]) {
6309          focusIndex = i;
6310          break;
6311        }
6312      }
6313
6314      if (document.activeElement === this.el_) {
6315        focusIndex = 0;
6316      }
6317
6318      if (event.shiftKey && focusIndex === 0) {
6319        focusableEls[focusableEls.length - 1].focus();
6320        event.preventDefault();
6321      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6322        focusableEls[0].focus();
6323        event.preventDefault();
6324      }
6325    }
6326    /**
6327     * get all focusable elements
6328     *
6329     * @private
6330     */
6331    ;
6332
6333    _proto.focusableEls_ = function focusableEls_() {
6334      var allChildren = this.el_.querySelectorAll('*');
6335      return Array.prototype.filter.call(allChildren, function (child) {
6336        return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
6337      });
6338    };
6339
6340    return ModalDialog;
6341  }(Component);
6342  /**
6343   * Default options for `ModalDialog` default options.
6344   *
6345   * @type {Object}
6346   * @private
6347   */
6348
6349
6350  ModalDialog.prototype.options_ = {
6351    pauseOnOpen: true,
6352    temporary: true
6353  };
6354  Component.registerComponent('ModalDialog', ModalDialog);
6355
6356  /**
6357   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6358   * {@link VideoTrackList}
6359   *
6360   * @extends EventTarget
6361   */
6362
6363  var TrackList =
6364  /*#__PURE__*/
6365  function (_EventTarget) {
6366    inheritsLoose(TrackList, _EventTarget);
6367
6368    /**
6369     * Create an instance of this class
6370     *
6371     * @param {Track[]} tracks
6372     *        A list of tracks to initialize the list with.
6373     *
6374     * @abstract
6375     */
6376    function TrackList(tracks) {
6377      var _this;
6378
6379      if (tracks === void 0) {
6380        tracks = [];
6381      }
6382
6383      _this = _EventTarget.call(this) || this;
6384      _this.tracks_ = [];
6385      /**
6386       * @memberof TrackList
6387       * @member {number} length
6388       *         The current number of `Track`s in the this Trackist.
6389       * @instance
6390       */
6391
6392      Object.defineProperty(assertThisInitialized(_this), 'length', {
6393        get: function get() {
6394          return this.tracks_.length;
6395        }
6396      });
6397
6398      for (var i = 0; i < tracks.length; i++) {
6399        _this.addTrack(tracks[i]);
6400      }
6401
6402      return _this;
6403    }
6404    /**
6405     * Add a {@link Track} to the `TrackList`
6406     *
6407     * @param {Track} track
6408     *        The audio, video, or text track to add to the list.
6409     *
6410     * @fires TrackList#addtrack
6411     */
6412
6413
6414    var _proto = TrackList.prototype;
6415
6416    _proto.addTrack = function addTrack(track) {
6417      var index = this.tracks_.length;
6418
6419      if (!('' + index in this)) {
6420        Object.defineProperty(this, index, {
6421          get: function get() {
6422            return this.tracks_[index];
6423          }
6424        });
6425      } // Do not add duplicate tracks
6426
6427
6428      if (this.tracks_.indexOf(track) === -1) {
6429        this.tracks_.push(track);
6430        /**
6431         * Triggered when a track is added to a track list.
6432         *
6433         * @event TrackList#addtrack
6434         * @type {EventTarget~Event}
6435         * @property {Track} track
6436         *           A reference to track that was added.
6437         */
6438
6439        this.trigger({
6440          track: track,
6441          type: 'addtrack',
6442          target: this
6443        });
6444      }
6445    }
6446    /**
6447     * Remove a {@link Track} from the `TrackList`
6448     *
6449     * @param {Track} rtrack
6450     *        The audio, video, or text track to remove from the list.
6451     *
6452     * @fires TrackList#removetrack
6453     */
6454    ;
6455
6456    _proto.removeTrack = function removeTrack(rtrack) {
6457      var track;
6458
6459      for (var i = 0, l = this.length; i < l; i++) {
6460        if (this[i] === rtrack) {
6461          track = this[i];
6462
6463          if (track.off) {
6464            track.off();
6465          }
6466
6467          this.tracks_.splice(i, 1);
6468          break;
6469        }
6470      }
6471
6472      if (!track) {
6473        return;
6474      }
6475      /**
6476       * Triggered when a track is removed from track list.
6477       *
6478       * @event TrackList#removetrack
6479       * @type {EventTarget~Event}
6480       * @property {Track} track
6481       *           A reference to track that was removed.
6482       */
6483
6484
6485      this.trigger({
6486        track: track,
6487        type: 'removetrack',
6488        target: this
6489      });
6490    }
6491    /**
6492     * Get a Track from the TrackList by a tracks id
6493     *
6494     * @param {string} id - the id of the track to get
6495     * @method getTrackById
6496     * @return {Track}
6497     * @private
6498     */
6499    ;
6500
6501    _proto.getTrackById = function getTrackById(id) {
6502      var result = null;
6503
6504      for (var i = 0, l = this.length; i < l; i++) {
6505        var track = this[i];
6506
6507        if (track.id === id) {
6508          result = track;
6509          break;
6510        }
6511      }
6512
6513      return result;
6514    };
6515
6516    return TrackList;
6517  }(EventTarget);
6518  /**
6519   * Triggered when a different track is selected/enabled.
6520   *
6521   * @event TrackList#change
6522   * @type {EventTarget~Event}
6523   */
6524
6525  /**
6526   * Events that can be called with on + eventName. See {@link EventHandler}.
6527   *
6528   * @property {Object} TrackList#allowedEvents_
6529   * @private
6530   */
6531
6532
6533  TrackList.prototype.allowedEvents_ = {
6534    change: 'change',
6535    addtrack: 'addtrack',
6536    removetrack: 'removetrack'
6537  }; // emulate attribute EventHandler support to allow for feature detection
6538
6539  for (var event in TrackList.prototype.allowedEvents_) {
6540    TrackList.prototype['on' + event] = null;
6541  }
6542
6543  /**
6544   * Anywhere we call this function we diverge from the spec
6545   * as we only support one enabled audiotrack at a time
6546   *
6547   * @param {AudioTrackList} list
6548   *        list to work on
6549   *
6550   * @param {AudioTrack} track
6551   *        The track to skip
6552   *
6553   * @private
6554   */
6555
6556  var disableOthers = function disableOthers(list, track) {
6557    for (var i = 0; i < list.length; i++) {
6558      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6559        continue;
6560      } // another audio track is enabled, disable it
6561
6562
6563      list[i].enabled = false;
6564    }
6565  };
6566  /**
6567   * The current list of {@link AudioTrack} for a media file.
6568   *
6569   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6570   * @extends TrackList
6571   */
6572
6573
6574  var AudioTrackList =
6575  /*#__PURE__*/
6576  function (_TrackList) {
6577    inheritsLoose(AudioTrackList, _TrackList);
6578
6579    /**
6580     * Create an instance of this class.
6581     *
6582     * @param {AudioTrack[]} [tracks=[]]
6583     *        A list of `AudioTrack` to instantiate the list with.
6584     */
6585    function AudioTrackList(tracks) {
6586      var _this;
6587
6588      if (tracks === void 0) {
6589        tracks = [];
6590      }
6591
6592      // make sure only 1 track is enabled
6593      // sorted from last index to first index
6594      for (var i = tracks.length - 1; i >= 0; i--) {
6595        if (tracks[i].enabled) {
6596          disableOthers(tracks, tracks[i]);
6597          break;
6598        }
6599      }
6600
6601      _this = _TrackList.call(this, tracks) || this;
6602      _this.changing_ = false;
6603      return _this;
6604    }
6605    /**
6606     * Add an {@link AudioTrack} to the `AudioTrackList`.
6607     *
6608     * @param {AudioTrack} track
6609     *        The AudioTrack to add to the list
6610     *
6611     * @fires TrackList#addtrack
6612     */
6613
6614
6615    var _proto = AudioTrackList.prototype;
6616
6617    _proto.addTrack = function addTrack(track) {
6618      var _this2 = this;
6619
6620      if (track.enabled) {
6621        disableOthers(this, track);
6622      }
6623
6624      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6625
6626
6627      if (!track.addEventListener) {
6628        return;
6629      }
6630
6631      track.enabledChange_ = function () {
6632        // when we are disabling other tracks (since we don't support
6633        // more than one track at a time) we will set changing_
6634        // to true so that we don't trigger additional change events
6635        if (_this2.changing_) {
6636          return;
6637        }
6638
6639        _this2.changing_ = true;
6640        disableOthers(_this2, track);
6641        _this2.changing_ = false;
6642
6643        _this2.trigger('change');
6644      };
6645      /**
6646       * @listens AudioTrack#enabledchange
6647       * @fires TrackList#change
6648       */
6649
6650
6651      track.addEventListener('enabledchange', track.enabledChange_);
6652    };
6653
6654    _proto.removeTrack = function removeTrack(rtrack) {
6655      _TrackList.prototype.removeTrack.call(this, rtrack);
6656
6657      if (rtrack.removeEventListener && rtrack.enabledChange_) {
6658        rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
6659        rtrack.enabledChange_ = null;
6660      }
6661    };
6662
6663    return AudioTrackList;
6664  }(TrackList);
6665
6666  /**
6667   * Un-select all other {@link VideoTrack}s that are selected.
6668   *
6669   * @param {VideoTrackList} list
6670   *        list to work on
6671   *
6672   * @param {VideoTrack} track
6673   *        The track to skip
6674   *
6675   * @private
6676   */
6677
6678  var disableOthers$1 = function disableOthers(list, track) {
6679    for (var i = 0; i < list.length; i++) {
6680      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6681        continue;
6682      } // another video track is enabled, disable it
6683
6684
6685      list[i].selected = false;
6686    }
6687  };
6688  /**
6689   * The current list of {@link VideoTrack} for a video.
6690   *
6691   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6692   * @extends TrackList
6693   */
6694
6695
6696  var VideoTrackList =
6697  /*#__PURE__*/
6698  function (_TrackList) {
6699    inheritsLoose(VideoTrackList, _TrackList);
6700
6701    /**
6702     * Create an instance of this class.
6703     *
6704     * @param {VideoTrack[]} [tracks=[]]
6705     *        A list of `VideoTrack` to instantiate the list with.
6706     */
6707    function VideoTrackList(tracks) {
6708      var _this;
6709
6710      if (tracks === void 0) {
6711        tracks = [];
6712      }
6713
6714      // make sure only 1 track is enabled
6715      // sorted from last index to first index
6716      for (var i = tracks.length - 1; i >= 0; i--) {
6717        if (tracks[i].selected) {
6718          disableOthers$1(tracks, tracks[i]);
6719          break;
6720        }
6721      }
6722
6723      _this = _TrackList.call(this, tracks) || this;
6724      _this.changing_ = false;
6725      /**
6726       * @member {number} VideoTrackList#selectedIndex
6727       *         The current index of the selected {@link VideoTrack`}.
6728       */
6729
6730      Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', {
6731        get: function get() {
6732          for (var _i = 0; _i < this.length; _i++) {
6733            if (this[_i].selected) {
6734              return _i;
6735            }
6736          }
6737
6738          return -1;
6739        },
6740        set: function set() {}
6741      });
6742      return _this;
6743    }
6744    /**
6745     * Add a {@link VideoTrack} to the `VideoTrackList`.
6746     *
6747     * @param {VideoTrack} track
6748     *        The VideoTrack to add to the list
6749     *
6750     * @fires TrackList#addtrack
6751     */
6752
6753
6754    var _proto = VideoTrackList.prototype;
6755
6756    _proto.addTrack = function addTrack(track) {
6757      var _this2 = this;
6758
6759      if (track.selected) {
6760        disableOthers$1(this, track);
6761      }
6762
6763      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6764
6765
6766      if (!track.addEventListener) {
6767        return;
6768      }
6769
6770      track.selectedChange_ = function () {
6771        if (_this2.changing_) {
6772          return;
6773        }
6774
6775        _this2.changing_ = true;
6776        disableOthers$1(_this2, track);
6777        _this2.changing_ = false;
6778
6779        _this2.trigger('change');
6780      };
6781      /**
6782       * @listens VideoTrack#selectedchange
6783       * @fires TrackList#change
6784       */
6785
6786
6787      track.addEventListener('selectedchange', track.selectedChange_);
6788    };
6789
6790    _proto.removeTrack = function removeTrack(rtrack) {
6791      _TrackList.prototype.removeTrack.call(this, rtrack);
6792
6793      if (rtrack.removeEventListener && rtrack.selectedChange_) {
6794        rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
6795        rtrack.selectedChange_ = null;
6796      }
6797    };
6798
6799    return VideoTrackList;
6800  }(TrackList);
6801
6802  /**
6803   * The current list of {@link TextTrack} for a media file.
6804   *
6805   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6806   * @extends TrackList
6807   */
6808
6809  var TextTrackList =
6810  /*#__PURE__*/
6811  function (_TrackList) {
6812    inheritsLoose(TextTrackList, _TrackList);
6813
6814    function TextTrackList() {
6815      return _TrackList.apply(this, arguments) || this;
6816    }
6817
6818    var _proto = TextTrackList.prototype;
6819
6820    /**
6821     * Add a {@link TextTrack} to the `TextTrackList`
6822     *
6823     * @param {TextTrack} track
6824     *        The text track to add to the list.
6825     *
6826     * @fires TrackList#addtrack
6827     */
6828    _proto.addTrack = function addTrack(track) {
6829      var _this = this;
6830
6831      _TrackList.prototype.addTrack.call(this, track);
6832
6833      if (!this.queueChange_) {
6834        this.queueChange_ = function () {
6835          return _this.queueTrigger('change');
6836        };
6837      }
6838
6839      if (!this.triggerSelectedlanguagechange) {
6840        this.triggerSelectedlanguagechange_ = function () {
6841          return _this.trigger('selectedlanguagechange');
6842        };
6843      }
6844      /**
6845       * @listens TextTrack#modechange
6846       * @fires TrackList#change
6847       */
6848
6849
6850      track.addEventListener('modechange', this.queueChange_);
6851      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
6852
6853      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6854        track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
6855      }
6856    };
6857
6858    _proto.removeTrack = function removeTrack(rtrack) {
6859      _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
6860
6861
6862      if (rtrack.removeEventListener) {
6863        if (this.queueChange_) {
6864          rtrack.removeEventListener('modechange', this.queueChange_);
6865        }
6866
6867        if (this.selectedlanguagechange_) {
6868          rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
6869        }
6870      }
6871    };
6872
6873    return TextTrackList;
6874  }(TrackList);
6875
6876  /**
6877   * @file html-track-element-list.js
6878   */
6879
6880  /**
6881   * The current list of {@link HtmlTrackElement}s.
6882   */
6883  var HtmlTrackElementList =
6884  /*#__PURE__*/
6885  function () {
6886    /**
6887     * Create an instance of this class.
6888     *
6889     * @param {HtmlTrackElement[]} [tracks=[]]
6890     *        A list of `HtmlTrackElement` to instantiate the list with.
6891     */
6892    function HtmlTrackElementList(trackElements) {
6893      if (trackElements === void 0) {
6894        trackElements = [];
6895      }
6896
6897      this.trackElements_ = [];
6898      /**
6899       * @memberof HtmlTrackElementList
6900       * @member {number} length
6901       *         The current number of `Track`s in the this Trackist.
6902       * @instance
6903       */
6904
6905      Object.defineProperty(this, 'length', {
6906        get: function get() {
6907          return this.trackElements_.length;
6908        }
6909      });
6910
6911      for (var i = 0, length = trackElements.length; i < length; i++) {
6912        this.addTrackElement_(trackElements[i]);
6913      }
6914    }
6915    /**
6916     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6917     *
6918     * @param {HtmlTrackElement} trackElement
6919     *        The track element to add to the list.
6920     *
6921     * @private
6922     */
6923
6924
6925    var _proto = HtmlTrackElementList.prototype;
6926
6927    _proto.addTrackElement_ = function addTrackElement_(trackElement) {
6928      var index = this.trackElements_.length;
6929
6930      if (!('' + index in this)) {
6931        Object.defineProperty(this, index, {
6932          get: function get() {
6933            return this.trackElements_[index];
6934          }
6935        });
6936      } // Do not add duplicate elements
6937
6938
6939      if (this.trackElements_.indexOf(trackElement) === -1) {
6940        this.trackElements_.push(trackElement);
6941      }
6942    }
6943    /**
6944     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6945     * {@link TextTrack}.
6946     *
6947     * @param {TextTrack} track
6948     *        The track associated with a track element.
6949     *
6950     * @return {HtmlTrackElement|undefined}
6951     *         The track element that was found or undefined.
6952     *
6953     * @private
6954     */
6955    ;
6956
6957    _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
6958      var trackElement_;
6959
6960      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6961        if (track === this.trackElements_[i].track) {
6962          trackElement_ = this.trackElements_[i];
6963          break;
6964        }
6965      }
6966
6967      return trackElement_;
6968    }
6969    /**
6970     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6971     *
6972     * @param {HtmlTrackElement} trackElement
6973     *        The track element to remove from the list.
6974     *
6975     * @private
6976     */
6977    ;
6978
6979    _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
6980      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6981        if (trackElement === this.trackElements_[i]) {
6982          if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
6983            this.trackElements_[i].track.off();
6984          }
6985
6986          if (typeof this.trackElements_[i].off === 'function') {
6987            this.trackElements_[i].off();
6988          }
6989
6990          this.trackElements_.splice(i, 1);
6991          break;
6992        }
6993      }
6994    };
6995
6996    return HtmlTrackElementList;
6997  }();
6998
6999  /**
7000   * @file text-track-cue-list.js
7001   */
7002
7003  /**
7004   * @typedef {Object} TextTrackCueList~TextTrackCue
7005   *
7006   * @property {string} id
7007   *           The unique id for this text track cue
7008   *
7009   * @property {number} startTime
7010   *           The start time for this text track cue
7011   *
7012   * @property {number} endTime
7013   *           The end time for this text track cue
7014   *
7015   * @property {boolean} pauseOnExit
7016   *           Pause when the end time is reached if true.
7017   *
7018   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
7019   */
7020
7021  /**
7022   * A List of TextTrackCues.
7023   *
7024   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
7025   */
7026  var TextTrackCueList =
7027  /*#__PURE__*/
7028  function () {
7029    /**
7030     * Create an instance of this class..
7031     *
7032     * @param {Array} cues
7033     *        A list of cues to be initialized with
7034     */
7035    function TextTrackCueList(cues) {
7036      TextTrackCueList.prototype.setCues_.call(this, cues);
7037      /**
7038       * @memberof TextTrackCueList
7039       * @member {number} length
7040       *         The current number of `TextTrackCue`s in the TextTrackCueList.
7041       * @instance
7042       */
7043
7044      Object.defineProperty(this, 'length', {
7045        get: function get() {
7046          return this.length_;
7047        }
7048      });
7049    }
7050    /**
7051     * A setter for cues in this list. Creates getters
7052     * an an index for the cues.
7053     *
7054     * @param {Array} cues
7055     *        An array of cues to set
7056     *
7057     * @private
7058     */
7059
7060
7061    var _proto = TextTrackCueList.prototype;
7062
7063    _proto.setCues_ = function setCues_(cues) {
7064      var oldLength = this.length || 0;
7065      var i = 0;
7066      var l = cues.length;
7067      this.cues_ = cues;
7068      this.length_ = cues.length;
7069
7070      var defineProp = function defineProp(index) {
7071        if (!('' + index in this)) {
7072          Object.defineProperty(this, '' + index, {
7073            get: function get() {
7074              return this.cues_[index];
7075            }
7076          });
7077        }
7078      };
7079
7080      if (oldLength < l) {
7081        i = oldLength;
7082
7083        for (; i < l; i++) {
7084          defineProp.call(this, i);
7085        }
7086      }
7087    }
7088    /**
7089     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
7090     *
7091     * @param {string} id
7092     *        The id of the cue that should be searched for.
7093     *
7094     * @return {TextTrackCueList~TextTrackCue|null}
7095     *         A single cue or null if none was found.
7096     */
7097    ;
7098
7099    _proto.getCueById = function getCueById(id) {
7100      var result = null;
7101
7102      for (var i = 0, l = this.length; i < l; i++) {
7103        var cue = this[i];
7104
7105        if (cue.id === id) {
7106          result = cue;
7107          break;
7108        }
7109      }
7110
7111      return result;
7112    };
7113
7114    return TextTrackCueList;
7115  }();
7116
7117  /**
7118   * @file track-kinds.js
7119   */
7120
7121  /**
7122   * All possible `VideoTrackKind`s
7123   *
7124   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7125   * @typedef VideoTrack~Kind
7126   * @enum
7127   */
7128  var VideoTrackKind = {
7129    alternative: 'alternative',
7130    captions: 'captions',
7131    main: 'main',
7132    sign: 'sign',
7133    subtitles: 'subtitles',
7134    commentary: 'commentary'
7135  };
7136  /**
7137   * All possible `AudioTrackKind`s
7138   *
7139   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7140   * @typedef AudioTrack~Kind
7141   * @enum
7142   */
7143
7144  var AudioTrackKind = {
7145    'alternative': 'alternative',
7146    'descriptions': 'descriptions',
7147    'main': 'main',
7148    'main-desc': 'main-desc',
7149    'translation': 'translation',
7150    'commentary': 'commentary'
7151  };
7152  /**
7153   * All possible `TextTrackKind`s
7154   *
7155   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7156   * @typedef TextTrack~Kind
7157   * @enum
7158   */
7159
7160  var TextTrackKind = {
7161    subtitles: 'subtitles',
7162    captions: 'captions',
7163    descriptions: 'descriptions',
7164    chapters: 'chapters',
7165    metadata: 'metadata'
7166  };
7167  /**
7168   * All possible `TextTrackMode`s
7169   *
7170   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7171   * @typedef TextTrack~Mode
7172   * @enum
7173   */
7174
7175  var TextTrackMode = {
7176    disabled: 'disabled',
7177    hidden: 'hidden',
7178    showing: 'showing'
7179  };
7180
7181  /**
7182   * A Track class that contains all of the common functionality for {@link AudioTrack},
7183   * {@link VideoTrack}, and {@link TextTrack}.
7184   *
7185   * > Note: This class should not be used directly
7186   *
7187   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7188   * @extends EventTarget
7189   * @abstract
7190   */
7191
7192  var Track =
7193  /*#__PURE__*/
7194  function (_EventTarget) {
7195    inheritsLoose(Track, _EventTarget);
7196
7197    /**
7198     * Create an instance of this class.
7199     *
7200     * @param {Object} [options={}]
7201     *        Object of option names and values
7202     *
7203     * @param {string} [options.kind='']
7204     *        A valid kind for the track type you are creating.
7205     *
7206     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7207     *        A unique id for this AudioTrack.
7208     *
7209     * @param {string} [options.label='']
7210     *        The menu label for this track.
7211     *
7212     * @param {string} [options.language='']
7213     *        A valid two character language code.
7214     *
7215     * @abstract
7216     */
7217    function Track(options) {
7218      var _this;
7219
7220      if (options === void 0) {
7221        options = {};
7222      }
7223
7224      _this = _EventTarget.call(this) || this;
7225      var trackProps = {
7226        id: options.id || 'vjs_track_' + newGUID(),
7227        kind: options.kind || '',
7228        label: options.label || '',
7229        language: options.language || ''
7230      };
7231      /**
7232       * @memberof Track
7233       * @member {string} id
7234       *         The id of this track. Cannot be changed after creation.
7235       * @instance
7236       *
7237       * @readonly
7238       */
7239
7240      /**
7241       * @memberof Track
7242       * @member {string} kind
7243       *         The kind of track that this is. Cannot be changed after creation.
7244       * @instance
7245       *
7246       * @readonly
7247       */
7248
7249      /**
7250       * @memberof Track
7251       * @member {string} label
7252       *         The label of this track. Cannot be changed after creation.
7253       * @instance
7254       *
7255       * @readonly
7256       */
7257
7258      /**
7259       * @memberof Track
7260       * @member {string} language
7261       *         The two letter language code for this track. Cannot be changed after
7262       *         creation.
7263       * @instance
7264       *
7265       * @readonly
7266       */
7267
7268      var _loop = function _loop(key) {
7269        Object.defineProperty(assertThisInitialized(_this), key, {
7270          get: function get() {
7271            return trackProps[key];
7272          },
7273          set: function set() {}
7274        });
7275      };
7276
7277      for (var key in trackProps) {
7278        _loop(key);
7279      }
7280
7281      return _this;
7282    }
7283
7284    return Track;
7285  }(EventTarget);
7286
7287  /**
7288   * @file url.js
7289   * @module url
7290   */
7291  /**
7292   * @typedef {Object} url:URLObject
7293   *
7294   * @property {string} protocol
7295   *           The protocol of the url that was parsed.
7296   *
7297   * @property {string} hostname
7298   *           The hostname of the url that was parsed.
7299   *
7300   * @property {string} port
7301   *           The port of the url that was parsed.
7302   *
7303   * @property {string} pathname
7304   *           The pathname of the url that was parsed.
7305   *
7306   * @property {string} search
7307   *           The search query of the url that was parsed.
7308   *
7309   * @property {string} hash
7310   *           The hash of the url that was parsed.
7311   *
7312   * @property {string} host
7313   *           The host of the url that was parsed.
7314   */
7315
7316  /**
7317   * Resolve and parse the elements of a URL.
7318   *
7319   * @function
7320   * @param    {String} url
7321   *           The url to parse
7322   *
7323   * @return   {url:URLObject}
7324   *           An object of url details
7325   */
7326
7327  var parseUrl = function parseUrl(url) {
7328    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
7329
7330    var a = document.createElement('a');
7331    a.href = url; // IE8 (and 9?) Fix
7332    // ie8 doesn't parse the URL correctly until the anchor is actually
7333    // added to the body, and an innerHTML is needed to trigger the parsing
7334
7335    var addToBody = a.host === '' && a.protocol !== 'file:';
7336    var div;
7337
7338    if (addToBody) {
7339      div = document.createElement('div');
7340      div.innerHTML = "<a href=\"" + url + "\"></a>";
7341      a = div.firstChild; // prevent the div from affecting layout
7342
7343      div.setAttribute('style', 'display:none; position:absolute;');
7344      document.body.appendChild(div);
7345    } // Copy the specific URL properties to a new object
7346    // This is also needed for IE8 because the anchor loses its
7347    // properties when it's removed from the dom
7348
7349
7350    var details = {};
7351
7352    for (var i = 0; i < props.length; i++) {
7353      details[props[i]] = a[props[i]];
7354    } // IE9 adds the port to the host property unlike everyone else. If
7355    // a port identifier is added for standard ports, strip it.
7356
7357
7358    if (details.protocol === 'http:') {
7359      details.host = details.host.replace(/:80$/, '');
7360    }
7361
7362    if (details.protocol === 'https:') {
7363      details.host = details.host.replace(/:443$/, '');
7364    }
7365
7366    if (!details.protocol) {
7367      details.protocol = window$1.location.protocol;
7368    }
7369
7370    if (addToBody) {
7371      document.body.removeChild(div);
7372    }
7373
7374    return details;
7375  };
7376  /**
7377   * Get absolute version of relative URL. Used to tell Flash the correct URL.
7378   *
7379   * @function
7380   * @param    {string} url
7381   *           URL to make absolute
7382   *
7383   * @return   {string}
7384   *           Absolute URL
7385   *
7386   * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7387   */
7388
7389  var getAbsoluteURL = function getAbsoluteURL(url) {
7390    // Check if absolute URL
7391    if (!url.match(/^https?:\/\//)) {
7392      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
7393      var div = document.createElement('div');
7394      div.innerHTML = "<a href=\"" + url + "\">x</a>";
7395      url = div.firstChild.href;
7396    }
7397
7398    return url;
7399  };
7400  /**
7401   * Returns the extension of the passed file name. It will return an empty string
7402   * if passed an invalid path.
7403   *
7404   * @function
7405   * @param    {string} path
7406   *           The fileName path like '/path/to/file.mp4'
7407   *
7408   * @return  {string}
7409   *           The extension in lower case or an empty string if no
7410   *           extension could be found.
7411   */
7412
7413  var getFileExtension = function getFileExtension(path) {
7414    if (typeof path === 'string') {
7415      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7416      var pathParts = splitPathRe.exec(path);
7417
7418      if (pathParts) {
7419        return pathParts.pop().toLowerCase();
7420      }
7421    }
7422
7423    return '';
7424  };
7425  /**
7426   * Returns whether the url passed is a cross domain request or not.
7427   *
7428   * @function
7429   * @param    {string} url
7430   *           The url to check.
7431   *
7432   * @param    {Object} [winLoc]
7433   *           the domain to check the url against, defaults to window.location
7434   *
7435   * @param    {string} [winLoc.protocol]
7436   *           The window location protocol defaults to window.location.protocol
7437   *
7438   * @param    {string} [winLoc.host]
7439   *           The window location host defaults to window.location.host
7440   *
7441   * @return   {boolean}
7442   *           Whether it is a cross domain request or not.
7443   */
7444
7445  var isCrossOrigin = function isCrossOrigin(url, winLoc) {
7446    if (winLoc === void 0) {
7447      winLoc = window$1.location;
7448    }
7449
7450    var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
7451
7452    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
7453    // IE8 doesn't know location.origin, so we won't rely on it here
7454
7455    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7456    return crossOrigin;
7457  };
7458
7459  var Url = /*#__PURE__*/Object.freeze({
7460    parseUrl: parseUrl,
7461    getAbsoluteURL: getAbsoluteURL,
7462    getFileExtension: getFileExtension,
7463    isCrossOrigin: isCrossOrigin
7464  });
7465
7466  var isFunction_1 = isFunction;
7467  var toString$1 = Object.prototype.toString;
7468
7469  function isFunction(fn) {
7470    var string = toString$1.call(fn);
7471    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
7472    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7473  }
7474
7475  /**
7476   * @license
7477   * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
7478   * Copyright (c) 2014 David Björklund
7479   * Available under the MIT license
7480   * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
7481   */
7482
7483
7484  var parseHeaders = function parseHeaders(headers) {
7485    var result = {};
7486
7487    if (!headers) {
7488      return result;
7489    }
7490
7491    headers.trim().split('\n').forEach(function (row) {
7492      var index = row.indexOf(':');
7493      var key = row.slice(0, index).trim().toLowerCase();
7494      var value = row.slice(index + 1).trim();
7495
7496      if (typeof result[key] === 'undefined') {
7497        result[key] = value;
7498      } else if (Array.isArray(result[key])) {
7499        result[key].push(value);
7500      } else {
7501        result[key] = [result[key], value];
7502      }
7503    });
vendor: 4,358 bytes, lines 7504-7670
7504    return result;
7505  };
7506
7507  var xhr = createXHR; // Allow use of default import syntax in TypeScript
7508
7509  var default_1 = createXHR;
7510  createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
7511  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
7512  forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
7513    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7514      options = initParams(uri, options, callback);
7515      options.method = method.toUpperCase();
7516      return _createXHR(options);
7517    };
7518  });
7519
7520  function forEachArray(array, iterator) {
7521    for (var i = 0; i < array.length; i++) {
7522      iterator(array[i]);
7523    }
7524  }
7525
7526  function isEmpty(obj) {
7527    for (var i in obj) {
7528      if (obj.hasOwnProperty(i)) return false;
7529    }
7530
7531    return true;
7532  }
7533
7534  function initParams(uri, options, callback) {
7535    var params = uri;
7536
7537    if (isFunction_1(options)) {
7538      callback = options;
7539
7540      if (typeof uri === "string") {
7541        params = {
7542          uri: uri
7543        };
7544      }
7545    } else {
7546      params = _extends_1({}, options, {
7547        uri: uri
7548      });
7549    }
7550
7551    params.callback = callback;
7552    return params;
7553  }
7554
7555  function createXHR(uri, options, callback) {
7556    options = initParams(uri, options, callback);
7557    return _createXHR(options);
7558  }
7559
7560  function _createXHR(options) {
7561    if (typeof options.callback === "undefined") {
7562      throw new Error("callback argument missing");
7563    }
7564
7565    var called = false;
7566
7567    var callback = function cbOnce(err, response, body) {
7568      if (!called) {
7569        called = true;
7570        options.callback(err, response, body);
7571      }
7572    };
7573
7574    function readystatechange() {
7575      if (xhr.readyState === 4) {
7576        setTimeout(loadFunc, 0);
7577      }
7578    }
7579
7580    function getBody() {
7581      // Chrome with requestType=blob throws errors arround when even testing access to responseText
7582      var body = undefined;
7583
7584      if (xhr.response) {
7585        body = xhr.response;
7586      } else {
7587        body = xhr.responseText || getXml(xhr);
7588      }
7589
7590      if (isJson) {
7591        try {
7592          body = JSON.parse(body);
7593        } catch (e) {}
7594      }
7595
7596      return body;
7597    }
7598
7599    function errorFunc(evt) {
7600      clearTimeout(timeoutTimer);
7601
7602      if (!(evt instanceof Error)) {
7603        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7604      }
7605
7606      evt.statusCode = 0;
7607      return callback(evt, failureResponse);
7608    } // will load the data & process the response in a special response object
7609
7610
7611    function loadFunc() {
7612      if (aborted) return;
7613      var status;
7614      clearTimeout(timeoutTimer);
7615
7616      if (options.useXDR && xhr.status === undefined) {
7617        //IE8 CORS GET successful response doesn't have a status field, but body is fine
7618        status = 200;
7619      } else {
7620        status = xhr.status === 1223 ? 204 : xhr.status;
7621      }
7622
7623      var response = failureResponse;
7624      var err = null;
7625
7626      if (status !== 0) {
7627        response = {
7628          body: getBody(),
7629          statusCode: status,
7630          method: method,
7631          headers: {},
7632          url: uri,
7633          rawRequest: xhr
7634        };
7635
7636        if (xhr.getAllResponseHeaders) {
7637          //remember xhr can in fact be XDR for CORS in IE
7638          response.headers = parseHeaders(xhr.getAllResponseHeaders());
7639        }
7640      } else {
7641        err = new Error("Internal XMLHttpRequest Error");
7642      }
7643
7644      return callback(err, response, response.body);
7645    }
7646
7647    var xhr = options.xhr || null;
7648
7649    if (!xhr) {
7650      if (options.cors || options.useXDR) {
7651        xhr = new createXHR.XDomainRequest();
7652      } else {
7653        xhr = new createXHR.XMLHttpRequest();
7654      }
7655    }
7656
7657    var key;
7658    var aborted;
7659    var uri = xhr.url = options.uri || options.url;
7660    var method = xhr.method = options.method || "GET";
7661    var body = options.body || options.data;
7662    var headers = xhr.headers = options.headers || {};
7663    var sync = !!options.sync;
7664    var isJson = false;
7665    var timeoutTimer;
7666    var failureResponse = {
7667      body: undefined,
7668      headers: {},
7669      statusCode: 0,
7670      method: method,
vendor: 4,117 bytes, lines 7671-7802
7671      url: uri,
7672      rawRequest: xhr
7673    };
7674
7675    if ("json" in options && options.json !== false) {
7676      isJson = true;
7677      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7678
7679      if (method !== "GET" && method !== "HEAD") {
7680        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7681
7682        body = JSON.stringify(options.json === true ? body : options.json);
7683      }
7684    }
7685
7686    xhr.onreadystatechange = readystatechange;
7687    xhr.onload = loadFunc;
7688    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
7689
7690    xhr.onprogress = function () {// IE must die
7691    };
7692
7693    xhr.onabort = function () {
7694      aborted = true;
7695    };
7696
7697    xhr.ontimeout = errorFunc;
7698    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
7699
7700    if (!sync) {
7701      xhr.withCredentials = !!options.withCredentials;
7702    } // Cannot set timeout with sync request
7703    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7704    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7705
7706
7707    if (!sync && options.timeout > 0) {
7708      timeoutTimer = setTimeout(function () {
7709        if (aborted) return;
7710        aborted = true; //IE9 may still call readystatechange
7711
7712        xhr.abort("timeout");
7713        var e = new Error("XMLHttpRequest timeout");
7714        e.code = "ETIMEDOUT";
7715        errorFunc(e);
7716      }, options.timeout);
7717    }
7718
7719    if (xhr.setRequestHeader) {
7720      for (key in headers) {
7721        if (headers.hasOwnProperty(key)) {
7722          xhr.setRequestHeader(key, headers[key]);
7723        }
7724      }
7725    } else if (options.headers && !isEmpty(options.headers)) {
7726      throw new Error("Headers cannot be set on an XDomainRequest object");
7727    }
7728
7729    if ("responseType" in options) {
7730      xhr.responseType = options.responseType;
7731    }
7732
7733    if ("beforeSend" in options && typeof options.beforeSend === "function") {
7734      options.beforeSend(xhr);
7735    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7736    // XMLHttpRequest spec says to pass null as body to indicate no body
7737    // See https://github.com/naugtur/xhr/issues/100.
7738
7739
7740    xhr.send(body || null);
7741    return xhr;
7742  }
7743
7744  function getXml(xhr) {
7745    // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
7746    // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
7747    try {
7748      if (xhr.responseType === "document") {
7749        return xhr.responseXML;
7750      }
7751
7752      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7753
7754      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7755        return xhr.responseXML;
7756      }
7757    } catch (e) {}
7758
7759    return null;
7760  }
7761
7762  function noop() {}
7763  xhr["default"] = default_1;
7764
7765  /**
7766   * Takes a webvtt file contents and parses it into cues
7767   *
7768   * @param {string} srcContent
7769   *        webVTT file contents
7770   *
7771   * @param {TextTrack} track
7772   *        TextTrack to add cues to. Cues come from the srcContent.
7773   *
7774   * @private
7775   */
7776
7777  var parseCues = function parseCues(srcContent, track) {
7778    var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
7779    var errors = [];
7780
7781    parser.oncue = function (cue) {
7782      track.addCue(cue);
7783    };
7784
7785    parser.onparsingerror = function (error) {
7786      errors.push(error);
7787    };
7788
7789    parser.onflush = function () {
7790      track.trigger({
7791        type: 'loadeddata',
7792        target: track
7793      });
7794    };
7795
7796    parser.parse(srcContent);
7797
7798    if (errors.length > 0) {
7799      if (window$1.console && window$1.console.groupCollapsed) {
7800        window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
7801      }
7802
7803      errors.forEach(function (error) {
7804        return log.error(error);
7805      });
7806
7807      if (window$1.console && window$1.console.groupEnd) {
7808        window$1.console.groupEnd();
7809      }
7810    }
7811
7812    parser.flush();
7813  };
7814  /**
7815   * Load a `TextTrack` from a specified url.
7816   *
7817   * @param {string} src
7818   *        Url to load track from.
7819   *
7820   * @param {TextTrack} track
7821   *        Track to add cues to. Comes from the content at the end of `url`.
7822   *
7823   * @private
7824   */
7825
7826
7827  var loadTrack = function loadTrack(src, track) {
7828    var opts = {
7829      uri: src
7830    };
7831    var crossOrigin = isCrossOrigin(src);
7832
7833    if (crossOrigin) {
7834      opts.cors = crossOrigin;
7835    }
7836
7837    xhr(opts, bind(this, function (err, response, responseBody) {
7838      if (err) {
7839        return log.error(err, response);
7840      }
7841
7842      track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7843      // NOTE: this is only used for the alt/video.novtt.js build
7844
7845      if (typeof window$1.WebVTT !== 'function') {
7846        if (track.tech_) {
7847          // to prevent use before define eslint error, we define loadHandler
7848          // as a let here
7849          track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) {
7850            if (event.type === 'vttjserror') {
7851              log.error("vttjs failed to load, stopping trying to process " + track.src);
7852              return;
7853            }
7854
7855            return parseCues(responseBody, track);
7856          });
7857        }
7858      } else {
7859        parseCues(responseBody, track);
7860      }
7861    }));
7862  };
7863  /**
7864   * A representation of a single `TextTrack`.
7865   *
7866   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7867   * @extends Track
7868   */
7869
7870
7871  var TextTrack =
7872  /*#__PURE__*/
7873  function (_Track) {
7874    inheritsLoose(TextTrack, _Track);
7875
7876    /**
7877     * Create an instance of this class.
7878     *
7879     * @param {Object} options={}
7880     *        Object of option names and values
7881     *
7882     * @param {Tech} options.tech
7883     *        A reference to the tech that owns this TextTrack.
7884     *
7885     * @param {TextTrack~Kind} [options.kind='subtitles']
7886     *        A valid text track kind.
7887     *
7888     * @param {TextTrack~Mode} [options.mode='disabled']
7889     *        A valid text track mode.
7890     *
7891     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7892     *        A unique id for this TextTrack.
7893     *
7894     * @param {string} [options.label='']
7895     *        The menu label for this track.
7896     *
7897     * @param {string} [options.language='']
7898     *        A valid two character language code.
7899     *
7900     * @param {string} [options.srclang='']
7901     *        A valid two character language code. An alternative, but deprioritized
7902     *        version of `options.language`
7903     *
7904     * @param {string} [options.src]
7905     *        A url to TextTrack cues.
7906     *
7907     * @param {boolean} [options.default]
7908     *        If this track should default to on or off.
7909     */
7910    function TextTrack(options) {
7911      var _this;
7912
7913      if (options === void 0) {
7914        options = {};
7915      }
7916
7917      if (!options.tech) {
7918        throw new Error('A tech was not provided.');
7919      }
7920
7921      var settings = mergeOptions(options, {
7922        kind: TextTrackKind[options.kind] || 'subtitles',
7923        language: options.language || options.srclang || ''
7924      });
7925      var mode = TextTrackMode[settings.mode] || 'disabled';
7926      var default_ = settings["default"];
7927
7928      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7929        mode = 'hidden';
7930      }
7931
7932      _this = _Track.call(this, settings) || this;
7933      _this.tech_ = settings.tech;
7934      _this.cues_ = [];
7935      _this.activeCues_ = [];
7936      _this.preload_ = _this.tech_.preloadTextTracks !== false;
7937      var cues = new TextTrackCueList(_this.cues_);
7938      var activeCues = new TextTrackCueList(_this.activeCues_);
7939      var changed = false;
7940      var timeupdateHandler = bind(assertThisInitialized(_this), function () {
7941        // Accessing this.activeCues for the side-effects of updating itself
7942        // due to its nature as a getter function. Do not remove or cues will
7943        // stop updating!
vendor: 8,677 bytes, lines 7944-8243
7944        // Use the setter to prevent deletion from uglify (pure_getters rule)
7945        this.activeCues = this.activeCues;
7946
7947        if (changed) {
7948          this.trigger('cuechange');
7949          changed = false;
7950        }
7951      });
7952
7953      if (mode !== 'disabled') {
7954        _this.tech_.ready(function () {
7955          _this.tech_.on('timeupdate', timeupdateHandler);
7956        }, true);
7957      }
7958
7959      Object.defineProperties(assertThisInitialized(_this), {
7960        /**
7961         * @memberof TextTrack
7962         * @member {boolean} default
7963         *         If this track was set to be on or off by default. Cannot be changed after
7964         *         creation.
7965         * @instance
7966         *
7967         * @readonly
7968         */
7969        "default": {
7970          get: function get() {
7971            return default_;
7972          },
7973          set: function set() {}
7974        },
7975
7976        /**
7977         * @memberof TextTrack
7978         * @member {string} mode
7979         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7980         *         not be set if setting to an invalid mode.
7981         * @instance
7982         *
7983         * @fires TextTrack#modechange
7984         */
7985        mode: {
7986          get: function get() {
7987            return mode;
7988          },
7989          set: function set(newMode) {
7990            var _this2 = this;
7991
7992            if (!TextTrackMode[newMode]) {
7993              return;
7994            }
7995
7996            mode = newMode;
7997
7998            if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
7999              // On-demand load.
8000              loadTrack(this.src, this);
8001            }
8002
8003            if (mode !== 'disabled') {
8004              this.tech_.ready(function () {
8005                _this2.tech_.on('timeupdate', timeupdateHandler);
8006              }, true);
8007            } else {
8008              this.tech_.off('timeupdate', timeupdateHandler);
8009            }
8010            /**
8011             * An event that fires when mode changes on this track. This allows
8012             * the TextTrackList that holds this track to act accordingly.
8013             *
8014             * > Note: This is not part of the spec!
8015             *
8016             * @event TextTrack#modechange
8017             * @type {EventTarget~Event}
8018             */
8019
8020
8021            this.trigger('modechange');
8022          }
8023        },
8024
8025        /**
8026         * @memberof TextTrack
8027         * @member {TextTrackCueList} cues
8028         *         The text track cue list for this TextTrack.
8029         * @instance
8030         */
8031        cues: {
8032          get: function get() {
8033            if (!this.loaded_) {
8034              return null;
8035            }
8036
8037            return cues;
8038          },
8039          set: function set() {}
8040        },
8041
8042        /**
8043         * @memberof TextTrack
8044         * @member {TextTrackCueList} activeCues
8045         *         The list text track cues that are currently active for this TextTrack.
8046         * @instance
8047         */
8048        activeCues: {
8049          get: function get() {
8050            if (!this.loaded_) {
8051              return null;
8052            } // nothing to do
8053
8054
8055            if (this.cues.length === 0) {
8056              return activeCues;
8057            }
8058
8059            var ct = this.tech_.currentTime();
8060            var active = [];
8061
8062            for (var i = 0, l = this.cues.length; i < l; i++) {
8063              var cue = this.cues[i];
8064
8065              if (cue.startTime <= ct && cue.endTime >= ct) {
8066                active.push(cue);
8067              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
8068                active.push(cue);
8069              }
8070            }
8071
8072            changed = false;
8073
8074            if (active.length !== this.activeCues_.length) {
8075              changed = true;
8076            } else {
8077              for (var _i = 0; _i < active.length; _i++) {
8078                if (this.activeCues_.indexOf(active[_i]) === -1) {
8079                  changed = true;
8080                }
8081              }
8082            }
8083
8084            this.activeCues_ = active;
8085            activeCues.setCues_(this.activeCues_);
8086            return activeCues;
8087          },
8088          // /!\ Keep this setter empty (see the timeupdate handler above)
8089          set: function set() {}
8090        }
8091      });
8092
8093      if (settings.src) {
8094        _this.src = settings.src;
8095
8096        if (!_this.preload_) {
8097          // Tracks will load on-demand.
8098          // Act like we're loaded for other purposes.
8099          _this.loaded_ = true;
8100        }
8101
8102        if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
8103          loadTrack(_this.src, assertThisInitialized(_this));
8104        }
8105      } else {
8106        _this.loaded_ = true;
8107      }
8108
8109      return _this;
8110    }
8111    /**
8112     * Add a cue to the internal list of cues.
8113     *
8114     * @param {TextTrack~Cue} cue
8115     *        The cue to add to our internal list
8116     */
8117
8118
8119    var _proto = TextTrack.prototype;
8120
8121    _proto.addCue = function addCue(originalCue) {
8122      var cue = originalCue;
8123
8124      if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
8125        cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8126
8127        for (var prop in originalCue) {
8128          if (!(prop in cue)) {
8129            cue[prop] = originalCue[prop];
8130          }
8131        } // make sure that `id` is copied over
8132
8133
8134        cue.id = originalCue.id;
8135        cue.originalCue_ = originalCue;
8136      }
8137
8138      var tracks = this.tech_.textTracks();
8139
8140      for (var i = 0; i < tracks.length; i++) {
8141        if (tracks[i] !== this) {
8142          tracks[i].removeCue(cue);
8143        }
8144      }
8145
8146      this.cues_.push(cue);
8147      this.cues.setCues_(this.cues_);
8148    }
8149    /**
8150     * Remove a cue from our internal list
8151     *
8152     * @param {TextTrack~Cue} removeCue
8153     *        The cue to remove from our internal list
8154     */
8155    ;
8156
8157    _proto.removeCue = function removeCue(_removeCue) {
8158      var i = this.cues_.length;
8159
8160      while (i--) {
8161        var cue = this.cues_[i];
8162
8163        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
8164          this.cues_.splice(i, 1);
8165          this.cues.setCues_(this.cues_);
8166          break;
8167        }
8168      }
8169    };
8170
8171    return TextTrack;
8172  }(Track);
8173  /**
8174   * cuechange - One or more cues in the track have become active or stopped being active.
8175   */
8176
8177
8178  TextTrack.prototype.allowedEvents_ = {
8179    cuechange: 'cuechange'
8180  };
8181
8182  /**
8183   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8184   * only one `AudioTrack` in the list will be enabled at a time.
8185   *
8186   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8187   * @extends Track
8188   */
8189
8190  var AudioTrack =
8191  /*#__PURE__*/
8192  function (_Track) {
8193    inheritsLoose(AudioTrack, _Track);
8194
8195    /**
8196     * Create an instance of this class.
8197     *
8198     * @param {Object} [options={}]
8199     *        Object of option names and values
8200     *
8201     * @param {AudioTrack~Kind} [options.kind='']
8202     *        A valid audio track kind
8203     *
8204     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8205     *        A unique id for this AudioTrack.
8206     *
8207     * @param {string} [options.label='']
8208     *        The menu label for this track.
8209     *
8210     * @param {string} [options.language='']
8211     *        A valid two character language code.
8212     *
8213     * @param {boolean} [options.enabled]
8214     *        If this track is the one that is currently playing. If this track is part of
8215     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8216     */
8217    function AudioTrack(options) {
8218      var _this;
8219
8220      if (options === void 0) {
8221        options = {};
8222      }
8223
8224      var settings = mergeOptions(options, {
8225        kind: AudioTrackKind[options.kind] || ''
8226      });
8227      _this = _Track.call(this, settings) || this;
8228      var enabled = false;
8229      /**
8230       * @memberof AudioTrack
8231       * @member {boolean} enabled
8232       *         If this `AudioTrack` is enabled or not. When setting this will
8233       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8234       * @instance
8235       *
8236       * @fires VideoTrack#selectedchange
8237       */
8238
8239      Object.defineProperty(assertThisInitialized(_this), 'enabled', {
8240        get: function get() {
8241          return enabled;
8242        },
8243        set: function set(newEnabled) {
8244          // an invalid or unchanged value
8245          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8246            return;
8247          }
8248
8249          enabled = newEnabled;
8250          /**
8251           * An event that fires when enabled changes on this track. This allows
8252           * the AudioTrackList that holds this track to act accordingly.
8253           *
8254           * > Note: This is not part of the spec! Native tracks will do
8255           *         this internally without an event.
8256           *
8257           * @event AudioTrack#enabledchange
8258           * @type {EventTarget~Event}
8259           */
8260
8261          this.trigger('enabledchange');
8262        }
8263      }); // if the user sets this track to selected then
8264      // set selected to that true value otherwise
8265      // we keep it false
8266
8267      if (settings.enabled) {
8268        _this.enabled = settings.enabled;
8269      }
8270
8271      _this.loaded_ = true;
8272      return _this;
8273    }
8274
8275    return AudioTrack;
8276  }(Track);
8277
8278  /**
8279   * A representation of a single `VideoTrack`.
8280   *
8281   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8282   * @extends Track
8283   */
8284
8285  var VideoTrack =
8286  /*#__PURE__*/
8287  function (_Track) {
8288    inheritsLoose(VideoTrack, _Track);
8289
8290    /**
8291     * Create an instance of this class.
8292     *
8293     * @param {Object} [options={}]
8294     *        Object of option names and values
8295     *
8296     * @param {string} [options.kind='']
8297     *        A valid {@link VideoTrack~Kind}
8298     *
8299     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8300     *        A unique id for this AudioTrack.
8301     *
8302     * @param {string} [options.label='']
8303     *        The menu label for this track.
8304     *
8305     * @param {string} [options.language='']
8306     *        A valid two character language code.
8307     *
8308     * @param {boolean} [options.selected]
8309     *        If this track is the one that is currently playing.
8310     */
8311    function VideoTrack(options) {
8312      var _this;
8313
8314      if (options === void 0) {
8315        options = {};
8316      }
8317
8318      var settings = mergeOptions(options, {
8319        kind: VideoTrackKind[options.kind] || ''
8320      });
8321      _this = _Track.call(this, settings) || this;
8322      var selected = false;
8323      /**
8324       * @memberof VideoTrack
8325       * @member {boolean} selected
8326       *         If this `VideoTrack` is selected or not. When setting this will
8327       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8328       * @instance
8329       *
8330       * @fires VideoTrack#selectedchange
8331       */
8332
8333      Object.defineProperty(assertThisInitialized(_this), 'selected', {
8334        get: function get() {
8335          return selected;
8336        },
8337        set: function set(newSelected) {
8338          // an invalid or unchanged value
8339          if (typeof newSelected !== 'boolean' || newSelected === selected) {
8340            return;
8341          }
8342
8343          selected = newSelected;
8344          /**
8345           * An event that fires when selected changes on this track. This allows
8346           * the VideoTrackList that holds this track to act accordingly.
8347           *
8348           * > Note: This is not part of the spec! Native tracks will do
8349           *         this internally without an event.
8350           *
8351           * @event VideoTrack#selectedchange
8352           * @type {EventTarget~Event}
8353           */
8354
8355          this.trigger('selectedchange');
8356        }
8357      }); // if the user sets this track to selected then
8358      // set selected to that true value otherwise
8359      // we keep it false
8360
8361      if (settings.selected) {
8362        _this.selected = settings.selected;
8363      }
8364
8365      return _this;
8366    }
8367
8368    return VideoTrack;
8369  }(Track);
8370
8371  /**
8372   * @memberof HTMLTrackElement
8373   * @typedef {HTMLTrackElement~ReadyState}
8374   * @enum {number}
8375   */
8376
8377  var NONE = 0;
8378  var LOADING = 1;
8379  var LOADED = 2;
8380  var ERROR = 3;
8381  /**
8382   * A single track represented in the DOM.
8383   *
8384   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8385   * @extends EventTarget
8386   */
8387
8388  var HTMLTrackElement =
8389  /*#__PURE__*/
8390  function (_EventTarget) {
8391    inheritsLoose(HTMLTrackElement, _EventTarget);
8392
8393    /**
8394     * Create an instance of this class.
8395     *
8396     * @param {Object} options={}
8397     *        Object of option names and values
8398     *
8399     * @param {Tech} options.tech
8400     *        A reference to the tech that owns this HTMLTrackElement.
8401     *
8402     * @param {TextTrack~Kind} [options.kind='subtitles']
8403     *        A valid text track kind.
8404     *
8405     * @param {TextTrack~Mode} [options.mode='disabled']
8406     *        A valid text track mode.
8407     *
8408     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8409     *        A unique id for this TextTrack.
8410     *
8411     * @param {string} [options.label='']
8412     *        The menu label for this track.
8413     *
8414     * @param {string} [options.language='']
8415     *        A valid two character language code.
8416     *
8417     * @param {string} [options.srclang='']
8418     *        A valid two character language code. An alternative, but deprioritized
8419     *        vesion of `options.language`
8420     *
8421     * @param {string} [options.src]
8422     *        A url to TextTrack cues.
8423     *
8424     * @param {boolean} [options.default]
8425     *        If this track should default to on or off.
8426     */
8427    function HTMLTrackElement(options) {
8428      var _this;
8429
8430      if (options === void 0) {
8431        options = {};
8432      }
8433
8434      _this = _EventTarget.call(this) || this;
8435      var readyState;
8436      var track = new TextTrack(options);
8437      _this.kind = track.kind;
8438      _this.src = track.src;
8439      _this.srclang = track.language;
8440      _this.label = track.label;
8441      _this["default"] = track["default"];
8442      Object.defineProperties(assertThisInitialized(_this), {
8443        /**
8444         * @memberof HTMLTrackElement
8445         * @member {HTMLTrackElement~ReadyState} readyState
8446         *         The current ready state of the track element.
8447         * @instance
8448         */
8449        readyState: {
8450          get: function get() {
8451            return readyState;
8452          }
8453        },
8454
8455        /**
8456         * @memberof HTMLTrackElement
8457         * @member {TextTrack} track
8458         *         The underlying TextTrack object.
8459         * @instance
8460         *
8461         */
8462        track: {
8463          get: function get() {
8464            return track;
8465          }
8466        }
8467      });
8468      readyState = NONE;
8469      /**
8470       * @listens TextTrack#loadeddata
8471       * @fires HTMLTrackElement#load
8472       */
8473
8474      track.addEventListener('loadeddata', function () {
8475        readyState = LOADED;
8476
8477        _this.trigger({
8478          type: 'load',
8479          target: assertThisInitialized(_this)
8480        });
8481      });
8482      return _this;
8483    }
8484
8485    return HTMLTrackElement;
8486  }(EventTarget);
8487
8488  HTMLTrackElement.prototype.allowedEvents_ = {
8489    load: 'load'
8490  };
8491  HTMLTrackElement.NONE = NONE;
8492  HTMLTrackElement.LOADING = LOADING;
8493  HTMLTrackElement.LOADED = LOADED;
8494  HTMLTrackElement.ERROR = ERROR;
8495
8496  /*
8497   * This file contains all track properties that are used in
8498   * player.js, tech.js, html5.js and possibly other techs in the future.
8499   */
8500
8501  var NORMAL = {
8502    audio: {
8503      ListClass: AudioTrackList,
8504      TrackClass: AudioTrack,
8505      capitalName: 'Audio'
8506    },
8507    video: {
8508      ListClass: VideoTrackList,
8509      TrackClass: VideoTrack,
8510      capitalName: 'Video'
8511    },
8512    text: {
8513      ListClass: TextTrackList,
8514      TrackClass: TextTrack,
8515      capitalName: 'Text'
8516    }
8517  };
8518  Object.keys(NORMAL).forEach(function (type) {
8519    NORMAL[type].getterName = type + "Tracks";
8520    NORMAL[type].privateName = type + "Tracks_";
8521  });
8522  var REMOTE = {
8523    remoteText: {
8524      ListClass: TextTrackList,
8525      TrackClass: TextTrack,
8526      capitalName: 'RemoteText',
8527      getterName: 'remoteTextTracks',
8528      privateName: 'remoteTextTracks_'
8529    },
8530    remoteTextEl: {
8531      ListClass: HtmlTrackElementList,
8532      TrackClass: HTMLTrackElement,
8533      capitalName: 'RemoteTextTrackEls',
8534      getterName: 'remoteTextTrackEls',
8535      privateName: 'remoteTextTrackEls_'
8536    }
8537  };
8538
8539  var ALL = _extends_1({}, NORMAL, REMOTE);
8540
8541  REMOTE.names = Object.keys(REMOTE);
8542  NORMAL.names = Object.keys(NORMAL);
8543  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8544
8545  /**
8546   * Copyright 2013 vtt.js Contributors
8547   *
8548   * Licensed under the Apache License, Version 2.0 (the "License");
vendor: 5,646 bytes, lines 8549-8727
8549   * you may not use this file except in compliance with the License.
8550   * You may obtain a copy of the License at
8551   *
8552   *   http://www.apache.org/licenses/LICENSE-2.0
8553   *
8554   * Unless required by applicable law or agreed to in writing, software
8555   * distributed under the License is distributed on an "AS IS" BASIS,
8556   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8557   * See the License for the specific language governing permissions and
8558   * limitations under the License.
8559   */
8560
8561  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
8562
8563  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
8564
8565  var _objCreate = Object.create || function () {
8566    function F() {}
8567
8568    return function (o) {
8569      if (arguments.length !== 1) {
8570        throw new Error('Object.create shim only accepts one parameter.');
8571      }
8572
8573      F.prototype = o;
8574      return new F();
8575    };
8576  }(); // Creates a new ParserError object from an errorData object. The errorData
8577  // object should have default code and message properties. The default message
8578  // property can be overriden by passing in a message parameter.
8579  // See ParsingError.Errors below for acceptable errors.
8580
8581
8582  function ParsingError(errorData, message) {
8583    this.name = "ParsingError";
8584    this.code = errorData.code;
8585    this.message = message || errorData.message;
8586  }
8587
8588  ParsingError.prototype = _objCreate(Error.prototype);
8589  ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
8590
8591  ParsingError.Errors = {
8592    BadSignature: {
8593      code: 0,
8594      message: "Malformed WebVTT signature."
8595    },
8596    BadTimeStamp: {
8597      code: 1,
8598      message: "Malformed time stamp."
8599    }
8600  }; // Try to parse input as a time stamp.
8601
8602  function parseTimeStamp(input) {
8603    function computeSeconds(h, m, s, f) {
8604      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8605    }
8606
8607    var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
8608
8609    if (!m) {
8610      return null;
8611    }
8612
8613    if (m[3]) {
8614      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8615      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8616    } else if (m[1] > 59) {
8617      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8618      // First position is hours as it's over 59.
8619      return computeSeconds(m[1], m[2], 0, m[4]);
8620    } else {
8621      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8622      return computeSeconds(0, m[1], m[2], m[4]);
8623    }
8624  } // A settings object holds key/value pairs and will ignore anything but the first
8625  // assignment to a specific key.
8626
8627
8628  function Settings() {
8629    this.values = _objCreate(null);
8630  }
8631
8632  Settings.prototype = {
8633    // Only accept the first assignment to any key.
8634    set: function set(k, v) {
8635      if (!this.get(k) && v !== "") {
8636        this.values[k] = v;
8637      }
8638    },
8639    // Return the value for a key, or a default value.
8640    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8641    // a number of possible default values as properties where 'defaultKey' is
8642    // the key of the property that will be chosen; otherwise it's assumed to be
8643    // a single value.
8644    get: function get(k, dflt, defaultKey) {
8645      if (defaultKey) {
8646        return this.has(k) ? this.values[k] : dflt[defaultKey];
8647      }
8648
8649      return this.has(k) ? this.values[k] : dflt;
8650    },
8651    // Check whether we have a value for a key.
8652    has: function has(k) {
8653      return k in this.values;
8654    },
8655    // Accept a setting if its one of the given alternatives.
8656    alt: function alt(k, v, a) {
8657      for (var n = 0; n < a.length; ++n) {
8658        if (v === a[n]) {
8659          this.set(k, v);
8660          break;
8661        }
8662      }
8663    },
8664    // Accept a setting if its a valid (signed) integer.
8665    integer: function integer(k, v) {
8666      if (/^-?\d+$/.test(v)) {
8667        // integer
8668        this.set(k, parseInt(v, 10));
8669      }
8670    },
8671    // Accept a setting if its a valid percentage.
8672    percent: function percent(k, v) {
8673      var m;
8674
8675      if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8676        v = parseFloat(v);
8677
8678        if (v >= 0 && v <= 100) {
8679          this.set(k, v);
8680          return true;
8681        }
8682      }
8683
8684      return false;
8685    }
8686  }; // Helper function to parse input into groups separated by 'groupDelim', and
8687  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8688
8689  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8690    var groups = groupDelim ? input.split(groupDelim) : [input];
8691
8692    for (var i in groups) {
8693      if (typeof groups[i] !== "string") {
8694        continue;
8695      }
8696
8697      var kv = groups[i].split(keyValueDelim);
8698
8699      if (kv.length !== 2) {
8700        continue;
8701      }
8702
8703      var k = kv[0];
8704      var v = kv[1];
8705      callback(k, v);
8706    }
8707  }
8708
8709  function parseCue(input, cue, regionList) {
8710    // Remember the original input if we need to throw an error.
8711    var oInput = input; // 4.1 WebVTT timestamp
8712
8713    function consumeTimeStamp() {
8714      var ts = parseTimeStamp(input);
8715
8716      if (ts === null) {
8717        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8718      } // Remove time stamp from input.
8719
8720
8721      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8722      return ts;
8723    } // 4.4.2 WebVTT cue settings
8724
8725
8726    function consumeCueSettings(input, cue) {
8727      var settings = new Settings();
vendor: 26,221 bytes, lines 8728-9480
8728      parseOptions(input, function (k, v) {
8729        switch (k) {
8730          case "region":
8731            // Find the last region we parsed with the same region id.
8732            for (var i = regionList.length - 1; i >= 0; i--) {
8733              if (regionList[i].id === v) {
8734                settings.set(k, regionList[i].region);
8735                break;
8736              }
8737            }
8738
8739            break;
8740
8741          case "vertical":
8742            settings.alt(k, v, ["rl", "lr"]);
8743            break;
8744
8745          case "line":
8746            var vals = v.split(","),
8747                vals0 = vals[0];
8748            settings.integer(k, vals0);
8749            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8750            settings.alt(k, vals0, ["auto"]);
8751
8752            if (vals.length === 2) {
8753              settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
8754            }
8755
8756            break;
8757
8758          case "position":
8759            vals = v.split(",");
8760            settings.percent(k, vals[0]);
8761
8762            if (vals.length === 2) {
8763              settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
8764            }
8765
8766            break;
8767
8768          case "size":
8769            settings.percent(k, v);
8770            break;
8771
8772          case "align":
8773            settings.alt(k, v, ["start", "center", "end", "left", "right"]);
8774            break;
8775        }
8776      }, /:/, /\s/); // Apply default values for any missing fields.
8777
8778      cue.region = settings.get("region", null);
8779      cue.vertical = settings.get("vertical", "");
8780
8781      try {
8782        cue.line = settings.get("line", "auto");
8783      } catch (e) {}
8784
8785      cue.lineAlign = settings.get("lineAlign", "start");
8786      cue.snapToLines = settings.get("snapToLines", true);
8787      cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center
8788
8789      try {
8790        cue.align = settings.get("align", "center");
8791      } catch (e) {
8792        cue.align = settings.get("align", "middle");
8793      }
8794
8795      try {
8796        cue.position = settings.get("position", "auto");
8797      } catch (e) {
8798        cue.position = settings.get("position", {
8799          start: 0,
8800          left: 0,
8801          center: 50,
8802          middle: 50,
8803          end: 100,
8804          right: 100
8805        }, cue.align);
8806      }
8807
8808      cue.positionAlign = settings.get("positionAlign", {
8809        start: "start",
8810        left: "start",
8811        center: "center",
8812        middle: "center",
8813        end: "end",
8814        right: "end"
8815      }, cue.align);
8816    }
8817
8818    function skipWhitespace() {
8819      input = input.replace(/^\s+/, "");
8820    } // 4.1 WebVTT cue timings.
8821
8822
8823    skipWhitespace();
8824    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8825
8826    skipWhitespace();
8827
8828    if (input.substr(0, 3) !== "-->") {
8829      // (3) next characters must match "-->"
8830      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8831    }
8832
8833    input = input.substr(3);
8834    skipWhitespace();
8835    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8836    // 4.1 WebVTT cue settings list.
8837
8838    skipWhitespace();
8839    consumeCueSettings(input, cue);
8840  }
8841
8842  var TEXTAREA_ELEMENT = document.createElement("textarea");
8843  var TAG_NAME = {
8844    c: "span",
8845    i: "i",
8846    b: "b",
8847    u: "u",
8848    ruby: "ruby",
8849    rt: "rt",
8850    v: "span",
8851    lang: "span"
8852  }; // 5.1 default text color
8853  // 5.2 default text background color is equivalent to text color with bg_ prefix
8854
8855  var DEFAULT_COLOR_CLASS = {
8856    white: 'rgba(255,255,255,1)',
8857    lime: 'rgba(0,255,0,1)',
8858    cyan: 'rgba(0,255,255,1)',
8859    red: 'rgba(255,0,0,1)',
8860    yellow: 'rgba(255,255,0,1)',
8861    magenta: 'rgba(255,0,255,1)',
8862    blue: 'rgba(0,0,255,1)',
8863    black: 'rgba(0,0,0,1)'
8864  };
8865  var TAG_ANNOTATION = {
8866    v: "title",
8867    lang: "lang"
8868  };
8869  var NEEDS_PARENT = {
8870    rt: "ruby"
8871  }; // Parse content into a document fragment.
8872
8873  function parseContent(window, input) {
8874    function nextToken() {
8875      // Check for end-of-string.
8876      if (!input) {
8877        return null;
8878      } // Consume 'n' characters from the input.
8879
8880
8881      function consume(result) {
8882        input = input.substr(result.length);
8883        return result;
8884      }
8885
8886      var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
8887      // the tag.
8888
8889      return consume(m[1] ? m[1] : m[2]);
8890    }
8891
8892    function unescape(s) {
8893      TEXTAREA_ELEMENT.innerHTML = s;
8894      s = TEXTAREA_ELEMENT.textContent;
8895      TEXTAREA_ELEMENT.textContent = "";
8896      return s;
8897    }
8898
8899    function shouldAdd(current, element) {
8900      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8901    } // Create an element for this tag.
8902
8903
8904    function createElement(type, annotation) {
8905      var tagName = TAG_NAME[type];
8906
8907      if (!tagName) {
8908        return null;
8909      }
8910
8911      var element = window.document.createElement(tagName);
8912      var name = TAG_ANNOTATION[type];
8913
8914      if (name && annotation) {
8915        element[name] = annotation.trim();
8916      }
8917
8918      return element;
8919    }
8920
8921    var rootDiv = window.document.createElement("div"),
8922        current = rootDiv,
8923        t,
8924        tagStack = [];
8925
8926    while ((t = nextToken()) !== null) {
8927      if (t[0] === '<') {
8928        if (t[1] === "/") {
8929          // If the closing tag matches, move back up to the parent node.
8930          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8931            tagStack.pop();
8932            current = current.parentNode;
8933          } // Otherwise just ignore the end tag.
8934
8935
8936          continue;
8937        }
8938
8939        var ts = parseTimeStamp(t.substr(1, t.length - 2));
8940        var node;
8941
8942        if (ts) {
8943          // Timestamps are lead nodes as well.
8944          node = window.document.createProcessingInstruction("timestamp", ts);
8945          current.appendChild(node);
8946          continue;
8947        }
8948
8949        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
8950
8951        if (!m) {
8952          continue;
8953        } // Try to construct an element, and ignore the tag if we couldn't.
8954
8955
8956        node = createElement(m[1], m[3]);
8957
8958        if (!node) {
8959          continue;
8960        } // Determine if the tag should be added based on the context of where it
8961        // is placed in the cuetext.
8962
8963
8964        if (!shouldAdd(current, node)) {
8965          continue;
8966        } // Set the class list (as a list of classes, separated by space).
8967
8968
8969        if (m[2]) {
8970          var classes = m[2].split('.');
8971          classes.forEach(function (cl) {
8972            var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color
8973
8974            var colorName = bgColor ? cl.slice(3) : cl;
8975
8976            if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
8977              var propName = bgColor ? 'background-color' : 'color';
8978              var propValue = DEFAULT_COLOR_CLASS[colorName];
8979              node.style[propName] = propValue;
8980            }
8981          });
8982          node.className = classes.join(' ');
8983        } // Append the node to the current node, and enter the scope of the new
8984        // node.
8985
8986
8987        tagStack.push(m[1]);
8988        current.appendChild(node);
8989        current = node;
8990        continue;
8991      } // Text nodes are leaf nodes.
8992
8993
8994      current.appendChild(window.document.createTextNode(unescape(t)));
8995    }
8996
8997    return rootDiv;
8998  } // This is a list of all the Unicode characters that have a strong
8999  // right-to-left category. What this means is that these characters are
9000  // written right-to-left for sure. It was generated by pulling all the strong
9001  // right-to-left characters out of the Unicode data table. That table can
9002  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
9003
9004
9005  var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
9006
9007  function isStrongRTLChar(charCode) {
9008    for (var i = 0; i < strongRTLRanges.length; i++) {
9009      var currentRange = strongRTLRanges[i];
9010
9011      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
9012        return true;
9013      }
9014    }
9015
9016    return false;
9017  }
9018
9019  function determineBidi(cueDiv) {
9020    var nodeStack = [],
9021        text = "",
9022        charCode;
9023
9024    if (!cueDiv || !cueDiv.childNodes) {
9025      return "ltr";
9026    }
9027
9028    function pushNodes(nodeStack, node) {
9029      for (var i = node.childNodes.length - 1; i >= 0; i--) {
9030        nodeStack.push(node.childNodes[i]);
9031      }
9032    }
9033
9034    function nextTextNode(nodeStack) {
9035      if (!nodeStack || !nodeStack.length) {
9036        return null;
9037      }
9038
9039      var node = nodeStack.pop(),
9040          text = node.textContent || node.innerText;
9041
9042      if (text) {
9043        // TODO: This should match all unicode type B characters (paragraph
9044        // separator characters). See issue #115.
9045        var m = text.match(/^.*(\n|\r)/);
9046
9047        if (m) {
9048          nodeStack.length = 0;
9049          return m[0];
9050        }
9051
9052        return text;
9053      }
9054
9055      if (node.tagName === "ruby") {
9056        return nextTextNode(nodeStack);
9057      }
9058
9059      if (node.childNodes) {
9060        pushNodes(nodeStack, node);
9061        return nextTextNode(nodeStack);
9062      }
9063    }
9064
9065    pushNodes(nodeStack, cueDiv);
9066
9067    while (text = nextTextNode(nodeStack)) {
9068      for (var i = 0; i < text.length; i++) {
9069        charCode = text.charCodeAt(i);
9070
9071        if (isStrongRTLChar(charCode)) {
9072          return "rtl";
9073        }
9074      }
9075    }
9076
9077    return "ltr";
9078  }
9079
9080  function computeLinePos(cue) {
9081    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
9082      return cue.line;
9083    }
9084
9085    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
9086      return -1;
9087    }
9088
9089    var track = cue.track,
9090        trackList = track.textTrackList,
9091        count = 0;
9092
9093    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
9094      if (trackList[i].mode === "showing") {
9095        count++;
9096      }
9097    }
9098
9099    return ++count * -1;
9100  }
9101
9102  function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
9103  // div on 'this'.
9104
9105
9106  StyleBox.prototype.applyStyles = function (styles, div) {
9107    div = div || this.div;
9108
9109    for (var prop in styles) {
9110      if (styles.hasOwnProperty(prop)) {
9111        div.style[prop] = styles[prop];
9112      }
9113    }
9114  };
9115
9116  StyleBox.prototype.formatStyle = function (val, unit) {
9117    return val === 0 ? 0 : val + unit;
9118  }; // Constructs the computed display state of the cue (a div). Places the div
9119  // into the overlay which should be a block level element (usually a div).
9120
9121
9122  function CueStyleBox(window, cue, styleOptions) {
9123    StyleBox.call(this);
9124    this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
9125    // have inline positioning and will function as the cue background box.
9126
9127    this.cueDiv = parseContent(window, cue.text);
9128    var styles = {
9129      color: "rgba(255, 255, 255, 1)",
9130      backgroundColor: "rgba(0, 0, 0, 0.8)",
9131      position: "relative",
9132      left: 0,
9133      right: 0,
9134      top: 0,
9135      bottom: 0,
9136      display: "inline",
9137      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9138      unicodeBidi: "plaintext"
9139    };
9140    this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
9141    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
9142    // mirrors of them except middle instead of center on Safari.
9143
9144    this.div = window.document.createElement("div");
9145    styles = {
9146      direction: determineBidi(this.cueDiv),
9147      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9148      unicodeBidi: "plaintext",
9149      textAlign: cue.align === "middle" ? "center" : cue.align,
9150      font: styleOptions.font,
9151      whiteSpace: "pre-line",
9152      position: "absolute"
9153    };
9154    this.applyStyles(styles);
9155    this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
9156    // position of the cue box. The reference edge will be resolved later when
9157    // the box orientation styles are applied.
9158
9159    var textPos = 0;
9160
9161    switch (cue.positionAlign) {
9162      case "start":
9163        textPos = cue.position;
9164        break;
9165
9166      case "center":
9167        textPos = cue.position - cue.size / 2;
9168        break;
9169
9170      case "end":
9171        textPos = cue.position - cue.size;
9172        break;
9173    } // Horizontal box orientation; textPos is the distance from the left edge of the
9174    // area to the left edge of the box and cue.size is the distance extending to
9175    // the right from there.
9176
9177
9178    if (cue.vertical === "") {
9179      this.applyStyles({
9180        left: this.formatStyle(textPos, "%"),
9181        width: this.formatStyle(cue.size, "%")
9182      }); // Vertical box orientation; textPos is the distance from the top edge of the
9183      // area to the top edge of the box and cue.size is the height extending
9184      // downwards from there.
9185    } else {
9186      this.applyStyles({
9187        top: this.formatStyle(textPos, "%"),
9188        height: this.formatStyle(cue.size, "%")
9189      });
9190    }
9191
9192    this.move = function (box) {
9193      this.applyStyles({
9194        top: this.formatStyle(box.top, "px"),
9195        bottom: this.formatStyle(box.bottom, "px"),
9196        left: this.formatStyle(box.left, "px"),
9197        right: this.formatStyle(box.right, "px"),
9198        height: this.formatStyle(box.height, "px"),
9199        width: this.formatStyle(box.width, "px")
9200      });
9201    };
9202  }
9203
9204  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9205  CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
9206  // compute things with such as if it overlaps or intersects with another Element.
9207  // Can initialize it with either a StyleBox or another BoxPosition.
9208
9209  function BoxPosition(obj) {
9210    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9211    // was passed in and we need to copy the results of 'getBoundingClientRect'
9212    // as the object returned is readonly. All co-ordinate values are in reference
9213    // to the viewport origin (top left).
9214    var lh, height, width, top;
9215
9216    if (obj.div) {
9217      height = obj.div.offsetHeight;
9218      width = obj.div.offsetWidth;
9219      top = obj.div.offsetTop;
9220      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9221      obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
9222      // the inner div's lines. This could be due to bold text, etc, on some platforms.
9223      // In this case we should get the average line height and use that. This will
9224      // result in the desired behaviour.
9225
9226      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9227    }
9228
9229    this.left = obj.left;
9230    this.right = obj.right;
9231    this.top = obj.top || top;
9232    this.height = obj.height || height;
9233    this.bottom = obj.bottom || top + (obj.height || height);
9234    this.width = obj.width || width;
9235    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9236  } // Move the box along a particular axis. Optionally pass in an amount to move
9237  // the box. If no amount is passed then the default is the line height of the
9238  // box.
9239
9240
9241  BoxPosition.prototype.move = function (axis, toMove) {
9242    toMove = toMove !== undefined ? toMove : this.lineHeight;
9243
9244    switch (axis) {
9245      case "+x":
9246        this.left += toMove;
9247        this.right += toMove;
9248        break;
9249
9250      case "-x":
9251        this.left -= toMove;
9252        this.right -= toMove;
9253        break;
9254
9255      case "+y":
9256        this.top += toMove;
9257        this.bottom += toMove;
9258        break;
9259
9260      case "-y":
9261        this.top -= toMove;
9262        this.bottom -= toMove;
9263        break;
9264    }
9265  }; // Check if this box overlaps another box, b2.
9266
9267
9268  BoxPosition.prototype.overlaps = function (b2) {
9269    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9270  }; // Check if this box overlaps any other boxes in boxes.
9271
9272
9273  BoxPosition.prototype.overlapsAny = function (boxes) {
9274    for (var i = 0; i < boxes.length; i++) {
9275      if (this.overlaps(boxes[i])) {
9276        return true;
9277      }
9278    }
9279
9280    return false;
9281  }; // Check if this box is within another box.
9282
9283
9284  BoxPosition.prototype.within = function (container) {
9285    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9286  }; // Check if this box is entirely within the container or it is overlapping
9287  // on the edge opposite of the axis direction passed. For example, if "+x" is
9288  // passed and the box is overlapping on the left edge of the container, then
9289  // return true.
9290
9291
9292  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9293    switch (axis) {
9294      case "+x":
9295        return this.left < container.left;
9296
9297      case "-x":
9298        return this.right > container.right;
9299
9300      case "+y":
9301        return this.top < container.top;
9302
9303      case "-y":
9304        return this.bottom > container.bottom;
9305    }
9306  }; // Find the percentage of the area that this box is overlapping with another
9307  // box.
9308
9309
9310  BoxPosition.prototype.intersectPercentage = function (b2) {
9311    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9312        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9313        intersectArea = x * y;
9314    return intersectArea / (this.height * this.width);
9315  }; // Convert the positions from this box to CSS compatible positions using
9316  // the reference container's positions. This has to be done because this
9317  // box's positions are in reference to the viewport origin, whereas, CSS
9318  // values are in referecne to their respective edges.
9319
9320
9321  BoxPosition.prototype.toCSSCompatValues = function (reference) {
9322    return {
9323      top: this.top - reference.top,
9324      bottom: reference.bottom - this.bottom,
9325      left: this.left - reference.left,
9326      right: reference.right - this.right,
9327      height: this.height,
9328      width: this.width
9329    };
9330  }; // Get an object that represents the box's position without anything extra.
9331  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9332
9333
9334  BoxPosition.getSimpleBoxPosition = function (obj) {
9335    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9336    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9337    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9338    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9339    var ret = {
9340      left: obj.left,
9341      right: obj.right,
9342      top: obj.top || top,
9343      height: obj.height || height,
9344      bottom: obj.bottom || top + (obj.height || height),
9345      width: obj.width || width
9346    };
9347    return ret;
9348  }; // Move a StyleBox to its specified, or next best, position. The containerBox
9349  // is the box that contains the StyleBox, such as a div. boxPositions are
9350  // a list of other boxes that the styleBox can't overlap with.
9351
9352
9353  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9354    // Find the best position for a cue box, b, on the video. The axis parameter
9355    // is a list of axis, the order of which, it will move the box along. For example:
9356    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9357    // direction. If it doesn't find a good position for it there it will then move
9358    // it along the x axis in the negative direction.
9359    function findBestPosition(b, axis) {
9360      var bestPosition,
9361          specifiedPosition = new BoxPosition(b),
9362          percentage = 1; // Highest possible so the first thing we get is better.
9363
9364      for (var i = 0; i < axis.length; i++) {
9365        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9366          b.move(axis[i]);
9367        } // We found a spot where we aren't overlapping anything. This is our
9368        // best position.
9369
9370
9371        if (b.within(containerBox)) {
9372          return b;
9373        }
9374
9375        var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
9376        // then remember this position as the best position.
9377
9378        if (percentage > p) {
9379          bestPosition = new BoxPosition(b);
9380          percentage = p;
9381        } // Reset the box position to the specified position.
9382
9383
9384        b = new BoxPosition(specifiedPosition);
9385      }
9386
9387      return bestPosition || specifiedPosition;
9388    }
9389
9390    var boxPosition = new BoxPosition(styleBox),
9391        cue = styleBox.cue,
9392        linePos = computeLinePos(cue),
9393        axis = []; // If we have a line number to align the cue to.
9394
9395    if (cue.snapToLines) {
9396      var size;
9397
9398      switch (cue.vertical) {
9399        case "":
9400          axis = ["+y", "-y"];
9401          size = "height";
9402          break;
9403
9404        case "rl":
9405          axis = ["+x", "-x"];
9406          size = "width";
9407          break;
9408
9409        case "lr":
9410          axis = ["-x", "+x"];
9411          size = "width";
9412          break;
9413      }
9414
9415      var step = boxPosition.lineHeight,
9416          position = step * Math.round(linePos),
9417          maxPosition = containerBox[size] + step,
9418          initialAxis = axis[0]; // If the specified intial position is greater then the max position then
9419      // clamp the box to the amount of steps it would take for the box to
9420      // reach the max position.
9421
9422      if (Math.abs(position) > maxPosition) {
9423        position = position < 0 ? -1 : 1;
9424        position *= Math.ceil(maxPosition / step) * step;
9425      } // If computed line position returns negative then line numbers are
9426      // relative to the bottom of the video instead of the top. Therefore, we
9427      // need to increase our initial position by the length or width of the
9428      // video, depending on the writing direction, and reverse our axis directions.
9429
9430
9431      if (linePos < 0) {
9432        position += cue.vertical === "" ? containerBox.height : containerBox.width;
9433        axis = axis.reverse();
9434      } // Move the box to the specified position. This may not be its best
9435      // position.
9436
9437
9438      boxPosition.move(initialAxis, position);
9439    } else {
9440      // If we have a percentage line value for the cue.
9441      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9442
9443      switch (cue.lineAlign) {
9444        case "center":
9445          linePos -= calculatedPercentage / 2;
9446          break;
9447
9448        case "end":
9449          linePos -= calculatedPercentage;
9450          break;
9451      } // Apply initial line position to the cue box.
9452
9453
9454      switch (cue.vertical) {
9455        case "":
9456          styleBox.applyStyles({
9457            top: styleBox.formatStyle(linePos, "%")
9458          });
9459          break;
9460
9461        case "rl":
9462          styleBox.applyStyles({
9463            left: styleBox.formatStyle(linePos, "%")
9464          });
9465          break;
9466
9467        case "lr":
9468          styleBox.applyStyles({
9469            right: styleBox.formatStyle(linePos, "%")
9470          });
9471          break;
9472      }
9473
9474      axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
9475      // to it.
9476
9477      boxPosition = new BoxPosition(styleBox);
9478    }
9479
9480    var bestPosition = findBestPosition(boxPosition, axis);
vendor: 5,111 bytes, lines 9481-9647
9481    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9482  }
9483
9484  function WebVTT$1() {} // Nothing
9485  // Helper to allow strings to be decoded instead of the default binary utf8 data.
9486
9487
9488  WebVTT$1.StringDecoder = function () {
9489    return {
9490      decode: function decode(data) {
9491        if (!data) {
9492          return "";
9493        }
9494
9495        if (typeof data !== "string") {
9496          throw new Error("Error - expected string data.");
9497        }
9498
9499        return decodeURIComponent(encodeURIComponent(data));
9500      }
9501    };
9502  };
9503
9504  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9505    if (!window || !cuetext) {
9506      return null;
9507    }
9508
9509    return parseContent(window, cuetext);
9510  };
9511
9512  var FONT_SIZE_PERCENT = 0.05;
9513  var FONT_STYLE = "sans-serif";
9514  var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
9515  // @param overlay A block level element (usually a div) that the computed cues
9516  //                and regions will be placed into.
9517
9518  WebVTT$1.processCues = function (window, cues, overlay) {
9519    if (!window || !cues || !overlay) {
9520      return null;
9521    } // Remove all previous children.
9522
9523
9524    while (overlay.firstChild) {
9525      overlay.removeChild(overlay.firstChild);
9526    }
9527
9528    var paddedOverlay = window.document.createElement("div");
9529    paddedOverlay.style.position = "absolute";
9530    paddedOverlay.style.left = "0";
9531    paddedOverlay.style.right = "0";
9532    paddedOverlay.style.top = "0";
9533    paddedOverlay.style.bottom = "0";
9534    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
9535    overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
9536    // be the case if a cue's state has been changed since the last computation or
9537    // if it has not been computed yet.
9538
9539    function shouldCompute(cues) {
9540      for (var i = 0; i < cues.length; i++) {
9541        if (cues[i].hasBeenReset || !cues[i].displayState) {
9542          return true;
9543        }
9544      }
9545
9546      return false;
9547    } // We don't need to recompute the cues' display states. Just reuse them.
9548
9549
9550    if (!shouldCompute(cues)) {
9551      for (var i = 0; i < cues.length; i++) {
9552        paddedOverlay.appendChild(cues[i].displayState);
9553      }
9554
9555      return;
9556    }
9557
9558    var boxPositions = [],
9559        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
9560        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
9561    var styleOptions = {
9562      font: fontSize + "px " + FONT_STYLE
9563    };
9564
9565    (function () {
9566      var styleBox, cue;
9567
9568      for (var i = 0; i < cues.length; i++) {
9569        cue = cues[i]; // Compute the intial position and styles of the cue div.
9570
9571        styleBox = new CueStyleBox(window, cue, styleOptions);
9572        paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
9573
9574        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
9575        // if we don't have too.
9576
9577        cue.displayState = styleBox.div;
9578        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
9579      }
9580    })();
9581  };
9582
9583  WebVTT$1.Parser = function (window, vttjs, decoder) {
9584    if (!decoder) {
9585      decoder = vttjs;
9586      vttjs = {};
9587    }
9588
9589    if (!vttjs) {
9590      vttjs = {};
9591    }
9592
9593    this.window = window;
9594    this.vttjs = vttjs;
9595    this.state = "INITIAL";
9596    this.buffer = "";
9597    this.decoder = decoder || new TextDecoder("utf8");
9598    this.regionList = [];
9599  };
9600
9601  WebVTT$1.Parser.prototype = {
9602    // If the error is a ParsingError then report it to the consumer if
9603    // possible. If it's not a ParsingError then throw it like normal.
9604    reportOrThrowError: function reportOrThrowError(e) {
9605      if (e instanceof ParsingError) {
9606        this.onparsingerror && this.onparsingerror(e);
9607      } else {
9608        throw e;
9609      }
9610    },
9611    parse: function parse(data) {
9612      var self = this; // If there is no data then we won't decode it, but will just try to parse
9613      // whatever is in buffer already. This may occur in circumstances, for
9614      // example when flush() is called.
9615
9616      if (data) {
9617        // Try to decode the data that we received.
9618        self.buffer += self.decoder.decode(data, {
9619          stream: true
9620        });
9621      }
9622
9623      function collectNextLine() {
9624        var buffer = self.buffer;
9625        var pos = 0;
9626
9627        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
9628          ++pos;
9629        }
9630
9631        var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
9632
9633        if (buffer[pos] === '\r') {
9634          ++pos;
9635        }
9636
9637        if (buffer[pos] === '\n') {
9638          ++pos;
9639        }
9640
9641        self.buffer = buffer.substr(pos);
9642        return line;
9643      } // 3.4 WebVTT region and WebVTT region settings syntax
9644
9645
9646      function parseRegion(input) {
9647        var settings = new Settings();
vendor: 8,124 bytes, lines 9648-9904
9648        parseOptions(input, function (k, v) {
9649          switch (k) {
9650            case "id":
9651              settings.set(k, v);
9652              break;
9653
9654            case "width":
9655              settings.percent(k, v);
9656              break;
9657
9658            case "lines":
9659              settings.integer(k, v);
9660              break;
9661
9662            case "regionanchor":
9663            case "viewportanchor":
9664              var xy = v.split(',');
9665
9666              if (xy.length !== 2) {
9667                break;
9668              } // We have to make sure both x and y parse, so use a temporary
9669              // settings object here.
9670
9671
9672              var anchor = new Settings();
9673              anchor.percent("x", xy[0]);
9674              anchor.percent("y", xy[1]);
9675
9676              if (!anchor.has("x") || !anchor.has("y")) {
9677                break;
9678              }
9679
9680              settings.set(k + "X", anchor.get("x"));
9681              settings.set(k + "Y", anchor.get("y"));
9682              break;
9683
9684            case "scroll":
9685              settings.alt(k, v, ["up"]);
9686              break;
9687          }
9688        }, /=/, /\s/); // Create the region, using default values for any values that were not
9689        // specified.
9690
9691        if (settings.has("id")) {
9692          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9693          region.width = settings.get("width", 100);
9694          region.lines = settings.get("lines", 3);
9695          region.regionAnchorX = settings.get("regionanchorX", 0);
9696          region.regionAnchorY = settings.get("regionanchorY", 100);
9697          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9698          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9699          region.scroll = settings.get("scroll", ""); // Register the region.
9700
9701          self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
9702          // reference it.
9703
9704          self.regionList.push({
9705            id: settings.get("id"),
9706            region: region
9707          });
9708        }
9709      } // draft-pantos-http-live-streaming-20
9710      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9711      // 3.5 WebVTT
9712
9713
9714      function parseTimestampMap(input) {
9715        var settings = new Settings();
9716        parseOptions(input, function (k, v) {
9717          switch (k) {
9718            case "MPEGT":
9719              settings.integer(k + 'S', v);
9720              break;
9721
9722            case "LOCA":
9723              settings.set(k + 'L', parseTimeStamp(v));
9724              break;
9725          }
9726        }, /[^\d]:/, /,/);
9727        self.ontimestampmap && self.ontimestampmap({
9728          "MPEGTS": settings.get("MPEGTS"),
9729          "LOCAL": settings.get("LOCAL")
9730        });
9731      } // 3.2 WebVTT metadata header syntax
9732
9733
9734      function parseHeader(input) {
9735        if (input.match(/X-TIMESTAMP-MAP/)) {
9736          // This line contains HLS X-TIMESTAMP-MAP metadata
9737          parseOptions(input, function (k, v) {
9738            switch (k) {
9739              case "X-TIMESTAMP-MAP":
9740                parseTimestampMap(v);
9741                break;
9742            }
9743          }, /=/);
9744        } else {
9745          parseOptions(input, function (k, v) {
9746            switch (k) {
9747              case "Region":
9748                // 3.3 WebVTT region metadata header syntax
9749                parseRegion(v);
9750                break;
9751            }
9752          }, /:/);
9753        }
9754      } // 5.1 WebVTT file parsing.
9755
9756
9757      try {
9758        var line;
9759
9760        if (self.state === "INITIAL") {
9761          // We can't start parsing until we have the first line.
9762          if (!/\r\n|\n/.test(self.buffer)) {
9763            return this;
9764          }
9765
9766          line = collectNextLine();
9767          var m = line.match(/^WEBVTT([ \t].*)?$/);
9768
9769          if (!m || !m[0]) {
9770            throw new ParsingError(ParsingError.Errors.BadSignature);
9771          }
9772
9773          self.state = "HEADER";
9774        }
9775
9776        var alreadyCollectedLine = false;
9777
9778        while (self.buffer) {
9779          // We can't parse a line until we have the full line.
9780          if (!/\r\n|\n/.test(self.buffer)) {
9781            return this;
9782          }
9783
9784          if (!alreadyCollectedLine) {
9785            line = collectNextLine();
9786          } else {
9787            alreadyCollectedLine = false;
9788          }
9789
9790          switch (self.state) {
9791            case "HEADER":
9792              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9793              if (/:/.test(line)) {
9794                parseHeader(line);
9795              } else if (!line) {
9796                // An empty line terminates the header and starts the body (cues).
9797                self.state = "ID";
9798              }
9799
9800              continue;
9801
9802            case "NOTE":
9803              // Ignore NOTE blocks.
9804              if (!line) {
9805                self.state = "ID";
9806              }
9807
9808              continue;
9809
9810            case "ID":
9811              // Check for the start of NOTE blocks.
9812              if (/^NOTE($|[ \t])/.test(line)) {
9813                self.state = "NOTE";
9814                break;
9815              } // 19-29 - Allow any number of line terminators, then initialize new cue values.
9816
9817
9818              if (!line) {
9819                continue;
9820              }
9821
9822              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center
9823
9824              try {
9825                self.cue.align = "center";
9826              } catch (e) {
9827                self.cue.align = "middle";
9828              }
9829
9830              self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
9831
9832              if (line.indexOf("-->") === -1) {
9833                self.cue.id = line;
9834                continue;
9835              }
9836
9837            // Process line as start of a cue.
9838
9839            /*falls through*/
9840
9841            case "CUE":
9842              // 40 - Collect cue timings and settings.
9843              try {
9844                parseCue(line, self.cue, self.regionList);
9845              } catch (e) {
9846                self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
9847
9848                self.cue = null;
9849                self.state = "BADCUE";
9850                continue;
9851              }
9852
9853              self.state = "CUETEXT";
9854              continue;
9855
9856            case "CUETEXT":
9857              var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
9858              // 35 - If we have the special substring '-->' then report the cue,
9859              // but do not collect the line as we need to process the current
9860              // one as a new cue.
9861
9862              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9863                // We are done parsing self cue.
9864                self.oncue && self.oncue(self.cue);
9865                self.cue = null;
9866                self.state = "ID";
9867                continue;
9868              }
9869
9870              if (self.cue.text) {
9871                self.cue.text += "\n";
9872              }
9873
9874              self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
9875              continue;
9876
9877            case "BADCUE":
9878              // BADCUE
9879              // 54-62 - Collect and discard the remaining cue.
9880              if (!line) {
9881                self.state = "ID";
9882              }
9883
9884              continue;
9885          }
9886        }
9887      } catch (e) {
9888        self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
9889
9890        if (self.state === "CUETEXT" && self.cue && self.oncue) {
9891          self.oncue(self.cue);
9892        }
9893
9894        self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
9895        // another exception occurred so enter BADCUE state.
9896
9897        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9898      }
9899
9900      return this;
9901    },
9902    flush: function flush() {
9903      var self = this;
9904
vendor: 10,224 bytes, lines 9905-10272
9905      try {
9906        // Finish decoding the stream.
9907        self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
9908
9909        if (self.cue || self.state === "HEADER") {
9910          self.buffer += "\n\n";
9911          self.parse();
9912        } // If we've flushed, parsed, and we're still on the INITIAL state then
9913        // that means we don't have enough of the stream to parse the first
9914        // line.
9915
9916
9917        if (self.state === "INITIAL") {
9918          throw new ParsingError(ParsingError.Errors.BadSignature);
9919        }
9920      } catch (e) {
9921        self.reportOrThrowError(e);
9922      }
9923
9924      self.onflush && self.onflush();
9925      return this;
9926    }
9927  };
9928  var vtt = WebVTT$1;
9929
9930  /**
9931   * Copyright 2013 vtt.js Contributors
9932   *
9933   * Licensed under the Apache License, Version 2.0 (the "License");
9934   * you may not use this file except in compliance with the License.
9935   * You may obtain a copy of the License at
9936   *
9937   *   http://www.apache.org/licenses/LICENSE-2.0
9938   *
9939   * Unless required by applicable law or agreed to in writing, software
9940   * distributed under the License is distributed on an "AS IS" BASIS,
9941   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9942   * See the License for the specific language governing permissions and
9943   * limitations under the License.
9944   */
9945  var autoKeyword = "auto";
9946  var directionSetting = {
9947    "": 1,
9948    "lr": 1,
9949    "rl": 1
9950  };
9951  var alignSetting = {
9952    "start": 1,
9953    "center": 1,
9954    "end": 1,
9955    "left": 1,
9956    "right": 1,
9957    "auto": 1,
9958    "line-left": 1,
9959    "line-right": 1
9960  };
9961
9962  function findDirectionSetting(value) {
9963    if (typeof value !== "string") {
9964      return false;
9965    }
9966
9967    var dir = directionSetting[value.toLowerCase()];
9968    return dir ? value.toLowerCase() : false;
9969  }
9970
9971  function findAlignSetting(value) {
9972    if (typeof value !== "string") {
9973      return false;
9974    }
9975
9976    var align = alignSetting[value.toLowerCase()];
9977    return align ? value.toLowerCase() : false;
9978  }
9979
9980  function VTTCue(startTime, endTime, text) {
9981    /**
9982     * Shim implementation specific properties. These properties are not in
9983     * the spec.
9984     */
9985    // Lets us know when the VTTCue's data has changed in such a way that we need
9986    // to recompute its display state. This lets us compute its display state
9987    // lazily.
9988    this.hasBeenReset = false;
9989    /**
9990     * VTTCue and TextTrackCue properties
9991     * http://dev.w3.org/html5/webvtt/#vttcue-interface
9992     */
9993
9994    var _id = "";
9995    var _pauseOnExit = false;
9996    var _startTime = startTime;
9997    var _endTime = endTime;
9998    var _text = text;
9999    var _region = null;
10000    var _vertical = "";
10001    var _snapToLines = true;
10002    var _line = "auto";
10003    var _lineAlign = "start";
10004    var _position = "auto";
10005    var _positionAlign = "auto";
10006    var _size = 100;
10007    var _align = "center";
10008    Object.defineProperties(this, {
10009      "id": {
10010        enumerable: true,
10011        get: function get() {
10012          return _id;
10013        },
10014        set: function set(value) {
10015          _id = "" + value;
10016        }
10017      },
10018      "pauseOnExit": {
10019        enumerable: true,
10020        get: function get() {
10021          return _pauseOnExit;
10022        },
10023        set: function set(value) {
10024          _pauseOnExit = !!value;
10025        }
10026      },
10027      "startTime": {
10028        enumerable: true,
10029        get: function get() {
10030          return _startTime;
10031        },
10032        set: function set(value) {
10033          if (typeof value !== "number") {
10034            throw new TypeError("Start time must be set to a number.");
10035          }
10036
10037          _startTime = value;
10038          this.hasBeenReset = true;
10039        }
10040      },
10041      "endTime": {
10042        enumerable: true,
10043        get: function get() {
10044          return _endTime;
10045        },
10046        set: function set(value) {
10047          if (typeof value !== "number") {
10048            throw new TypeError("End time must be set to a number.");
10049          }
10050
10051          _endTime = value;
10052          this.hasBeenReset = true;
10053        }
10054      },
10055      "text": {
10056        enumerable: true,
10057        get: function get() {
10058          return _text;
10059        },
10060        set: function set(value) {
10061          _text = "" + value;
10062          this.hasBeenReset = true;
10063        }
10064      },
10065      "region": {
10066        enumerable: true,
10067        get: function get() {
10068          return _region;
10069        },
10070        set: function set(value) {
10071          _region = value;
10072          this.hasBeenReset = true;
10073        }
10074      },
10075      "vertical": {
10076        enumerable: true,
10077        get: function get() {
10078          return _vertical;
10079        },
10080        set: function set(value) {
10081          var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
10082
10083          if (setting === false) {
10084            throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
10085          }
10086
10087          _vertical = setting;
10088          this.hasBeenReset = true;
10089        }
10090      },
10091      "snapToLines": {
10092        enumerable: true,
10093        get: function get() {
10094          return _snapToLines;
10095        },
10096        set: function set(value) {
10097          _snapToLines = !!value;
10098          this.hasBeenReset = true;
10099        }
10100      },
10101      "line": {
10102        enumerable: true,
10103        get: function get() {
10104          return _line;
10105        },
10106        set: function set(value) {
10107          if (typeof value !== "number" && value !== autoKeyword) {
10108            throw new SyntaxError("Line: an invalid number or illegal string was specified.");
10109          }
10110
10111          _line = value;
10112          this.hasBeenReset = true;
10113        }
10114      },
10115      "lineAlign": {
10116        enumerable: true,
10117        get: function get() {
10118          return _lineAlign;
10119        },
10120        set: function set(value) {
10121          var setting = findAlignSetting(value);
10122
10123          if (!setting) {
10124            console.warn("lineAlign: an invalid or illegal string was specified.");
10125          } else {
10126            _lineAlign = setting;
10127            this.hasBeenReset = true;
10128          }
10129        }
10130      },
10131      "position": {
10132        enumerable: true,
10133        get: function get() {
10134          return _position;
10135        },
10136        set: function set(value) {
10137          if (value < 0 || value > 100) {
10138            throw new Error("Position must be between 0 and 100.");
10139          }
10140
10141          _position = value;
10142          this.hasBeenReset = true;
10143        }
10144      },
10145      "positionAlign": {
10146        enumerable: true,
10147        get: function get() {
10148          return _positionAlign;
10149        },
10150        set: function set(value) {
10151          var setting = findAlignSetting(value);
10152
10153          if (!setting) {
10154            console.warn("positionAlign: an invalid or illegal string was specified.");
10155          } else {
10156            _positionAlign = setting;
10157            this.hasBeenReset = true;
10158          }
10159        }
10160      },
10161      "size": {
10162        enumerable: true,
10163        get: function get() {
10164          return _size;
10165        },
10166        set: function set(value) {
10167          if (value < 0 || value > 100) {
10168            throw new Error("Size must be between 0 and 100.");
10169          }
10170
10171          _size = value;
10172          this.hasBeenReset = true;
10173        }
10174      },
10175      "align": {
10176        enumerable: true,
10177        get: function get() {
10178          return _align;
10179        },
10180        set: function set(value) {
10181          var setting = findAlignSetting(value);
10182
10183          if (!setting) {
10184            throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
10185          }
10186
10187          _align = setting;
10188          this.hasBeenReset = true;
10189        }
10190      }
10191    });
10192    /**
10193     * Other <track> spec defined properties
10194     */
10195    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
10196
10197    this.displayState = undefined;
10198  }
10199  /**
10200   * VTTCue methods
10201   */
10202
10203
10204  VTTCue.prototype.getCueAsHTML = function () {
10205    // Assume WebVTT.convertCueToDOMTree is on the global.
10206    return WebVTT.convertCueToDOMTree(window, this.text);
10207  };
10208
10209  var vttcue = VTTCue;
10210
10211  /**
10212   * Copyright 2013 vtt.js Contributors
10213   *
10214   * Licensed under the Apache License, Version 2.0 (the "License");
10215   * you may not use this file except in compliance with the License.
10216   * You may obtain a copy of the License at
10217   *
10218   *   http://www.apache.org/licenses/LICENSE-2.0
10219   *
10220   * Unless required by applicable law or agreed to in writing, software
10221   * distributed under the License is distributed on an "AS IS" BASIS,
10222   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10223   * See the License for the specific language governing permissions and
10224   * limitations under the License.
10225   */
10226  var scrollSetting = {
10227    "": true,
10228    "up": true
10229  };
10230
10231  function findScrollSetting(value) {
10232    if (typeof value !== "string") {
10233      return false;
10234    }
10235
10236    var scroll = scrollSetting[value.toLowerCase()];
10237    return scroll ? value.toLowerCase() : false;
10238  }
10239
10240  function isValidPercentValue(value) {
10241    return typeof value === "number" && value >= 0 && value <= 100;
10242  } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
10243
10244
10245  function VTTRegion() {
10246    var _width = 100;
10247    var _lines = 3;
10248    var _regionAnchorX = 0;
10249    var _regionAnchorY = 100;
10250    var _viewportAnchorX = 0;
10251    var _viewportAnchorY = 100;
10252    var _scroll = "";
10253    Object.defineProperties(this, {
10254      "width": {
10255        enumerable: true,
10256        get: function get() {
10257          return _width;
10258        },
10259        set: function set(value) {
10260          if (!isValidPercentValue(value)) {
10261            throw new Error("Width must be between 0 and 100.");
10262          }
10263
10264          _width = value;
10265        }
10266      },
10267      "lines": {
10268        enumerable: true,
10269        get: function get() {
10270          return _lines;
10271        },
10272        set: function set(value) {
10273          if (typeof value !== "number") {
10274            throw new TypeError("Lines must be set to a number.");
10275          }
10276
10277          _lines = value;
10278        }
10279      },
10280      "regionAnchorY": {
10281        enumerable: true,
10282        get: function get() {
10283          return _regionAnchorY;
10284        },
10285        set: function set(value) {
10286          if (!isValidPercentValue(value)) {
10287            throw new Error("RegionAnchorX must be between 0 and 100.");
10288          }
10289
10290          _regionAnchorY = value;
10291        }
10292      },
10293      "regionAnchorX": {
10294        enumerable: true,
10295        get: function get() {
10296          return _regionAnchorX;
10297        },
10298        set: function set(value) {
10299          if (!isValidPercentValue(value)) {
10300            throw new Error("RegionAnchorY must be between 0 and 100.");
10301          }
10302
10303          _regionAnchorX = value;
10304        }
10305      },
10306      "viewportAnchorY": {
10307        enumerable: true,
10308        get: function get() {
10309          return _viewportAnchorY;
10310        },
10311        set: function set(value) {
10312          if (!isValidPercentValue(value)) {
10313            throw new Error("ViewportAnchorY must be between 0 and 100.");
10314          }
10315
10316          _viewportAnchorY = value;
10317        }
10318      },
10319      "viewportAnchorX": {
10320        enumerable: true,
10321        get: function get() {
10322          return _viewportAnchorX;
10323        },
10324        set: function set(value) {
10325          if (!isValidPercentValue(value)) {
10326            throw new Error("ViewportAnchorX must be between 0 and 100.");
10327          }
10328
10329          _viewportAnchorX = value;
10330        }
10331      },
10332      "scroll": {
10333        enumerable: true,
10334        get: function get() {
10335          return _scroll;
10336        },
10337        set: function set(value) {
10338          var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
10339
10340          if (setting === false) {
10341            console.warn("Scroll: an invalid or illegal string was specified.");
10342          } else {
10343            _scroll = setting;
10344          }
10345        }
10346      }
10347    });
10348  }
10349
10350  var vttregion = VTTRegion;
10351
10352  var browserIndex = createCommonjsModule(function (module) {
10353    /**
10354     * Copyright 2013 vtt.js Contributors
10355     *
10356     * Licensed under the Apache License, Version 2.0 (the "License");
10357     * you may not use this file except in compliance with the License.
10358     * You may obtain a copy of the License at
10359     *
10360     *   http://www.apache.org/licenses/LICENSE-2.0
10361     *
10362     * Unless required by applicable law or agreed to in writing, software
10363     * distributed under the License is distributed on an "AS IS" BASIS,
10364     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10365     * See the License for the specific language governing permissions and
10366     * limitations under the License.
10367     */
10368    // Default exports for Node. Export the extended versions of VTTCue and
10369    // VTTRegion in Node since we likely want the capability to convert back and
10370    // forth between JSON. If we don't then it's not that big of a deal since we're
10371    // off browser.
10372    var vttjs = module.exports = {
10373      WebVTT: vtt,
10374      VTTCue: vttcue,
10375      VTTRegion: vttregion
10376    };
10377    window$1.vttjs = vttjs;
10378    window$1.WebVTT = vttjs.WebVTT;
10379    var cueShim = vttjs.VTTCue;
10380    var regionShim = vttjs.VTTRegion;
10381    var nativeVTTCue = window$1.VTTCue;
10382    var nativeVTTRegion = window$1.VTTRegion;
10383
10384    vttjs.shim = function () {
10385      window$1.VTTCue = cueShim;
10386      window$1.VTTRegion = regionShim;
10387    };
10388
10389    vttjs.restore = function () {
10390      window$1.VTTCue = nativeVTTCue;
10391      window$1.VTTRegion = nativeVTTRegion;
10392    };
10393
10394    if (!window$1.VTTCue) {
10395      vttjs.shim();
10396    }
10397  });
10398  var browserIndex_1 = browserIndex.WebVTT;
10399  var browserIndex_2 = browserIndex.VTTCue;
10400  var browserIndex_3 = browserIndex.VTTRegion;
10401
10402  /**
10403   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10404   * that just contains the src url alone.
10405   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10406     * `var SourceString = 'http://example.com/some-video.mp4';`
10407   *
10408   * @typedef {Object|string} Tech~SourceObject
10409   *
10410   * @property {string} src
10411   *           The url to the source
10412   *
10413   * @property {string} type
10414   *           The mime type of the source
10415   */
10416
10417  /**
10418   * A function used by {@link Tech} to create a new {@link TextTrack}.
10419   *
10420   * @private
10421   *
10422   * @param {Tech} self
10423   *        An instance of the Tech class.
10424   *
10425   * @param {string} kind
10426   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10427   *
10428   * @param {string} [label]
10429   *        Label to identify the text track
10430   *
10431   * @param {string} [language]
10432   *        Two letter language abbreviation
10433   *
10434   * @param {Object} [options={}]
10435   *        An object with additional text track options
10436   *
10437   * @return {TextTrack}
10438   *          The text track that was created.
10439   */
10440
10441  function createTrackHelper(self, kind, label, language, options) {
10442    if (options === void 0) {
10443      options = {};
10444    }
10445
10446    var tracks = self.textTracks();
10447    options.kind = kind;
10448
10449    if (label) {
10450      options.label = label;
10451    }
10452
10453    if (language) {
10454      options.language = language;
10455    }
10456
10457    options.tech = self;
10458    var track = new ALL.text.TrackClass(options);
10459    tracks.addTrack(track);
10460    return track;
10461  }
10462  /**
10463   * This is the base class for media playback technology controllers, such as
10464   * {@link Flash} and {@link HTML5}
10465   *
10466   * @extends Component
10467   */
10468
10469
10470  var Tech =
10471  /*#__PURE__*/
10472  function (_Component) {
10473    inheritsLoose(Tech, _Component);
10474
10475    /**
10476    * Create an instance of this Tech.
10477    *
10478    * @param {Object} [options]
10479    *        The key/value store of player options.
10480    *
10481    * @param {Component~ReadyCallback} ready
10482    *        Callback function to call when the `HTML5` Tech is ready.
10483    */
10484    function Tech(options, ready) {
10485      var _this;
10486
10487      if (options === void 0) {
10488        options = {};
10489      }
10490
10491      if (ready === void 0) {
10492        ready = function ready() {};
10493      }
10494
10495      // we don't want the tech to report user activity automatically.
vendor: 60,948 bytes, lines 10496-12588
10496      // This is done manually in addControlsListeners
10497      options.reportTouchActivity = false;
10498      _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
10499      // implement a very limited played()
10500
10501      _this.hasStarted_ = false;
10502
10503      _this.on('playing', function () {
10504        this.hasStarted_ = true;
10505      });
10506
10507      _this.on('loadstart', function () {
10508        this.hasStarted_ = false;
10509      });
10510
10511      ALL.names.forEach(function (name) {
10512        var props = ALL[name];
10513
10514        if (options && options[props.getterName]) {
10515          _this[props.privateName] = options[props.getterName];
10516        }
10517      }); // Manually track progress in cases where the browser/flash player doesn't report it.
10518
10519      if (!_this.featuresProgressEvents) {
10520        _this.manualProgressOn();
10521      } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
10522
10523
10524      if (!_this.featuresTimeupdateEvents) {
10525        _this.manualTimeUpdatesOn();
10526      }
10527
10528      ['Text', 'Audio', 'Video'].forEach(function (track) {
10529        if (options["native" + track + "Tracks"] === false) {
10530          _this["featuresNative" + track + "Tracks"] = false;
10531        }
10532      });
10533
10534      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
10535        _this.featuresNativeTextTracks = false;
10536      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
10537        _this.featuresNativeTextTracks = true;
10538      }
10539
10540      if (!_this.featuresNativeTextTracks) {
10541        _this.emulateTextTracks();
10542      }
10543
10544      _this.preloadTextTracks = options.preloadTextTracks !== false;
10545      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
10546
10547      _this.initTrackListeners(); // Turn on component tap events only if not using native controls
10548
10549
10550      if (!options.nativeControlsForTouch) {
10551        _this.emitTapEvents();
10552      }
10553
10554      if (_this.constructor) {
10555        _this.name_ = _this.constructor.name || 'Unknown Tech';
10556      }
10557
10558      return _this;
10559    }
10560    /**
10561     * A special function to trigger source set in a way that will allow player
10562     * to re-trigger if the player or tech are not ready yet.
10563     *
10564     * @fires Tech#sourceset
10565     * @param {string} src The source string at the time of the source changing.
10566     */
10567
10568
10569    var _proto = Tech.prototype;
10570
10571    _proto.triggerSourceset = function triggerSourceset(src) {
10572      var _this2 = this;
10573
10574      if (!this.isReady_) {
10575        // on initial ready we have to trigger source set
10576        // 1ms after ready so that player can watch for it.
10577        this.one('ready', function () {
10578          return _this2.setTimeout(function () {
10579            return _this2.triggerSourceset(src);
10580          }, 1);
10581        });
10582      }
10583      /**
10584       * Fired when the source is set on the tech causing the media element
10585       * to reload.
10586       *
10587       * @see {@link Player#event:sourceset}
10588       * @event Tech#sourceset
10589       * @type {EventTarget~Event}
10590       */
10591
10592
10593      this.trigger({
10594        src: src,
10595        type: 'sourceset'
10596      });
10597    }
10598    /* Fallbacks for unsupported event types
10599    ================================================================================ */
10600
10601    /**
10602     * Polyfill the `progress` event for browsers that don't support it natively.
10603     *
10604     * @see {@link Tech#trackProgress}
10605     */
10606    ;
10607
10608    _proto.manualProgressOn = function manualProgressOn() {
10609      this.on('durationchange', this.onDurationChange);
10610      this.manualProgress = true; // Trigger progress watching when a source begins loading
10611
10612      this.one('ready', this.trackProgress);
10613    }
10614    /**
10615     * Turn off the polyfill for `progress` events that was created in
10616     * {@link Tech#manualProgressOn}
10617     */
10618    ;
10619
10620    _proto.manualProgressOff = function manualProgressOff() {
10621      this.manualProgress = false;
10622      this.stopTrackingProgress();
10623      this.off('durationchange', this.onDurationChange);
10624    }
10625    /**
10626     * This is used to trigger a `progress` event when the buffered percent changes. It
10627     * sets an interval function that will be called every 500 milliseconds to check if the
10628     * buffer end percent has changed.
10629     *
10630     * > This function is called by {@link Tech#manualProgressOn}
10631     *
10632     * @param {EventTarget~Event} event
10633     *        The `ready` event that caused this to run.
10634     *
10635     * @listens Tech#ready
10636     * @fires Tech#progress
10637     */
10638    ;
10639
10640    _proto.trackProgress = function trackProgress(event) {
10641      this.stopTrackingProgress();
10642      this.progressInterval = this.setInterval(bind(this, function () {
10643        // Don't trigger unless buffered amount is greater than last time
10644        var numBufferedPercent = this.bufferedPercent();
10645
10646        if (this.bufferedPercent_ !== numBufferedPercent) {
10647          /**
10648           * See {@link Player#progress}
10649           *
10650           * @event Tech#progress
10651           * @type {EventTarget~Event}
10652           */
10653          this.trigger('progress');
10654        }
10655
10656        this.bufferedPercent_ = numBufferedPercent;
10657
10658        if (numBufferedPercent === 1) {
10659          this.stopTrackingProgress();
10660        }
10661      }), 500);
10662    }
10663    /**
10664     * Update our internal duration on a `durationchange` event by calling
10665     * {@link Tech#duration}.
10666     *
10667     * @param {EventTarget~Event} event
10668     *        The `durationchange` event that caused this to run.
10669     *
10670     * @listens Tech#durationchange
10671     */
10672    ;
10673
10674    _proto.onDurationChange = function onDurationChange(event) {
10675      this.duration_ = this.duration();
10676    }
10677    /**
10678     * Get and create a `TimeRange` object for buffering.
10679     *
10680     * @return {TimeRange}
10681     *         The time range object that was created.
10682     */
10683    ;
10684
10685    _proto.buffered = function buffered() {
10686      return createTimeRanges(0, 0);
10687    }
10688    /**
10689     * Get the percentage of the current video that is currently buffered.
10690     *
10691     * @return {number}
10692     *         A number from 0 to 1 that represents the decimal percentage of the
10693     *         video that is buffered.
10694     *
10695     */
10696    ;
10697
10698    _proto.bufferedPercent = function bufferedPercent$1() {
10699      return bufferedPercent(this.buffered(), this.duration_);
10700    }
10701    /**
10702     * Turn off the polyfill for `progress` events that was created in
10703     * {@link Tech#manualProgressOn}
10704     * Stop manually tracking progress events by clearing the interval that was set in
10705     * {@link Tech#trackProgress}.
10706     */
10707    ;
10708
10709    _proto.stopTrackingProgress = function stopTrackingProgress() {
10710      this.clearInterval(this.progressInterval);
10711    }
10712    /**
10713     * Polyfill the `timeupdate` event for browsers that don't support it.
10714     *
10715     * @see {@link Tech#trackCurrentTime}
10716     */
10717    ;
10718
10719    _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10720      this.manualTimeUpdates = true;
10721      this.on('play', this.trackCurrentTime);
10722      this.on('pause', this.stopTrackingCurrentTime);
10723    }
10724    /**
10725     * Turn off the polyfill for `timeupdate` events that was created in
10726     * {@link Tech#manualTimeUpdatesOn}
10727     */
10728    ;
10729
10730    _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10731      this.manualTimeUpdates = false;
10732      this.stopTrackingCurrentTime();
10733      this.off('play', this.trackCurrentTime);
10734      this.off('pause', this.stopTrackingCurrentTime);
10735    }
10736    /**
10737     * Sets up an interval function to track current time and trigger `timeupdate` every
10738     * 250 milliseconds.
10739     *
10740     * @listens Tech#play
10741     * @triggers Tech#timeupdate
10742     */
10743    ;
10744
10745    _proto.trackCurrentTime = function trackCurrentTime() {
10746      if (this.currentTimeInterval) {
10747        this.stopTrackingCurrentTime();
10748      }
10749
10750      this.currentTimeInterval = this.setInterval(function () {
10751        /**
10752         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10753         *
10754         * @event Tech#timeupdate
10755         * @type {EventTarget~Event}
10756         */
10757        this.trigger({
10758          type: 'timeupdate',
10759          target: this,
10760          manuallyTriggered: true
10761        }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10762      }, 250);
10763    }
10764    /**
10765     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10766     * `timeupdate` event is no longer triggered.
10767     *
10768     * @listens {Tech#pause}
10769     */
10770    ;
10771
10772    _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10773      this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
10774      // the progress bar won't make it all the way to the end
10775
10776      this.trigger({
10777        type: 'timeupdate',
10778        target: this,
10779        manuallyTriggered: true
10780      });
10781    }
10782    /**
10783     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10784     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10785     *
10786     * @fires Component#dispose
10787     */
10788    ;
10789
10790    _proto.dispose = function dispose() {
10791      // clear out all tracks because we can't reuse them between techs
10792      this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
10793
10794      if (this.manualProgress) {
10795        this.manualProgressOff();
10796      }
10797
10798      if (this.manualTimeUpdates) {
10799        this.manualTimeUpdatesOff();
10800      }
10801
10802      _Component.prototype.dispose.call(this);
10803    }
10804    /**
10805     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10806     *
10807     * > Note: Techs without source handlers should call this between sources for `video`
10808     *         & `audio` tracks. You don't want to use them between tracks!
10809     *
10810     * @param {string[]|string} types
10811     *        TrackList names to clear, valid names are `video`, `audio`, and
10812     *        `text`.
10813     */
10814    ;
10815
10816    _proto.clearTracks = function clearTracks(types) {
10817      var _this3 = this;
10818
10819      types = [].concat(types); // clear out all tracks because we can't reuse them between techs
10820
10821      types.forEach(function (type) {
10822        var list = _this3[type + "Tracks"]() || [];
10823        var i = list.length;
10824
10825        while (i--) {
10826          var track = list[i];
10827
10828          if (type === 'text') {
10829            _this3.removeRemoteTextTrack(track);
10830          }
10831
10832          list.removeTrack(track);
10833        }
10834      });
10835    }
10836    /**
10837     * Remove any TextTracks added via addRemoteTextTrack that are
10838     * flagged for automatic garbage collection
10839     */
10840    ;
10841
10842    _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10843      var list = this.autoRemoteTextTracks_ || [];
10844      var i = list.length;
10845
10846      while (i--) {
10847        var track = list[i];
10848        this.removeRemoteTextTrack(track);
10849      }
10850    }
10851    /**
10852     * Reset the tech, which will removes all sources and reset the internal readyState.
10853     *
10854     * @abstract
10855     */
10856    ;
10857
10858    _proto.reset = function reset() {}
10859    /**
10860     * Get or set an error on the Tech.
10861     *
10862     * @param {MediaError} [err]
10863     *        Error to set on the Tech
10864     *
10865     * @return {MediaError|null}
10866     *         The current error object on the tech, or null if there isn't one.
10867     */
10868    ;
10869
10870    _proto.error = function error(err) {
10871      if (err !== undefined) {
10872        this.error_ = new MediaError(err);
10873        this.trigger('error');
10874      }
10875
10876      return this.error_;
10877    }
10878    /**
10879     * Returns the `TimeRange`s that have been played through for the current source.
10880     *
10881     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10882     *         It only checks whether the source has played at all or not.
10883     *
10884     * @return {TimeRange}
10885     *         - A single time range if this video has played
10886     *         - An empty set of ranges if not.
10887     */
10888    ;
10889
10890    _proto.played = function played() {
10891      if (this.hasStarted_) {
10892        return createTimeRanges(0, 0);
10893      }
10894
10895      return createTimeRanges();
10896    }
10897    /**
10898     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10899     * previously called.
10900     *
10901     * @fires Tech#timeupdate
10902     */
10903    ;
10904
10905    _proto.setCurrentTime = function setCurrentTime() {
10906      // improve the accuracy of manual timeupdates
10907      if (this.manualTimeUpdates) {
10908        /**
10909         * A manual `timeupdate` event.
10910         *
10911         * @event Tech#timeupdate
10912         * @type {EventTarget~Event}
10913         */
10914        this.trigger({
10915          type: 'timeupdate',
10916          target: this,
10917          manuallyTriggered: true
10918        });
10919      }
10920    }
10921    /**
10922     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10923     * {@link TextTrackList} events.
10924     *
10925     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10926     *
10927     * @fires Tech#audiotrackchange
10928     * @fires Tech#videotrackchange
10929     * @fires Tech#texttrackchange
10930     */
10931    ;
10932
10933    _proto.initTrackListeners = function initTrackListeners() {
10934      var _this4 = this;
10935
10936      /**
10937        * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10938        *
10939        * @event Tech#audiotrackchange
10940        * @type {EventTarget~Event}
10941        */
10942
10943      /**
10944        * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10945        *
10946        * @event Tech#videotrackchange
10947        * @type {EventTarget~Event}
10948        */
10949
10950      /**
10951        * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10952        *
10953        * @event Tech#texttrackchange
10954        * @type {EventTarget~Event}
10955        */
10956      NORMAL.names.forEach(function (name) {
10957        var props = NORMAL[name];
10958
10959        var trackListChanges = function trackListChanges() {
10960          _this4.trigger(name + "trackchange");
10961        };
10962
10963        var tracks = _this4[props.getterName]();
10964
10965        tracks.addEventListener('removetrack', trackListChanges);
10966        tracks.addEventListener('addtrack', trackListChanges);
10967
10968        _this4.on('dispose', function () {
10969          tracks.removeEventListener('removetrack', trackListChanges);
10970          tracks.removeEventListener('addtrack', trackListChanges);
10971        });
10972      });
10973    }
10974    /**
10975     * Emulate TextTracks using vtt.js if necessary
10976     *
10977     * @fires Tech#vttjsloaded
10978     * @fires Tech#vttjserror
10979     */
10980    ;
10981
10982    _proto.addWebVttScript_ = function addWebVttScript_() {
10983      var _this5 = this;
10984
10985      if (window$1.WebVTT) {
10986        return;
10987      } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10988      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10989      // before inserting the WebVTT script
10990
10991
10992      if (document.body.contains(this.el())) {
10993        // load via require if available and vtt.js script location was not passed in
10994        // as an option. novtt builds will turn the above require call into an empty object
10995        // which will cause this if check to always fail.
10996        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10997          this.trigger('vttjsloaded');
10998          return;
10999        } // load vtt.js via the script location option or the cdn of no location was
11000        // passed in
11001
11002
11003        var script = document.createElement('script');
11004        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
11005
11006        script.onload = function () {
11007          /**
11008           * Fired when vtt.js is loaded.
11009           *
11010           * @event Tech#vttjsloaded
11011           * @type {EventTarget~Event}
11012           */
11013          _this5.trigger('vttjsloaded');
11014        };
11015
11016        script.onerror = function () {
11017          /**
11018           * Fired when vtt.js was not loaded due to an error
11019           *
11020           * @event Tech#vttjsloaded
11021           * @type {EventTarget~Event}
11022           */
11023          _this5.trigger('vttjserror');
11024        };
11025
11026        this.on('dispose', function () {
11027          script.onload = null;
11028          script.onerror = null;
11029        }); // but have not loaded yet and we set it to true before the inject so that
11030        // we don't overwrite the injected window.WebVTT if it loads right away
11031
11032        window$1.WebVTT = true;
11033        this.el().parentNode.appendChild(script);
11034      } else {
11035        this.ready(this.addWebVttScript_);
11036      }
11037    }
11038    /**
11039     * Emulate texttracks
11040     *
11041     */
11042    ;
11043
11044    _proto.emulateTextTracks = function emulateTextTracks() {
11045      var _this6 = this;
11046
11047      var tracks = this.textTracks();
11048      var remoteTracks = this.remoteTextTracks();
11049
11050      var handleAddTrack = function handleAddTrack(e) {
11051        return tracks.addTrack(e.track);
11052      };
11053
11054      var handleRemoveTrack = function handleRemoveTrack(e) {
11055        return tracks.removeTrack(e.track);
11056      };
11057
11058      remoteTracks.on('addtrack', handleAddTrack);
11059      remoteTracks.on('removetrack', handleRemoveTrack);
11060      this.addWebVttScript_();
11061
11062      var updateDisplay = function updateDisplay() {
11063        return _this6.trigger('texttrackchange');
11064      };
11065
11066      var textTracksChanges = function textTracksChanges() {
11067        updateDisplay();
11068
11069        for (var i = 0; i < tracks.length; i++) {
11070          var track = tracks[i];
11071          track.removeEventListener('cuechange', updateDisplay);
11072
11073          if (track.mode === 'showing') {
11074            track.addEventListener('cuechange', updateDisplay);
11075          }
11076        }
11077      };
11078
11079      textTracksChanges();
11080      tracks.addEventListener('change', textTracksChanges);
11081      tracks.addEventListener('addtrack', textTracksChanges);
11082      tracks.addEventListener('removetrack', textTracksChanges);
11083      this.on('dispose', function () {
11084        remoteTracks.off('addtrack', handleAddTrack);
11085        remoteTracks.off('removetrack', handleRemoveTrack);
11086        tracks.removeEventListener('change', textTracksChanges);
11087        tracks.removeEventListener('addtrack', textTracksChanges);
11088        tracks.removeEventListener('removetrack', textTracksChanges);
11089
11090        for (var i = 0; i < tracks.length; i++) {
11091          var track = tracks[i];
11092          track.removeEventListener('cuechange', updateDisplay);
11093        }
11094      });
11095    }
11096    /**
11097     * Create and returns a remote {@link TextTrack} object.
11098     *
11099     * @param {string} kind
11100     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
11101     *
11102     * @param {string} [label]
11103     *        Label to identify the text track
11104     *
11105     * @param {string} [language]
11106     *        Two letter language abbreviation
11107     *
11108     * @return {TextTrack}
11109     *         The TextTrack that gets created.
11110     */
11111    ;
11112
11113    _proto.addTextTrack = function addTextTrack(kind, label, language) {
11114      if (!kind) {
11115        throw new Error('TextTrack kind is required but was not provided');
11116      }
11117
11118      return createTrackHelper(this, kind, label, language);
11119    }
11120    /**
11121     * Create an emulated TextTrack for use by addRemoteTextTrack
11122     *
11123     * This is intended to be overridden by classes that inherit from
11124     * Tech in order to create native or custom TextTracks.
11125     *
11126     * @param {Object} options
11127     *        The object should contain the options to initialize the TextTrack with.
11128     *
11129     * @param {string} [options.kind]
11130     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
11131     *
11132     * @param {string} [options.label].
11133     *        Label to identify the text track
11134     *
11135     * @param {string} [options.language]
11136     *        Two letter language abbreviation.
11137     *
11138     * @return {HTMLTrackElement}
11139     *         The track element that gets created.
11140     */
11141    ;
11142
11143    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
11144      var track = mergeOptions(options, {
11145        tech: this
11146      });
11147      return new REMOTE.remoteTextEl.TrackClass(track);
11148    }
11149    /**
11150     * Creates a remote text track object and returns an html track element.
11151     *
11152     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
11153     *
11154     * @param {Object} options
11155     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
11156     *
11157     * @param {boolean} [manualCleanup=true]
11158     *        - When false: the TextTrack will be automatically removed from the video
11159     *          element whenever the source changes
11160     *        - When True: The TextTrack will have to be cleaned up manually
11161     *
11162     * @return {HTMLTrackElement}
11163     *         An Html Track Element.
11164     *
11165     * @deprecated The default functionality for this function will be equivalent
11166     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
11167     *             also be removed.
11168     */
11169    ;
11170
11171    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
11172      var _this7 = this;
11173
11174      if (options === void 0) {
11175        options = {};
11176      }
11177
11178      var htmlTrackElement = this.createRemoteTextTrack(options);
11179
11180      if (manualCleanup !== true && manualCleanup !== false) {
11181        // deprecation warning
11182        log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
11183        manualCleanup = true;
11184      } // store HTMLTrackElement and TextTrack to remote list
11185
11186
11187      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
11188      this.remoteTextTracks().addTrack(htmlTrackElement.track);
11189
11190      if (manualCleanup !== true) {
11191        // create the TextTrackList if it doesn't exist
11192        this.ready(function () {
11193          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
11194        });
11195      }
11196
11197      return htmlTrackElement;
11198    }
11199    /**
11200     * Remove a remote text track from the remote `TextTrackList`.
11201     *
11202     * @param {TextTrack} track
11203     *        `TextTrack` to remove from the `TextTrackList`
11204     */
11205    ;
11206
11207    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
11208      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
11209
11210      this.remoteTextTrackEls().removeTrackElement_(trackElement);
11211      this.remoteTextTracks().removeTrack(track);
11212      this.autoRemoteTextTracks_.removeTrack(track);
11213    }
11214    /**
11215     * Gets available media playback quality metrics as specified by the W3C's Media
11216     * Playback Quality API.
11217     *
11218     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
11219     *
11220     * @return {Object}
11221     *         An object with supported media playback quality metrics
11222     *
11223     * @abstract
11224     */
11225    ;
11226
11227    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
11228      return {};
11229    }
11230    /**
11231     * Attempt to create a floating video window always on top of other windows
11232     * so that users may continue consuming media while they interact with other
11233     * content sites, or applications on their device.
11234     *
11235     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
11236     *
11237     * @return {Promise|undefined}
11238     *         A promise with a Picture-in-Picture window if the browser supports
11239     *         Promises (or one was passed in as an option). It returns undefined
11240     *         otherwise.
11241     *
11242     * @abstract
11243     */
11244    ;
11245
11246    _proto.requestPictureInPicture = function requestPictureInPicture() {
11247      var PromiseClass = this.options_.Promise || window$1.Promise;
11248
11249      if (PromiseClass) {
11250        return PromiseClass.reject();
11251      }
11252    }
11253    /**
11254     * A method to set a poster from a `Tech`.
11255     *
11256     * @abstract
11257     */
11258    ;
11259
11260    _proto.setPoster = function setPoster() {}
11261    /**
11262     * A method to check for the presence of the 'playsinline' <video> attribute.
11263     *
11264     * @abstract
11265     */
11266    ;
11267
11268    _proto.playsinline = function playsinline() {}
11269    /**
11270     * A method to set or unset the 'playsinline' <video> attribute.
11271     *
11272     * @abstract
11273     */
11274    ;
11275
11276    _proto.setPlaysinline = function setPlaysinline() {}
11277    /**
11278     * Attempt to force override of native audio tracks.
11279     *
11280     * @param {boolean} override - If set to true native audio will be overridden,
11281     * otherwise native audio will potentially be used.
11282     *
11283     * @abstract
11284     */
11285    ;
11286
11287    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
11288    /**
11289     * Attempt to force override of native video tracks.
11290     *
11291     * @param {boolean} override - If set to true native video will be overridden,
11292     * otherwise native video will potentially be used.
11293     *
11294     * @abstract
11295     */
11296    ;
11297
11298    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
11299    /*
11300     * Check if the tech can support the given mime-type.
11301     *
11302     * The base tech does not support any type, but source handlers might
11303     * overwrite this.
11304     *
11305     * @param  {string} type
11306     *         The mimetype to check for support
11307     *
11308     * @return {string}
11309     *         'probably', 'maybe', or empty string
11310     *
11311     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11312     *
11313     * @abstract
11314     */
11315    ;
11316
11317    _proto.canPlayType = function canPlayType() {
11318      return '';
11319    }
11320    /**
11321     * Check if the type is supported by this tech.
11322     *
11323     * The base tech does not support any type, but source handlers might
11324     * overwrite this.
11325     *
11326     * @param {string} type
11327     *        The media type to check
11328     * @return {string} Returns the native video element's response
11329     */
11330    ;
11331
11332    Tech.canPlayType = function canPlayType() {
11333      return '';
11334    }
11335    /**
11336     * Check if the tech can support the given source
11337     *
11338     * @param {Object} srcObj
11339     *        The source object
11340     * @param {Object} options
11341     *        The options passed to the tech
11342     * @return {string} 'probably', 'maybe', or '' (empty string)
11343     */
11344    ;
11345
11346    Tech.canPlaySource = function canPlaySource(srcObj, options) {
11347      return Tech.canPlayType(srcObj.type);
11348    }
11349    /*
11350     * Return whether the argument is a Tech or not.
11351     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11352     *
11353     * @param {Object} component
11354     *        The item to check
11355     *
11356     * @return {boolean}
11357     *         Whether it is a tech or not
11358     *         - True if it is a tech
11359     *         - False if it is not
11360     */
11361    ;
11362
11363    Tech.isTech = function isTech(component) {
11364      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11365    }
11366    /**
11367     * Registers a `Tech` into a shared list for videojs.
11368     *
11369     * @param {string} name
11370     *        Name of the `Tech` to register.
11371     *
11372     * @param {Object} tech
11373     *        The `Tech` class to register.
11374     */
11375    ;
11376
11377    Tech.registerTech = function registerTech(name, tech) {
11378      if (!Tech.techs_) {
11379        Tech.techs_ = {};
11380      }
11381
11382      if (!Tech.isTech(tech)) {
11383        throw new Error("Tech " + name + " must be a Tech");
11384      }
11385
11386      if (!Tech.canPlayType) {
11387        throw new Error('Techs must have a static canPlayType method on them');
11388      }
11389
11390      if (!Tech.canPlaySource) {
11391        throw new Error('Techs must have a static canPlaySource method on them');
11392      }
11393
11394      name = toTitleCase(name);
11395      Tech.techs_[name] = tech;
11396      Tech.techs_[toLowerCase(name)] = tech;
11397
11398      if (name !== 'Tech') {
11399        // camel case the techName for use in techOrder
11400        Tech.defaultTechOrder_.push(name);
11401      }
11402
11403      return tech;
11404    }
11405    /**
11406     * Get a `Tech` from the shared list by name.
11407     *
11408     * @param {string} name
11409     *        `camelCase` or `TitleCase` name of the Tech to get
11410     *
11411     * @return {Tech|undefined}
11412     *         The `Tech` or undefined if there was no tech with the name requested.
11413     */
11414    ;
11415
11416    Tech.getTech = function getTech(name) {
11417      if (!name) {
11418        return;
11419      }
11420
11421      if (Tech.techs_ && Tech.techs_[name]) {
11422        return Tech.techs_[name];
11423      }
11424
11425      name = toTitleCase(name);
11426
11427      if (window$1 && window$1.videojs && window$1.videojs[name]) {
11428        log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
11429        return window$1.videojs[name];
11430      }
11431    };
11432
11433    return Tech;
11434  }(Component);
11435  /**
11436   * Get the {@link VideoTrackList}
11437   *
11438   * @returns {VideoTrackList}
11439   * @method Tech.prototype.videoTracks
11440   */
11441
11442  /**
11443   * Get the {@link AudioTrackList}
11444   *
11445   * @returns {AudioTrackList}
11446   * @method Tech.prototype.audioTracks
11447   */
11448
11449  /**
11450   * Get the {@link TextTrackList}
11451   *
11452   * @returns {TextTrackList}
11453   * @method Tech.prototype.textTracks
11454   */
11455
11456  /**
11457   * Get the remote element {@link TextTrackList}
11458   *
11459   * @returns {TextTrackList}
11460   * @method Tech.prototype.remoteTextTracks
11461   */
11462
11463  /**
11464   * Get the remote element {@link HtmlTrackElementList}
11465   *
11466   * @returns {HtmlTrackElementList}
11467   * @method Tech.prototype.remoteTextTrackEls
11468   */
11469
11470
11471  ALL.names.forEach(function (name) {
11472    var props = ALL[name];
11473
11474    Tech.prototype[props.getterName] = function () {
11475      this[props.privateName] = this[props.privateName] || new props.ListClass();
11476      return this[props.privateName];
11477    };
11478  });
11479  /**
11480   * List of associated text tracks
11481   *
11482   * @type {TextTrackList}
11483   * @private
11484   * @property Tech#textTracks_
11485   */
11486
11487  /**
11488   * List of associated audio tracks.
11489   *
11490   * @type {AudioTrackList}
11491   * @private
11492   * @property Tech#audioTracks_
11493   */
11494
11495  /**
11496   * List of associated video tracks.
11497   *
11498   * @type {VideoTrackList}
11499   * @private
11500   * @property Tech#videoTracks_
11501   */
11502
11503  /**
11504   * Boolean indicating whether the `Tech` supports volume control.
11505   *
11506   * @type {boolean}
11507   * @default
11508   */
11509
11510  Tech.prototype.featuresVolumeControl = true;
11511  /**
11512   * Boolean indicating whether the `Tech` supports muting volume.
11513   *
11514   * @type {bolean}
11515   * @default
11516   */
11517
11518  Tech.prototype.featuresMuteControl = true;
11519  /**
11520   * Boolean indicating whether the `Tech` supports fullscreen resize control.
11521   * Resizing plugins using request fullscreen reloads the plugin
11522   *
11523   * @type {boolean}
11524   * @default
11525   */
11526
11527  Tech.prototype.featuresFullscreenResize = false;
11528  /**
11529   * Boolean indicating whether the `Tech` supports changing the speed at which the video
11530   * plays. Examples:
11531   *   - Set player to play 2x (twice) as fast
11532   *   - Set player to play 0.5x (half) as fast
11533   *
11534   * @type {boolean}
11535   * @default
11536   */
11537
11538  Tech.prototype.featuresPlaybackRate = false;
11539  /**
11540   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
11541   * not triggered by video-js-swf. This will be used to determine if
11542   * {@link Tech#manualProgressOn} should be called.
11543   *
11544   * @type {boolean}
11545   * @default
11546   */
11547
11548  Tech.prototype.featuresProgressEvents = false;
11549  /**
11550   * Boolean indicating whether the `Tech` supports the `sourceset` event.
11551   *
11552   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
11553   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
11554   * a new source.
11555   *
11556   * @type {boolean}
11557   * @default
11558   */
11559
11560  Tech.prototype.featuresSourceset = false;
11561  /**
11562   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
11563   * not triggered by video-js-swf. This will be used to determine if
11564   * {@link Tech#manualTimeUpdates} should be called.
11565   *
11566   * @type {boolean}
11567   * @default
11568   */
11569
11570  Tech.prototype.featuresTimeupdateEvents = false;
11571  /**
11572   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
11573   * This will help us integrate with native `TextTrack`s if the browser supports them.
11574   *
11575   * @type {boolean}
11576   * @default
11577   */
11578
11579  Tech.prototype.featuresNativeTextTracks = false;
11580  /**
11581   * A functional mixin for techs that want to use the Source Handler pattern.
11582   * Source handlers are scripts for handling specific formats.
11583   * The source handler pattern is used for adaptive formats (HLS, DASH) that
11584   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
11585   * Example: `Tech.withSourceHandlers.call(MyTech);`
11586   *
11587   * @param {Tech} _Tech
11588   *        The tech to add source handler functions to.
11589   *
11590   * @mixes Tech~SourceHandlerAdditions
11591   */
11592
11593  Tech.withSourceHandlers = function (_Tech) {
11594    /**
11595     * Register a source handler
11596     *
11597     * @param {Function} handler
11598     *        The source handler class
11599     *
11600     * @param {number} [index]
11601     *        Register it at the following index
11602     */
11603    _Tech.registerSourceHandler = function (handler, index) {
11604      var handlers = _Tech.sourceHandlers;
11605
11606      if (!handlers) {
11607        handlers = _Tech.sourceHandlers = [];
11608      }
11609
11610      if (index === undefined) {
11611        // add to the end of the list
11612        index = handlers.length;
11613      }
11614
11615      handlers.splice(index, 0, handler);
11616    };
11617    /**
11618     * Check if the tech can support the given type. Also checks the
11619     * Techs sourceHandlers.
11620     *
11621     * @param {string} type
11622     *         The mimetype to check.
11623     *
11624     * @return {string}
11625     *         'probably', 'maybe', or '' (empty string)
11626     */
11627
11628
11629    _Tech.canPlayType = function (type) {
11630      var handlers = _Tech.sourceHandlers || [];
11631      var can;
11632
11633      for (var i = 0; i < handlers.length; i++) {
11634        can = handlers[i].canPlayType(type);
11635
11636        if (can) {
11637          return can;
11638        }
11639      }
11640
11641      return '';
11642    };
11643    /**
11644     * Returns the first source handler that supports the source.
11645     *
11646     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
11647     *
11648     * @param {Tech~SourceObject} source
11649     *        The source object
11650     *
11651     * @param {Object} options
11652     *        The options passed to the tech
11653     *
11654     * @return {SourceHandler|null}
11655     *          The first source handler that supports the source or null if
11656     *          no SourceHandler supports the source
11657     */
11658
11659
11660    _Tech.selectSourceHandler = function (source, options) {
11661      var handlers = _Tech.sourceHandlers || [];
11662      var can;
11663
11664      for (var i = 0; i < handlers.length; i++) {
11665        can = handlers[i].canHandleSource(source, options);
11666
11667        if (can) {
11668          return handlers[i];
11669        }
11670      }
11671
11672      return null;
11673    };
11674    /**
11675     * Check if the tech can support the given source.
11676     *
11677     * @param {Tech~SourceObject} srcObj
11678     *        The source object
11679     *
11680     * @param {Object} options
11681     *        The options passed to the tech
11682     *
11683     * @return {string}
11684     *         'probably', 'maybe', or '' (empty string)
11685     */
11686
11687
11688    _Tech.canPlaySource = function (srcObj, options) {
11689      var sh = _Tech.selectSourceHandler(srcObj, options);
11690
11691      if (sh) {
11692        return sh.canHandleSource(srcObj, options);
11693      }
11694
11695      return '';
11696    };
11697    /**
11698     * When using a source handler, prefer its implementation of
11699     * any function normally provided by the tech.
11700     */
11701
11702
11703    var deferrable = ['seekable', 'seeking', 'duration'];
11704    /**
11705     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11706     * function if it exists, with a fallback to the Techs seekable function.
11707     *
11708     * @method _Tech.seekable
11709     */
11710
11711    /**
11712     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11713     * function if it exists, otherwise it will fallback to the techs duration function.
11714     *
11715     * @method _Tech.duration
11716     */
11717
11718    deferrable.forEach(function (fnName) {
11719      var originalFn = this[fnName];
11720
11721      if (typeof originalFn !== 'function') {
11722        return;
11723      }
11724
11725      this[fnName] = function () {
11726        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11727          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11728        }
11729
11730        return originalFn.apply(this, arguments);
11731      };
11732    }, _Tech.prototype);
11733    /**
11734     * Create a function for setting the source using a source object
11735     * and source handlers.
11736     * Should never be called unless a source handler was found.
11737     *
11738     * @param {Tech~SourceObject} source
11739     *        A source object with src and type keys
11740     */
11741
11742    _Tech.prototype.setSource = function (source) {
11743      var sh = _Tech.selectSourceHandler(source, this.options_);
11744
11745      if (!sh) {
11746        // Fall back to a native source hander when unsupported sources are
11747        // deliberately set
11748        if (_Tech.nativeSourceHandler) {
11749          sh = _Tech.nativeSourceHandler;
11750        } else {
11751          log.error('No source handler found for the current source.');
11752        }
11753      } // Dispose any existing source handler
11754
11755
11756      this.disposeSourceHandler();
11757      this.off('dispose', this.disposeSourceHandler);
11758
11759      if (sh !== _Tech.nativeSourceHandler) {
11760        this.currentSource_ = source;
11761      }
11762
11763      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11764      this.one('dispose', this.disposeSourceHandler);
11765    };
11766    /**
11767     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11768     *
11769     * @listens Tech#dispose
11770     */
11771
11772
11773    _Tech.prototype.disposeSourceHandler = function () {
11774      // if we have a source and get another one
11775      // then we are loading something new
11776      // than clear all of our current tracks
11777      if (this.currentSource_) {
11778        this.clearTracks(['audio', 'video']);
11779        this.currentSource_ = null;
11780      } // always clean up auto-text tracks
11781
11782
11783      this.cleanupAutoTextTracks();
11784
11785      if (this.sourceHandler_) {
11786        if (this.sourceHandler_.dispose) {
11787          this.sourceHandler_.dispose();
11788        }
11789
11790        this.sourceHandler_ = null;
11791      }
11792    };
11793  }; // The base Tech class needs to be registered as a Component. It is the only
11794  // Tech that can be registered as a Component.
11795
11796
11797  Component.registerComponent('Tech', Tech);
11798  Tech.registerTech('Tech', Tech);
11799  /**
11800   * A list of techs that should be added to techOrder on Players
11801   *
11802   * @private
11803   */
11804
11805  Tech.defaultTechOrder_ = [];
11806
11807  /**
11808   * @file middleware.js
11809   * @module middleware
11810   */
11811  var middlewares = {};
11812  var middlewareInstances = {};
11813  var TERMINATOR = {};
11814  /**
11815   * A middleware object is a plain JavaScript object that has methods that
11816   * match the {@link Tech} methods found in the lists of allowed
11817   * {@link module:middleware.allowedGetters|getters},
11818   * {@link module:middleware.allowedSetters|setters}, and
11819   * {@link module:middleware.allowedMediators|mediators}.
11820   *
11821   * @typedef {Object} MiddlewareObject
11822   */
11823
11824  /**
11825   * A middleware factory function that should return a
11826   * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
11827   *
11828   * This factory will be called for each player when needed, with the player
11829   * passed in as an argument.
11830   *
11831   * @callback MiddlewareFactory
11832   * @param {Player} player
11833   *        A Video.js player.
11834   */
11835
11836  /**
11837   * Define a middleware that the player should use by way of a factory function
11838   * that returns a middleware object.
11839   *
11840   * @param  {string} type
11841   *         The MIME type to match or `"*"` for all MIME types.
11842   *
11843   * @param  {MiddlewareFactory} middleware
11844   *         A middleware factory function that will be executed for
11845   *         matching types.
11846   */
11847
11848  function use(type, middleware) {
11849    middlewares[type] = middlewares[type] || [];
11850    middlewares[type].push(middleware);
11851  }
11852  /**
11853   * Asynchronously sets a source using middleware by recursing through any
11854   * matching middlewares and calling `setSource` on each, passing along the
11855   * previous returned value each time.
11856   *
11857   * @param  {Player} player
11858   *         A {@link Player} instance.
11859   *
11860   * @param  {Tech~SourceObject} src
11861   *         A source object.
11862   *
11863   * @param  {Function}
11864   *         The next middleware to run.
11865   */
11866
11867  function setSource(player, src, next) {
11868    player.setTimeout(function () {
11869      return setSourceHelper(src, middlewares[src.type], next, player);
11870    }, 1);
11871  }
11872  /**
11873   * When the tech is set, passes the tech to each middleware's `setTech` method.
11874   *
11875   * @param {Object[]} middleware
11876   *        An array of middleware instances.
11877   *
11878   * @param {Tech} tech
11879   *        A Video.js tech.
11880   */
11881
11882  function setTech(middleware, tech) {
11883    middleware.forEach(function (mw) {
11884      return mw.setTech && mw.setTech(tech);
11885    });
11886  }
11887  /**
11888   * Calls a getter on the tech first, through each middleware
11889   * from right to left to the player.
11890   *
11891   * @param  {Object[]} middleware
11892   *         An array of middleware instances.
11893   *
11894   * @param  {Tech} tech
11895   *         The current tech.
11896   *
11897   * @param  {string} method
11898   *         A method name.
11899   *
11900   * @return {Mixed}
11901   *         The final value from the tech after middleware has intercepted it.
11902   */
11903
11904  function get(middleware, tech, method) {
11905    return middleware.reduceRight(middlewareIterator(method), tech[method]());
11906  }
11907  /**
11908   * Takes the argument given to the player and calls the setter method on each
11909   * middleware from left to right to the tech.
11910   *
11911   * @param  {Object[]} middleware
11912   *         An array of middleware instances.
11913   *
11914   * @param  {Tech} tech
11915   *         The current tech.
11916   *
11917   * @param  {string} method
11918   *         A method name.
11919   *
11920   * @param  {Mixed} arg
11921   *         The value to set on the tech.
11922   *
11923   * @return {Mixed}
11924   *         The return value of the `method` of the `tech`.
11925   */
11926
11927  function set(middleware, tech, method, arg) {
11928    return tech[method](middleware.reduce(middlewareIterator(method), arg));
11929  }
11930  /**
11931   * Takes the argument given to the player and calls the `call` version of the
11932   * method on each middleware from left to right.
11933   *
11934   * Then, call the passed in method on the tech and return the result unchanged
11935   * back to the player, through middleware, this time from right to left.
11936   *
11937   * @param  {Object[]} middleware
11938   *         An array of middleware instances.
11939   *
11940   * @param  {Tech} tech
11941   *         The current tech.
11942   *
11943   * @param  {string} method
11944   *         A method name.
11945   *
11946   * @param  {Mixed} arg
11947   *         The value to set on the tech.
11948   *
11949   * @return {Mixed}
11950   *         The return value of the `method` of the `tech`, regardless of the
11951   *         return values of middlewares.
11952   */
11953
11954  function mediate(middleware, tech, method, arg) {
11955    if (arg === void 0) {
11956      arg = null;
11957    }
11958
11959    var callMethod = 'call' + toTitleCase(method);
11960    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11961    var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
11962    // prevent confusion if a techs method actually returns null.
11963
11964    var returnValue = terminated ? null : tech[method](middlewareValue);
11965    executeRight(middleware, method, returnValue, terminated);
11966    return returnValue;
11967  }
11968  /**
11969   * Enumeration of allowed getters where the keys are method names.
11970   *
11971   * @type {Object}
11972   */
11973
11974  var allowedGetters = {
11975    buffered: 1,
11976    currentTime: 1,
11977    duration: 1,
11978    muted: 1,
11979    played: 1,
11980    paused: 1,
11981    seekable: 1,
11982    volume: 1
11983  };
11984  /**
11985   * Enumeration of allowed setters where the keys are method names.
11986   *
11987   * @type {Object}
11988   */
11989
11990  var allowedSetters = {
11991    setCurrentTime: 1,
11992    setMuted: 1,
11993    setVolume: 1
11994  };
11995  /**
11996   * Enumeration of allowed mediators where the keys are method names.
11997   *
11998   * @type {Object}
11999   */
12000
12001  var allowedMediators = {
12002    play: 1,
12003    pause: 1
12004  };
12005
12006  function middlewareIterator(method) {
12007    return function (value, mw) {
12008      // if the previous middleware terminated, pass along the termination
12009      if (value === TERMINATOR) {
12010        return TERMINATOR;
12011      }
12012
12013      if (mw[method]) {
12014        return mw[method](value);
12015      }
12016
12017      return value;
12018    };
12019  }
12020
12021  function executeRight(mws, method, value, terminated) {
12022    for (var i = mws.length - 1; i >= 0; i--) {
12023      var mw = mws[i];
12024
12025      if (mw[method]) {
12026        mw[method](terminated, value);
12027      }
12028    }
12029  }
12030  /**
12031   * Clear the middleware cache for a player.
12032   *
12033   * @param  {Player} player
12034   *         A {@link Player} instance.
12035   */
12036
12037
12038  function clearCacheForPlayer(player) {
12039    middlewareInstances[player.id()] = null;
12040  }
12041  /**
12042   * {
12043   *  [playerId]: [[mwFactory, mwInstance], ...]
12044   * }
12045   *
12046   * @private
12047   */
12048
12049  function getOrCreateFactory(player, mwFactory) {
12050    var mws = middlewareInstances[player.id()];
12051    var mw = null;
12052
12053    if (mws === undefined || mws === null) {
12054      mw = mwFactory(player);
12055      middlewareInstances[player.id()] = [[mwFactory, mw]];
12056      return mw;
12057    }
12058
12059    for (var i = 0; i < mws.length; i++) {
12060      var _mws$i = mws[i],
12061          mwf = _mws$i[0],
12062          mwi = _mws$i[1];
12063
12064      if (mwf !== mwFactory) {
12065        continue;
12066      }
12067
12068      mw = mwi;
12069    }
12070
12071    if (mw === null) {
12072      mw = mwFactory(player);
12073      mws.push([mwFactory, mw]);
12074    }
12075
12076    return mw;
12077  }
12078
12079  function setSourceHelper(src, middleware, next, player, acc, lastRun) {
12080    if (src === void 0) {
12081      src = {};
12082    }
12083
12084    if (middleware === void 0) {
12085      middleware = [];
12086    }
12087
12088    if (acc === void 0) {
12089      acc = [];
12090    }
12091
12092    if (lastRun === void 0) {
12093      lastRun = false;
12094    }
12095
12096    var _middleware = middleware,
12097        mwFactory = _middleware[0],
12098        mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
12099
12100
12101    if (typeof mwFactory === 'string') {
12102      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
12103      // then call the mw's setSource method
12104    } else if (mwFactory) {
12105      var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
12106
12107      if (!mw.setSource) {
12108        acc.push(mw);
12109        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12110      }
12111
12112      mw.setSource(assign({}, src), function (err, _src) {
12113        // something happened, try the next middleware on the current level
12114        // make sure to use the old src
12115        if (err) {
12116          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
12117        } // we've succeeded, now we need to go deeper
12118
12119
12120        acc.push(mw); // if it's the same type, continue down the current chain
12121        // otherwise, we want to go down the new chain
12122
12123        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
12124      });
12125    } else if (mwrest.length) {
12126      setSourceHelper(src, mwrest, next, player, acc, lastRun);
12127    } else if (lastRun) {
12128      next(src, acc);
12129    } else {
12130      setSourceHelper(src, middlewares['*'], next, player, acc, true);
12131    }
12132  }
12133
12134  /**
12135   * Mimetypes
12136   *
12137   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
12138   * @typedef Mimetypes~Kind
12139   * @enum
12140   */
12141
12142  var MimetypesKind = {
12143    opus: 'video/ogg',
12144    ogv: 'video/ogg',
12145    mp4: 'video/mp4',
12146    mov: 'video/mp4',
12147    m4v: 'video/mp4',
12148    mkv: 'video/x-matroska',
12149    m4a: 'audio/mp4',
12150    mp3: 'audio/mpeg',
12151    aac: 'audio/aac',
12152    oga: 'audio/ogg',
12153    m3u8: 'application/x-mpegURL',
12154    jpg: 'image/jpeg',
12155    jpeg: 'image/jpeg',
12156    gif: 'image/gif',
12157    png: 'image/png',
12158    svg: 'image/svg+xml',
12159    webp: 'image/webp'
12160  };
12161  /**
12162   * Get the mimetype of a given src url if possible
12163   *
12164   * @param {string} src
12165   *        The url to the src
12166   *
12167   * @return {string}
12168   *         return the mimetype if it was known or empty string otherwise
12169   */
12170
12171  var getMimetype = function getMimetype(src) {
12172    if (src === void 0) {
12173      src = '';
12174    }
12175
12176    var ext = getFileExtension(src);
12177    var mimetype = MimetypesKind[ext.toLowerCase()];
12178    return mimetype || '';
12179  };
12180  /**
12181   * Find the mime type of a given source string if possible. Uses the player
12182   * source cache.
12183   *
12184   * @param {Player} player
12185   *        The player object
12186   *
12187   * @param {string} src
12188   *        The source string
12189   *
12190   * @return {string}
12191   *         The type that was found
12192   */
12193
12194  var findMimetype = function findMimetype(player, src) {
12195    if (!src) {
12196      return '';
12197    } // 1. check for the type in the `source` cache
12198
12199
12200    if (player.cache_.source.src === src && player.cache_.source.type) {
12201      return player.cache_.source.type;
12202    } // 2. see if we have this source in our `currentSources` cache
12203
12204
12205    var matchingSources = player.cache_.sources.filter(function (s) {
12206      return s.src === src;
12207    });
12208
12209    if (matchingSources.length) {
12210      return matchingSources[0].type;
12211    } // 3. look for the src url in source elements and use the type there
12212
12213
12214    var sources = player.$$('source');
12215
12216    for (var i = 0; i < sources.length; i++) {
12217      var s = sources[i];
12218
12219      if (s.type && s.src && s.src === src) {
12220        return s.type;
12221      }
12222    } // 4. finally fallback to our list of mime types based on src url extension
12223
12224
12225    return getMimetype(src);
12226  };
12227
12228  /**
12229   * @module filter-source
12230   */
12231  /**
12232   * Filter out single bad source objects or multiple source objects in an
12233   * array. Also flattens nested source object arrays into a 1 dimensional
12234   * array of source objects.
12235   *
12236   * @param {Tech~SourceObject|Tech~SourceObject[]} src
12237   *        The src object to filter
12238   *
12239   * @return {Tech~SourceObject[]}
12240   *         An array of sourceobjects containing only valid sources
12241   *
12242   * @private
12243   */
12244
12245  var filterSource = function filterSource(src) {
12246    // traverse array
12247    if (Array.isArray(src)) {
12248      var newsrc = [];
12249      src.forEach(function (srcobj) {
12250        srcobj = filterSource(srcobj);
12251
12252        if (Array.isArray(srcobj)) {
12253          newsrc = newsrc.concat(srcobj);
12254        } else if (isObject(srcobj)) {
12255          newsrc.push(srcobj);
12256        }
12257      });
12258      src = newsrc;
12259    } else if (typeof src === 'string' && src.trim()) {
12260      // convert string into object
12261      src = [fixSource({
12262        src: src
12263      })];
12264    } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
12265      // src is already valid
12266      src = [fixSource(src)];
12267    } else {
12268      // invalid source, turn it into an empty array
12269      src = [];
12270    }
12271
12272    return src;
12273  };
12274  /**
12275   * Checks src mimetype, adding it when possible
12276   *
12277   * @param {Tech~SourceObject} src
12278   *        The src object to check
12279   * @return {Tech~SourceObject}
12280   *        src Object with known type
12281   */
12282
12283
12284  function fixSource(src) {
12285    if (!src.type) {
12286      var mimetype = getMimetype(src.src);
12287
12288      if (mimetype) {
12289        src.type = mimetype;
12290      }
12291    }
12292
12293    return src;
12294  }
12295
12296  /**
12297   * The `MediaLoader` is the `Component` that decides which playback technology to load
12298   * when a player is initialized.
12299   *
12300   * @extends Component
12301   */
12302
12303  var MediaLoader =
12304  /*#__PURE__*/
12305  function (_Component) {
12306    inheritsLoose(MediaLoader, _Component);
12307
12308    /**
12309     * Create an instance of this class.
12310     *
12311     * @param {Player} player
12312     *        The `Player` that this class should attach to.
12313     *
12314     * @param {Object} [options]
12315     *        The key/value store of player options.
12316     *
12317     * @param {Component~ReadyCallback} [ready]
12318     *        The function that is run when this component is ready.
12319     */
12320    function MediaLoader(player, options, ready) {
12321      var _this;
12322
12323      // MediaLoader has no element
12324      var options_ = mergeOptions({
12325        createEl: false
12326      }, options);
12327      _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
12328      // load the first supported playback technology.
12329
12330      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
12331        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
12332          var techName = toTitleCase(j[i]);
12333          var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
12334          // Remove once that deprecated behavior is removed.
12335
12336          if (!techName) {
12337            tech = Component.getComponent(techName);
12338          } // Check if the browser supports this technology
12339
12340
12341          if (tech && tech.isSupported()) {
12342            player.loadTech_(techName);
12343            break;
12344          }
12345        }
12346      } else {
12347        // Loop through playback technologies (HTML5, Flash) and check for support.
12348        // Then load the best source.
12349        // A few assumptions here:
12350        //   All playback technologies respect preload false.
12351        player.src(options.playerOptions.sources);
12352      }
12353
12354      return _this;
12355    }
12356
12357    return MediaLoader;
12358  }(Component);
12359
12360  Component.registerComponent('MediaLoader', MediaLoader);
12361
12362  /**
12363   * Component which is clickable or keyboard actionable, but is not a
12364   * native HTML button.
12365   *
12366   * @extends Component
12367   */
12368
12369  var ClickableComponent =
12370  /*#__PURE__*/
12371  function (_Component) {
12372    inheritsLoose(ClickableComponent, _Component);
12373
12374    /**
12375     * Creates an instance of this class.
12376     *
12377     * @param  {Player} player
12378     *         The `Player` that this class should be attached to.
12379     *
12380     * @param  {Object} [options]
12381     *         The key/value store of player options.
12382     *
12383     * @param  {function} [options.clickHandler]
12384     *         The function to call when the button is clicked / activated
12385     */
12386    function ClickableComponent(player, options) {
12387      var _this;
12388
12389      _this = _Component.call(this, player, options) || this;
12390
12391      _this.emitTapEvents();
12392
12393      _this.enable();
12394
12395      return _this;
12396    }
12397    /**
12398     * Create the `ClickableComponent`s DOM element.
12399     *
12400     * @param {string} [tag=div]
12401     *        The element's node type.
12402     *
12403     * @param {Object} [props={}]
12404     *        An object of properties that should be set on the element.
12405     *
12406     * @param {Object} [attributes={}]
12407     *        An object of attributes that should be set on the element.
12408     *
12409     * @return {Element}
12410     *         The element that gets created.
12411     */
12412
12413
12414    var _proto = ClickableComponent.prototype;
12415
12416    _proto.createEl = function createEl(tag, props, attributes) {
12417      if (tag === void 0) {
12418        tag = 'div';
12419      }
12420
12421      if (props === void 0) {
12422        props = {};
12423      }
12424
12425      if (attributes === void 0) {
12426        attributes = {};
12427      }
12428
12429      props = assign({
12430        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12431        className: this.buildCSSClass(),
12432        tabIndex: 0
12433      }, props);
12434
12435      if (tag === 'button') {
12436        log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
12437      } // Add ARIA attributes for clickable element which is not a native HTML button
12438
12439
12440      attributes = assign({
12441        role: 'button'
12442      }, attributes);
12443      this.tabIndex_ = props.tabIndex;
12444
12445      var el = _Component.prototype.createEl.call(this, tag, props, attributes);
12446
12447      this.createControlTextEl(el);
12448      return el;
12449    };
12450
12451    _proto.dispose = function dispose() {
12452      // remove controlTextEl_ on dispose
12453      this.controlTextEl_ = null;
12454
12455      _Component.prototype.dispose.call(this);
12456    }
12457    /**
12458     * Create a control text element on this `ClickableComponent`
12459     *
12460     * @param {Element} [el]
12461     *        Parent element for the control text.
12462     *
12463     * @return {Element}
12464     *         The control text element that gets created.
12465     */
12466    ;
12467
12468    _proto.createControlTextEl = function createControlTextEl(el) {
12469      this.controlTextEl_ = createEl('span', {
12470        className: 'vjs-control-text'
12471      }, {
12472        // let the screen reader user know that the text of the element may change
12473        'aria-live': 'polite'
12474      });
12475
12476      if (el) {
12477        el.appendChild(this.controlTextEl_);
12478      }
12479
12480      this.controlText(this.controlText_, el);
12481      return this.controlTextEl_;
12482    }
12483    /**
12484     * Get or set the localize text to use for the controls on the `ClickableComponent`.
12485     *
12486     * @param {string} [text]
12487     *        Control text for element.
12488     *
12489     * @param {Element} [el=this.el()]
12490     *        Element to set the title on.
12491     *
12492     * @return {string}
12493     *         - The control text when getting
12494     */
12495    ;
12496
12497    _proto.controlText = function controlText(text, el) {
12498      if (el === void 0) {
12499        el = this.el();
12500      }
12501
12502      if (text === undefined) {
12503        return this.controlText_ || 'Need Text';
12504      }
12505
12506      var localizedText = this.localize(text);
12507      this.controlText_ = text;
12508      textContent(this.controlTextEl_, localizedText);
12509
12510      if (!this.nonIconControl) {
12511        // Set title attribute if only an icon is shown
12512        el.setAttribute('title', localizedText);
12513      }
12514    }
12515    /**
12516     * Builds the default DOM `className`.
12517     *
12518     * @return {string}
12519     *         The DOM `className` for this object.
12520     */
12521    ;
12522
12523    _proto.buildCSSClass = function buildCSSClass() {
12524      return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
12525    }
12526    /**
12527     * Enable this `ClickableComponent`
12528     */
12529    ;
12530
12531    _proto.enable = function enable() {
12532      if (!this.enabled_) {
12533        this.enabled_ = true;
12534        this.removeClass('vjs-disabled');
12535        this.el_.setAttribute('aria-disabled', 'false');
12536
12537        if (typeof this.tabIndex_ !== 'undefined') {
12538          this.el_.setAttribute('tabIndex', this.tabIndex_);
12539        }
12540
12541        this.on(['tap', 'click'], this.handleClick);
12542        this.on('keydown', this.handleKeyDown);
12543      }
12544    }
12545    /**
12546     * Disable this `ClickableComponent`
12547     */
12548    ;
12549
12550    _proto.disable = function disable() {
12551      this.enabled_ = false;
12552      this.addClass('vjs-disabled');
12553      this.el_.setAttribute('aria-disabled', 'true');
12554
12555      if (typeof this.tabIndex_ !== 'undefined') {
12556        this.el_.removeAttribute('tabIndex');
12557      }
12558
12559      this.off('mouseover', this.handleMouseOver);
12560      this.off('mouseout', this.handleMouseOut);
12561      this.off(['tap', 'click'], this.handleClick);
12562      this.off('keydown', this.handleKeyDown);
12563    }
12564    /**
12565     * Event handler that is called when a `ClickableComponent` receives a
12566     * `click` or `tap` event.
12567     *
12568     * @param {EventTarget~Event} event
12569     *        The `tap` or `click` event that caused this function to be called.
12570     *
12571     * @listens tap
12572     * @listens click
12573     * @abstract
12574     */
12575    ;
12576
12577    _proto.handleClick = function handleClick(event) {
12578      if (this.options_.clickHandler) {
12579        this.options_.clickHandler.call(this, arguments);
12580      }
12581    }
12582    /**
12583     * Event handler that is called when a `ClickableComponent` receives a
12584     * `keydown` event.
12585     *
12586     * By default, if the key is Space or Enter, it will trigger a `click` event.
12587     *
12588     * @param {EventTarget~Event} event
12589     *        The `keydown` event that caused this function to be called.
12590     *
12591     * @listens keydown
12592     */
12593    ;
12594
12595    _proto.handleKeyDown = function handleKeyDown(event) {
12596      // Support Space or Enter key operation to fire a click event. Also,
12597      // prevent the event from propagating through the DOM and triggering
12598      // Player hotkeys.
12599      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12600        event.preventDefault();
12601        event.stopPropagation();
12602        this.trigger('click');
12603      } else {
12604        // Pass keypress handling up for unsupported keys
12605        _Component.prototype.handleKeyDown.call(this, event);
12606      }
12607    };
12608
12609    return ClickableComponent;
12610  }(Component);
12611
12612  Component.registerComponent('ClickableComponent', ClickableComponent);
12613
12614  /**
12615   * A `ClickableComponent` that handles showing the poster image for the player.
12616   *
12617   * @extends ClickableComponent
12618   */
12619
12620  var PosterImage =
12621  /*#__PURE__*/
12622  function (_ClickableComponent) {
12623    inheritsLoose(PosterImage, _ClickableComponent);
12624
12625    /**
12626     * Create an instance of this class.
12627     *
12628     * @param {Player} player
12629     *        The `Player` that this class should attach to.
12630     *
12631     * @param {Object} [options]
12632     *        The key/value store of player options.
12633     */
12634    function PosterImage(player, options) {
12635      var _this;
12636
12637      _this = _ClickableComponent.call(this, player, options) || this;
12638
12639      _this.update();
12640
12641      player.on('posterchange', bind(assertThisInitialized(_this), _this.update));
12642      return _this;
12643    }
12644    /**
12645     * Clean up and dispose of the `PosterImage`.
12646     */
12647
12648
12649    var _proto = PosterImage.prototype;
12650
12651    _proto.dispose = function dispose() {
12652      this.player().off('posterchange', this.update);
12653
12654      _ClickableComponent.prototype.dispose.call(this);
12655    }
12656    /**
12657     * Create the `PosterImage`s DOM element.
12658     *
12659     * @return {Element}
12660     *         The element that gets created.
12661     */
12662    ;
12663
12664    _proto.createEl = function createEl$1() {
12665      var el = createEl('div', {
12666        className: 'vjs-poster',
12667        // Don't want poster to be tabbable.
12668        tabIndex: -1
12669      });
12670      return el;
12671    }
12672    /**
12673     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
12674     *
12675     * @listens Player#posterchange
12676     *
12677     * @param {EventTarget~Event} [event]
12678     *        The `Player#posterchange` event that triggered this function.
12679     */
12680    ;
12681
12682    _proto.update = function update(event) {
12683      var url = this.player().poster();
12684      this.setSrc(url); // If there's no poster source we should display:none on this component
12685      // so it's not still clickable or right-clickable
12686
12687      if (url) {
12688        this.show();
12689      } else {
12690        this.hide();
12691      }
12692    }
12693    /**
12694     * Set the source of the `PosterImage` depending on the display method.
12695     *
12696     * @param {string} url
12697     *        The URL to the source for the `PosterImage`.
12698     */
12699    ;
12700
12701    _proto.setSrc = function setSrc(url) {
12702      var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
12703      // this will throw an extra error
12704
12705      if (url) {
12706        backgroundImage = "url(\"" + url + "\")";
12707      }
12708
12709      this.el_.style.backgroundImage = backgroundImage;
12710    }
12711    /**
12712     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
12713     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
12714     *
12715     * @listens tap
12716     * @listens click
12717     * @listens keydown
12718     *
12719     * @param {EventTarget~Event} event
12720     +        The `click`, `tap` or `keydown` event that caused this function to be called.
12721     */
12722    ;
12723
12724    _proto.handleClick = function handleClick(event) {
12725      // We don't want a click to trigger playback when controls are disabled
12726      if (!this.player_.controls()) {
12727        return;
12728      }
12729
12730      if (this.player_.tech(true)) {
12731        this.player_.tech(true).focus();
12732      }
12733
12734      if (this.player_.paused()) {
12735        silencePromise(this.player_.play());
12736      } else {
12737        this.player_.pause();
12738      }
12739    };
12740
12741    return PosterImage;
12742  }(ClickableComponent);
12743
12744  Component.registerComponent('PosterImage', PosterImage);
12745
12746  var darkGray = '#222';
12747  var lightGray = '#ccc';
12748  var fontMap = {
12749    monospace: 'monospace',
12750    sansSerif: 'sans-serif',
12751    serif: 'serif',
12752    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
12753    monospaceSerif: '"Courier New", monospace',
12754    proportionalSansSerif: 'sans-serif',
12755    proportionalSerif: 'serif',
12756    casual: '"Comic Sans MS", Impact, fantasy',
12757    script: '"Monotype Corsiva", cursive',
12758    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
12759  };
12760  /**
12761   * Construct an rgba color from a given hex color code.
12762   *
12763   * @param {number} color
12764   *        Hex number for color, like #f0e or #f604e2.
12765   *
12766   * @param {number} opacity
12767   *        Value for opacity, 0.0 - 1.0.
12768   *
12769   * @return {string}
12770   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
12771   */
12772
12773  function constructColor(color, opacity) {
12774    var hex;
12775
12776    if (color.length === 4) {
12777      // color looks like "#f0e"
12778      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
12779    } else if (color.length === 7) {
12780      // color looks like "#f604e2"
12781      hex = color.slice(1);
12782    } else {
12783      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
12784    }
12785
12786    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
12787  }
12788  /**
12789   * Try to update the style of a DOM element. Some style changes will throw an error,
12790   * particularly in IE8. Those should be noops.
12791   *
12792   * @param {Element} el
12793   *        The DOM element to be styled.
12794   *
12795   * @param {string} style
12796   *        The CSS property on the element that should be styled.
12797   *
12798   * @param {string} rule
12799   *        The style rule that should be applied to the property.
12800   *
12801   * @private
12802   */
12803
12804  function tryUpdateStyle(el, style, rule) {
12805    try {
12806      el.style[style] = rule;
12807    } catch (e) {
12808      // Satisfies linter.
12809      return;
12810    }
12811  }
12812  /**
12813   * The component for displaying text track cues.
12814   *
12815   * @extends Component
12816   */
12817
12818
12819  var TextTrackDisplay =
12820  /*#__PURE__*/
12821  function (_Component) {
12822    inheritsLoose(TextTrackDisplay, _Component);
12823
12824    /**
12825     * Creates an instance of this class.
12826     *
12827     * @param {Player} player
12828     *        The `Player` that this class should be attached to.
12829     *
12830     * @param {Object} [options]
12831     *        The key/value store of player options.
12832     *
12833     * @param {Component~ReadyCallback} [ready]
12834     *        The function to call when `TextTrackDisplay` is ready.
12835     */
12836    function TextTrackDisplay(player, options, ready) {
12837      var _this;
12838
12839      _this = _Component.call(this, player, options, ready) || this;
12840      var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay);
12841      player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay));
12842      player.on('texttrackchange', updateDisplayHandler);
12843      player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error
12844      // if a track should show by default and the display hadn't loaded yet.
12845      // Should probably be moved to an external track loader when we support
12846      // tracks that don't need a display.
12847
12848      player.ready(bind(assertThisInitialized(_this), function () {
12849        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12850          this.hide();
12851          return;
12852        }
12853
12854        player.on('fullscreenchange', updateDisplayHandler);
12855        player.on('playerresize', updateDisplayHandler);
12856        window$1.addEventListener('orientationchange', updateDisplayHandler);
12857        player.on('dispose', function () {
12858          return window$1.removeEventListener('orientationchange', updateDisplayHandler);
12859        });
12860        var tracks = this.options_.playerOptions.tracks || [];
12861
12862        for (var i = 0; i < tracks.length; i++) {
12863          this.player_.addRemoteTextTrack(tracks[i], true);
12864        }
12865
12866        this.preselectTrack();
12867      }));
12868      return _this;
12869    }
12870    /**
12871    * Preselect a track following this precedence:
12872    * - matches the previously selected {@link TextTrack}'s language and kind
12873    * - matches the previously selected {@link TextTrack}'s language only
12874    * - is the first default captions track
12875    * - is the first default descriptions track
12876    *
12877    * @listens Player#loadstart
12878    */
12879
12880
12881    var _proto = TextTrackDisplay.prototype;
12882
12883    _proto.preselectTrack = function preselectTrack() {
12884      var modes = {
12885        captions: 1,
12886        subtitles: 1
12887      };
12888      var trackList = this.player_.textTracks();
12889      var userPref = this.player_.cache_.selectedLanguage;
12890      var firstDesc;
12891      var firstCaptions;
12892      var preferredTrack;
12893
12894      for (var i = 0; i < trackList.length; i++) {
12895        var track = trackList[i];
12896
12897        if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
12898          // Always choose the track that matches both language and kind
12899          if (track.kind === userPref.kind) {
12900            preferredTrack = track; // or choose the first track that matches language
12901          } else if (!preferredTrack) {
12902            preferredTrack = track;
12903          } // clear everything if offTextTrackMenuItem was clicked
12904
12905        } else if (userPref && !userPref.enabled) {
12906          preferredTrack = null;
12907          firstDesc = null;
12908          firstCaptions = null;
12909        } else if (track["default"]) {
12910          if (track.kind === 'descriptions' && !firstDesc) {
12911            firstDesc = track;
12912          } else if (track.kind in modes && !firstCaptions) {
12913            firstCaptions = track;
12914          }
12915        }
12916      } // The preferredTrack matches the user preference and takes
12917      // precedence over all the other tracks.
12918      // So, display the preferredTrack before the first default track
12919      // and the subtitles/captions track before the descriptions track
12920
12921
12922      if (preferredTrack) {
12923        preferredTrack.mode = 'showing';
12924      } else if (firstCaptions) {
12925        firstCaptions.mode = 'showing';
12926      } else if (firstDesc) {
12927        firstDesc.mode = 'showing';
12928      }
12929    }
12930    /**
12931     * Turn display of {@link TextTrack}'s from the current state into the other state.
12932     * There are only two states:
12933     * - 'shown'
12934     * - 'hidden'
12935     *
12936     * @listens Player#loadstart
12937     */
12938    ;
12939
12940    _proto.toggleDisplay = function toggleDisplay() {
12941      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12942        this.hide();
12943      } else {
12944        this.show();
12945      }
12946    }
12947    /**
12948     * Create the {@link Component}'s DOM element.
12949     *
12950     * @return {Element}
12951     *         The element that was created.
12952     */
12953    ;
12954
12955    _proto.createEl = function createEl() {
12956      return _Component.prototype.createEl.call(this, 'div', {
12957        className: 'vjs-text-track-display'
12958      }, {
12959        'aria-live': 'off',
12960        'aria-atomic': 'true'
12961      });
12962    }
12963    /**
12964     * Clear all displayed {@link TextTrack}s.
12965     */
12966    ;
12967
12968    _proto.clearDisplay = function clearDisplay() {
12969      if (typeof window$1.WebVTT === 'function') {
12970        window$1.WebVTT.processCues(window$1, [], this.el_);
12971      }
12972    }
12973    /**
12974     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12975     * a {@link Player#fullscreenchange} is fired.
12976     *
12977     * @listens Player#texttrackchange
12978     * @listens Player#fullscreenchange
12979     */
12980    ;
12981
12982    _proto.updateDisplay = function updateDisplay() {
12983      var tracks = this.player_.textTracks();
12984      var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
12985      this.clearDisplay();
12986
12987      if (allowMultipleShowingTracks) {
12988        var showingTracks = [];
12989
12990        for (var _i = 0; _i < tracks.length; ++_i) {
12991          var track = tracks[_i];
12992
12993          if (track.mode !== 'showing') {
12994            continue;
12995          }
12996
12997          showingTracks.push(track);
12998        }
12999
13000        this.updateForTrack(showingTracks);
13001        return;
13002      } //  Track display prioritization model: if multiple tracks are 'showing',
13003      //  display the first 'subtitles' or 'captions' track which is 'showing',
13004      //  otherwise display the first 'descriptions' track which is 'showing'
13005
13006
13007      var descriptionsTrack = null;
13008      var captionsSubtitlesTrack = null;
13009      var i = tracks.length;
13010
13011      while (i--) {
13012        var _track = tracks[i];
13013
13014        if (_track.mode === 'showing') {
13015          if (_track.kind === 'descriptions') {
13016            descriptionsTrack = _track;
13017          } else {
13018            captionsSubtitlesTrack = _track;
13019          }
13020        }
13021      }
13022
13023      if (captionsSubtitlesTrack) {
13024        if (this.getAttribute('aria-live') !== 'off') {
13025          this.setAttribute('aria-live', 'off');
13026        }
13027
13028        this.updateForTrack(captionsSubtitlesTrack);
13029      } else if (descriptionsTrack) {
13030        if (this.getAttribute('aria-live') !== 'assertive') {
13031          this.setAttribute('aria-live', 'assertive');
13032        }
13033
13034        this.updateForTrack(descriptionsTrack);
13035      }
13036    }
13037    /**
13038     * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
13039     *
13040     * @param {TextTrack} track
13041     *        Text track object containing active cues to style.
13042     */
13043    ;
13044
13045    _proto.updateDisplayState = function updateDisplayState(track) {
13046      var overrides = this.player_.textTrackSettings.getValues();
13047      var cues = track.activeCues;
13048      var i = cues.length;
13049
13050      while (i--) {
13051        var cue = cues[i];
13052
13053        if (!cue) {
13054          continue;
13055        }
13056
13057        var cueDiv = cue.displayState;
13058
13059        if (overrides.color) {
13060          cueDiv.firstChild.style.color = overrides.color;
13061        }
13062
13063        if (overrides.textOpacity) {
13064          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
13065        }
13066
13067        if (overrides.backgroundColor) {
13068          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
13069        }
13070
13071        if (overrides.backgroundOpacity) {
13072          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
13073        }
13074
13075        if (overrides.windowColor) {
13076          if (overrides.windowOpacity) {
13077            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
13078          } else {
13079            cueDiv.style.backgroundColor = overrides.windowColor;
13080          }
13081        }
13082
13083        if (overrides.edgeStyle) {
13084          if (overrides.edgeStyle === 'dropshadow') {
13085            cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
13086          } else if (overrides.edgeStyle === 'raised') {
13087            cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
13088          } else if (overrides.edgeStyle === 'depressed') {
13089            cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
13090          } else if (overrides.edgeStyle === 'uniform') {
13091            cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
13092          }
13093        }
13094
13095        if (overrides.fontPercent && overrides.fontPercent !== 1) {
13096          var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
13097          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
13098          cueDiv.style.height = 'auto';
13099          cueDiv.style.top = 'auto';
13100          cueDiv.style.bottom = '2px';
13101        }
13102
13103        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
13104          if (overrides.fontFamily === 'small-caps') {
13105            cueDiv.firstChild.style.fontVariant = 'small-caps';
13106          } else {
13107            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
13108          }
13109        }
13110      }
13111    }
13112    /**
13113     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
13114     *
13115     * @param {TextTrack|TextTrack[]} tracks
13116     *        Text track object or text track array to be added to the list.
13117     */
13118    ;
13119
13120    _proto.updateForTrack = function updateForTrack(tracks) {
13121      if (!Array.isArray(tracks)) {
13122        tracks = [tracks];
13123      }
13124
13125      if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) {
13126        return !track.activeCues;
13127      })) {
13128        return;
13129      }
13130
13131      var cues = []; // push all active track cues
13132
13133      for (var i = 0; i < tracks.length; ++i) {
13134        var track = tracks[i];
13135
13136        for (var j = 0; j < track.activeCues.length; ++j) {
13137          cues.push(track.activeCues[j]);
13138        }
13139      } // removes all cues before it processes new ones
13140
13141
13142      window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
13143
13144      for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
13145        var _track2 = tracks[_i2];
13146
13147        for (var _j = 0; _j < _track2.activeCues.length; ++_j) {
13148          var cueEl = _track2.activeCues[_j].displayState;
13149          addClass(cueEl, 'vjs-text-track-cue');
13150          addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2));
13151        }
13152
13153        if (this.player_.textTrackSettings) {
13154          this.updateDisplayState(_track2);
13155        }
13156      }
13157    };
13158
13159    return TextTrackDisplay;
13160  }(Component);
13161
13162  Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
13163
13164  /**
13165   * A loading spinner for use during waiting/loading events.
13166   *
13167   * @extends Component
13168   */
13169
13170  var LoadingSpinner =
13171  /*#__PURE__*/
13172  function (_Component) {
13173    inheritsLoose(LoadingSpinner, _Component);
13174
13175    function LoadingSpinner() {
13176      return _Component.apply(this, arguments) || this;
13177    }
13178
13179    var _proto = LoadingSpinner.prototype;
13180
13181    /**
13182     * Create the `LoadingSpinner`s DOM element.
13183     *
13184     * @return {Element}
13185     *         The dom element that gets created.
13186     */
13187    _proto.createEl = function createEl$1() {
13188      var isAudio = this.player_.isAudio();
13189      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
13190      var controlText = createEl('span', {
13191        className: 'vjs-control-text',
13192        innerHTML: this.localize('{1} is loading.', [playerType])
13193      });
13194
13195      var el = _Component.prototype.createEl.call(this, 'div', {
13196        className: 'vjs-loading-spinner',
13197        dir: 'ltr'
13198      });
13199
13200      el.appendChild(controlText);
13201      return el;
13202    };
13203
13204    return LoadingSpinner;
13205  }(Component);
13206
13207  Component.registerComponent('LoadingSpinner', LoadingSpinner);
13208
13209  /**
13210   * Base class for all buttons.
13211   *
13212   * @extends ClickableComponent
13213   */
13214
13215  var Button =
13216  /*#__PURE__*/
13217  function (_ClickableComponent) {
13218    inheritsLoose(Button, _ClickableComponent);
13219
13220    function Button() {
13221      return _ClickableComponent.apply(this, arguments) || this;
13222    }
13223
13224    var _proto = Button.prototype;
13225
13226    /**
13227     * Create the `Button`s DOM element.
13228     *
13229     * @param {string} [tag="button"]
13230     *        The element's node type. This argument is IGNORED: no matter what
13231     *        is passed, it will always create a `button` element.
13232     *
13233     * @param {Object} [props={}]
13234     *        An object of properties that should be set on the element.
13235     *
13236     * @param {Object} [attributes={}]
13237     *        An object of attributes that should be set on the element.
13238     *
13239     * @return {Element}
13240     *         The element that gets created.
13241     */
13242    _proto.createEl = function createEl(tag, props, attributes) {
13243      if (props === void 0) {
13244        props = {};
13245      }
13246
13247      if (attributes === void 0) {
13248        attributes = {};
13249      }
13250
13251      tag = 'button';
13252      props = assign({
13253        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
13254        className: this.buildCSSClass()
13255      }, props); // Add attributes for button element
13256
13257      attributes = assign({
13258        // Necessary since the default button type is "submit"
13259        type: 'button'
13260      }, attributes);
13261      var el = Component.prototype.createEl.call(this, tag, props, attributes);
13262      this.createControlTextEl(el);
13263      return el;
13264    }
13265    /**
13266     * Add a child `Component` inside of this `Button`.
13267     *
13268     * @param {string|Component} child
13269     *        The name or instance of a child to add.
13270     *
13271     * @param {Object} [options={}]
13272     *        The key/value store of options that will get passed to children of
13273     *        the child.
13274     *
13275     * @return {Component}
13276     *         The `Component` that gets added as a child. When using a string the
13277     *         `Component` will get created by this process.
13278     *
13279     * @deprecated since version 5
13280     */
13281    ;
13282
13283    _proto.addChild = function addChild(child, options) {
13284      if (options === void 0) {
13285        options = {};
13286      }
13287
13288      var className = this.constructor.name;
13289      log.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method
13290
13291      return Component.prototype.addChild.call(this, child, options);
13292    }
13293    /**
13294     * Enable the `Button` element so that it can be activated or clicked. Use this with
13295     * {@link Button#disable}.
13296     */
13297    ;
13298
13299    _proto.enable = function enable() {
13300      _ClickableComponent.prototype.enable.call(this);
13301
13302      this.el_.removeAttribute('disabled');
13303    }
13304    /**
13305     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
13306     * {@link Button#enable}.
13307     */
13308    ;
13309
13310    _proto.disable = function disable() {
13311      _ClickableComponent.prototype.disable.call(this);
13312
13313      this.el_.setAttribute('disabled', 'disabled');
13314    }
13315    /**
13316     * This gets called when a `Button` has focus and `keydown` is triggered via a key
13317     * press.
13318     *
13319     * @param {EventTarget~Event} event
13320     *        The event that caused this function to get called.
13321     *
13322     * @listens keydown
13323     */
13324    ;
13325
13326    _proto.handleKeyDown = function handleKeyDown(event) {
13327      // Ignore Space or Enter key operation, which is handled by the browser for
13328      // a button - though not for its super class, ClickableComponent. Also,
13329      // prevent the event from propagating through the DOM and triggering Player
13330      // hotkeys. We do not preventDefault here because we _want_ the browser to
13331      // handle it.
13332      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
13333        event.stopPropagation();
13334        return;
13335      } // Pass keypress handling up for unsupported keys
13336
13337
13338      _ClickableComponent.prototype.handleKeyDown.call(this, event);
13339    };
13340
13341    return Button;
13342  }(ClickableComponent);
13343
13344  Component.registerComponent('Button', Button);
13345
13346  /**
13347   * The initial play button that shows before the video has played. The hiding of the
13348   * `BigPlayButton` get done via CSS and `Player` states.
13349   *
13350   * @extends Button
13351   */
13352
13353  var BigPlayButton =
13354  /*#__PURE__*/
13355  function (_Button) {
13356    inheritsLoose(BigPlayButton, _Button);
13357
13358    function BigPlayButton(player, options) {
13359      var _this;
13360
13361      _this = _Button.call(this, player, options) || this;
13362      _this.mouseused_ = false;
13363
13364      _this.on('mousedown', _this.handleMouseDown);
13365
13366      return _this;
13367    }
13368    /**
13369     * Builds the default DOM `className`.
13370     *
13371     * @return {string}
13372     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
13373     */
13374
13375
13376    var _proto = BigPlayButton.prototype;
13377
13378    _proto.buildCSSClass = function buildCSSClass() {
13379      return 'vjs-big-play-button';
13380    }
13381    /**
13382     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
13383     * for more detailed information on what a click can be.
13384     *
13385     * @param {EventTarget~Event} event
13386     *        The `keydown`, `tap`, or `click` event that caused this function to be
13387     *        called.
13388     *
13389     * @listens tap
13390     * @listens click
13391     */
13392    ;
13393
13394    _proto.handleClick = function handleClick(event) {
13395      var playPromise = this.player_.play(); // exit early if clicked via the mouse
13396
13397      if (this.mouseused_ && event.clientX && event.clientY) {
13398        var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
13399        silencePromise(playPromise);
13400
13401        if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
13402        // calling .focus() on the video element causes the video to go black,
13403        // so we avoid it in that specific case
13404        !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
13405          this.player_.tech(true).focus();
13406        }
13407
13408        return;
13409      }
13410
13411      var cb = this.player_.getChild('controlBar');
13412      var playToggle = cb && cb.getChild('playToggle');
13413
13414      if (!playToggle) {
13415        this.player_.tech(true).focus();
13416        return;
13417      }
13418
13419      var playFocus = function playFocus() {
13420        return playToggle.focus();
13421      };
13422
13423      if (isPromise(playPromise)) {
13424        playPromise.then(playFocus, function () {});
13425      } else {
13426        this.setTimeout(playFocus, 1);
13427      }
13428    };
13429
13430    _proto.handleKeyDown = function handleKeyDown(event) {
13431      this.mouseused_ = false;
13432
13433      _Button.prototype.handleKeyDown.call(this, event);
13434    };
13435
13436    _proto.handleMouseDown = function handleMouseDown(event) {
13437      this.mouseused_ = true;
13438    };
13439
13440    return BigPlayButton;
13441  }(Button);
13442  /**
13443   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
13444   *
13445   * @type {string}
13446   * @private
13447   */
13448
13449
13450  BigPlayButton.prototype.controlText_ = 'Play Video';
13451  Component.registerComponent('BigPlayButton', BigPlayButton);
13452
13453  /**
13454   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
13455   * it gets clicked.
13456   *
13457   * @extends Button
13458   */
13459
13460  var CloseButton =
13461  /*#__PURE__*/
13462  function (_Button) {
13463    inheritsLoose(CloseButton, _Button);
13464
13465    /**
13466    * Creates an instance of the this class.
13467    *
13468    * @param  {Player} player
13469    *         The `Player` that this class should be attached to.
13470    *
13471    * @param  {Object} [options]
13472    *         The key/value store of player options.
13473    */
13474    function CloseButton(player, options) {
13475      var _this;
13476
13477      _this = _Button.call(this, player, options) || this;
13478
13479      _this.controlText(options && options.controlText || _this.localize('Close'));
13480
13481      return _this;
13482    }
13483    /**
13484    * Builds the default DOM `className`.
13485    *
13486    * @return {string}
13487    *         The DOM `className` for this object.
13488    */
13489
13490
13491    var _proto = CloseButton.prototype;
13492
13493    _proto.buildCSSClass = function buildCSSClass() {
13494      return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
13495    }
13496    /**
13497     * This gets called when a `CloseButton` gets clicked. See
13498     * {@link ClickableComponent#handleClick} for more information on when
13499     * this will be triggered
13500     *
13501     * @param {EventTarget~Event} event
13502     *        The `keydown`, `tap`, or `click` event that caused this function to be
13503     *        called.
13504     *
13505     * @listens tap
13506     * @listens click
13507     * @fires CloseButton#close
13508     */
13509    ;
13510
13511    _proto.handleClick = function handleClick(event) {
13512      /**
13513       * Triggered when the a `CloseButton` is clicked.
13514       *
13515       * @event CloseButton#close
13516       * @type {EventTarget~Event}
13517       *
13518       * @property {boolean} [bubbles=false]
13519       *           set to false so that the close event does not
13520       *           bubble up to parents if there is no listener
13521       */
13522      this.trigger({
13523        type: 'close',
13524        bubbles: false
13525      });
13526    }
13527    /**
13528     * Event handler that is called when a `CloseButton` receives a
13529     * `keydown` event.
13530     *
13531     * By default, if the key is Esc, it will trigger a `click` event.
13532     *
13533     * @param {EventTarget~Event} event
13534     *        The `keydown` event that caused this function to be called.
13535     *
13536     * @listens keydown
13537     */
13538    ;
13539
13540    _proto.handleKeyDown = function handleKeyDown(event) {
13541      // Esc button will trigger `click` event
13542      if (keycode.isEventKey(event, 'Esc')) {
13543        event.preventDefault();
13544        event.stopPropagation();
13545        this.trigger('click');
13546      } else {
13547        // Pass keypress handling up for unsupported keys
13548        _Button.prototype.handleKeyDown.call(this, event);
13549      }
13550    };
13551
13552    return CloseButton;
13553  }(Button);
13554
13555  Component.registerComponent('CloseButton', CloseButton);
13556
13557  /**
13558   * Button to toggle between play and pause.
13559   *
13560   * @extends Button
13561   */
13562
13563  var PlayToggle =
13564  /*#__PURE__*/
13565  function (_Button) {
13566    inheritsLoose(PlayToggle, _Button);
13567
13568    /**
13569     * Creates an instance of this class.
13570     *
13571     * @param {Player} player
13572     *        The `Player` that this class should be attached to.
13573     *
13574     * @param {Object} [options={}]
13575     *        The key/value store of player options.
13576     */
13577    function PlayToggle(player, options) {
13578      var _this;
13579
13580      if (options === void 0) {
13581        options = {};
13582      }
13583
13584      _this = _Button.call(this, player, options) || this; // show or hide replay icon
13585
13586      options.replay = options.replay === undefined || options.replay;
13587
13588      _this.on(player, 'play', _this.handlePlay);
13589
13590      _this.on(player, 'pause', _this.handlePause);
13591
13592      if (options.replay) {
13593        _this.on(player, 'ended', _this.handleEnded);
13594      }
13595
13596      return _this;
13597    }
13598    /**
13599     * Builds the default DOM `className`.
13600     *
13601     * @return {string}
13602     *         The DOM `className` for this object.
13603     */
13604
13605
13606    var _proto = PlayToggle.prototype;
13607
13608    _proto.buildCSSClass = function buildCSSClass() {
13609      return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
13610    }
13611    /**
13612     * This gets called when an `PlayToggle` is "clicked". See
13613     * {@link ClickableComponent} for more detailed information on what a click can be.
13614     *
13615     * @param {EventTarget~Event} [event]
13616     *        The `keydown`, `tap`, or `click` event that caused this function to be
13617     *        called.
13618     *
13619     * @listens tap
13620     * @listens click
13621     */
13622    ;
13623
13624    _proto.handleClick = function handleClick(event) {
13625      if (this.player_.paused()) {
13626        this.player_.play();
13627      } else {
13628        this.player_.pause();
13629      }
13630    }
13631    /**
13632     * This gets called once after the video has ended and the user seeks so that
13633     * we can change the replay button back to a play button.
13634     *
13635     * @param {EventTarget~Event} [event]
13636     *        The event that caused this function to run.
13637     *
13638     * @listens Player#seeked
13639     */
13640    ;
13641
13642    _proto.handleSeeked = function handleSeeked(event) {
13643      this.removeClass('vjs-ended');
13644
13645      if (this.player_.paused()) {
13646        this.handlePause(event);
13647      } else {
13648        this.handlePlay(event);
13649      }
13650    }
13651    /**
13652     * Add the vjs-playing class to the element so it can change appearance.
13653     *
13654     * @param {EventTarget~Event} [event]
13655     *        The event that caused this function to run.
13656     *
13657     * @listens Player#play
13658     */
13659    ;
13660
13661    _proto.handlePlay = function handlePlay(event) {
13662      this.removeClass('vjs-ended');
13663      this.removeClass('vjs-paused');
13664      this.addClass('vjs-playing'); // change the button text to "Pause"
13665
13666      this.controlText('Pause');
13667    }
13668    /**
13669     * Add the vjs-paused class to the element so it can change appearance.
13670     *
13671     * @param {EventTarget~Event} [event]
13672     *        The event that caused this function to run.
13673     *
13674     * @listens Player#pause
13675     */
13676    ;
13677
13678    _proto.handlePause = function handlePause(event) {
13679      this.removeClass('vjs-playing');
13680      this.addClass('vjs-paused'); // change the button text to "Play"
13681
13682      this.controlText('Play');
13683    }
13684    /**
13685     * Add the vjs-ended class to the element so it can change appearance
13686     *
13687     * @param {EventTarget~Event} [event]
13688     *        The event that caused this function to run.
13689     *
13690     * @listens Player#ended
13691     */
13692    ;
13693
13694    _proto.handleEnded = function handleEnded(event) {
13695      this.removeClass('vjs-playing');
13696      this.addClass('vjs-ended'); // change the button text to "Replay"
13697
13698      this.controlText('Replay'); // on the next seek remove the replay button
13699
13700      this.one(this.player_, 'seeked', this.handleSeeked);
13701    };
13702
13703    return PlayToggle;
13704  }(Button);
13705  /**
13706   * The text that should display over the `PlayToggle`s controls. Added for localization.
13707   *
13708   * @type {string}
13709   * @private
13710   */
13711
13712
13713  PlayToggle.prototype.controlText_ = 'Play';
13714  Component.registerComponent('PlayToggle', PlayToggle);
13715
13716  /**
13717   * @file format-time.js
13718   * @module format-time
13719   */
13720
13721  /**
13722   * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
13723   * seconds) will force a number of leading zeros to cover the length of the
13724   * guide.
13725   *
13726   * @private
13727   * @param  {number} seconds
13728   *         Number of seconds to be turned into a string
13729   *
13730   * @param  {number} guide
13731   *         Number (in seconds) to model the string after
13732   *
13733   * @return {string}
13734   *         Time formatted as H:MM:SS or M:SS
13735   */
13736  var defaultImplementation = function defaultImplementation(seconds, guide) {
13737    seconds = seconds < 0 ? 0 : seconds;
13738    var s = Math.floor(seconds % 60);
13739    var m = Math.floor(seconds / 60 % 60);
13740    var h = Math.floor(seconds / 3600);
13741    var gm = Math.floor(guide / 60 % 60);
13742    var gh = Math.floor(guide / 3600); // handle invalid times
13743
13744    if (isNaN(seconds) || seconds === Infinity) {
13745      // '-' is false for all relational operators (e.g. <, >
13745=) so this setting
13746      // will add the minimum number of fields specified by the guide
13747      h = m = s = '-';
13748    } // Check if we need to show hours
13749
13750
13751    h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
13752    // Always show at least one digit of minutes.
13753
13754    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
13755
13756    s = s < 10 ? '0' + s : s;
13757    return h + m + s;
13758  }; // Internal pointer to the current implementation.
13759
13760
13761  var implementation = defaultImplementation;
13762  /**
13763   * Replaces the default formatTime implementation with a custom implementation.
13764   *
13765   * @param {Function} customImplementation
13766   *        A function which will be used in place of the default formatTime
13767   *        implementation. Will receive the current time in seconds and the
13768   *        guide (in seconds) as arguments.
13769   */
13770
13771  function setFormatTime(customImplementation) {
13772    implementation = customImplementation;
13773  }
13774  /**
13775   * Resets formatTime to the default implementation.
13776   */
13777
13778  function resetFormatTime() {
13779    implementation = defaultImplementation;
13780  }
13781  /**
13782   * Delegates to either the default time formatting function or a custom
13783   * function supplied via `setFormatTime`.
13784   *
13785   * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
13786   * guide (in seconds) will force a number of leading zeros to cover the
13787   * length of the guide.
13788   *
13789   * @static
13790   * @example  formatTime(125, 600) === "02:05"
13791   * @param    {number} seconds
13792   *           Number of seconds to be turned into a string
13793   *
13794   * @param    {number} guide
13795   *           Number (in seconds) to model the string after
13796   *
13797   * @return   {string}
13798   *           Time formatted as H:MM:SS or M:SS
13799   */
13800
13801  function formatTime(seconds, guide) {
13802    if (guide === void 0) {
13803      guide = seconds;
13804    }
13805
13806    return implementation(seconds, guide);
13807  }
13808
13809  /**
13810   * Displays time information about the video
13811   *
13812   * @extends Component
13813   */
13814
13815  var TimeDisplay =
13816  /*#__PURE__*/
13817  function (_Component) {
13818    inheritsLoose(TimeDisplay, _Component);
13819
13820    /**
13821     * Creates an instance of this class.
13822     *
13823     * @param {Player} player
13824     *        The `Player` that this class should be attached to.
13825     *
13826     * @param {Object} [options]
13827     *        The key/value store of player options.
13828     */
13829    function TimeDisplay(player, options) {
13830      var _this;
13831
13832      _this = _Component.call(this, player, options) || this;
13833
13834      _this.on(player, ['timeupdate', 'ended'], _this.updateContent);
13835
13836      _this.updateTextNode_();
13837
13838      return _this;
13839    }
13840    /**
13841     * Create the `Component`'s DOM element
13842     *
13843     * @return {Element}
13844     *         The element that was created.
13845     */
13846
13847
13848    var _proto = TimeDisplay.prototype;
13849
13850    _proto.createEl = function createEl$1() {
13851      var className = this.buildCSSClass();
13852
13853      var el = _Component.prototype.createEl.call(this, 'div', {
13854        className: className + " vjs-time-control vjs-control",
13855        innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
13856      });
13857
13858      this.contentEl_ = createEl('span', {
13859        className: className + "-display"
13860      }, {
13861        // tell screen readers not to automatically read the time as it changes
13862        'aria-live': 'off',
13863        // span elements have no implicit role, but some screen readers (notably VoiceOver)
13864        // treat them as a break between items in the DOM when using arrow keys
13865        // (or left-to-right swipes on iOS) to read contents of a page. Using
13866        // role='presentation' causes VoiceOver to NOT treat this span as a break.
13867        'role': 'presentation'
13868      });
13869      el.appendChild(this.contentEl_);
13870      return el;
13871    };
13872
13873    _proto.dispose = function dispose() {
13874      this.contentEl_ = null;
13875      this.textNode_ = null;
13876
13877      _Component.prototype.dispose.call(this);
13878    }
13879    /**
13880     * Updates the time display text node with a new time
13881     *
13882     * @param {number} [time=0] the time to update to
13883     *
13884     * @private
13885     */
13886    ;
13887
13888    _proto.updateTextNode_ = function updateTextNode_(time) {
13889      var _this2 = this;
13890
13891      if (time === void 0) {
13892        time = 0;
13893      }
13894
13895      time = formatTime(time);
13896
13897      if (this.formattedTime_ === time) {
13898        return;
13899      }
13900
13901      this.formattedTime_ = time;
13902      this.requestAnimationFrame(function () {
13903        if (!_this2.contentEl_) {
13904          return;
13905        }
13906
13907        var oldNode = _this2.textNode_;
13908        _this2.textNode_ = document.createTextNode(_this2.formattedTime_);
13909
13910        if (!_this2.textNode_) {
13911          return;
13912        }
13913
13914        if (oldNode) {
13915          _this2.contentEl_.replaceChild(_this2.textNode_, oldNode);
13916        } else {
13917          _this2.contentEl_.appendChild(_this2.textNode_);
13918        }
13919      });
13920    }
13921    /**
13922     * To be filled out in the child class, should update the displayed time
13923     * in accordance with the fact that the current time has changed.
13924     *
13925     * @param {EventTarget~Event} [event]
13926     *        The `timeupdate`  event that caused this to run.
13927     *
13928     * @listens Player#timeupdate
13929     */
13930    ;
13931
13932    _proto.updateContent = function updateContent(event) {};
13933
13934    return TimeDisplay;
13935  }(Component);
13936  /**
13937   * The text that is added to the `TimeDisplay` for screen reader users.
13938   *
13939   * @type {string}
13940   * @private
13941   */
13942
13943
13944  TimeDisplay.prototype.labelText_ = 'Time';
13945  /**
13946   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13947   *
13948   * @type {string}
13949   * @private
13950   *
13951   * @deprecated in v7; controlText_ is not used in non-active display Components
13952   */
13953
13954  TimeDisplay.prototype.controlText_ = 'Time';
13955  Component.registerComponent('TimeDisplay', TimeDisplay);
13956
13957  /**
13958   * Displays the current time
13959   *
13960   * @extends Component
13961   */
13962
13963  var CurrentTimeDisplay =
13964  /*#__PURE__*/
13965  function (_TimeDisplay) {
13966    inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
13967
13968    function CurrentTimeDisplay() {
13969      return _TimeDisplay.apply(this, arguments) || this;
13970    }
13971
13972    var _proto = CurrentTimeDisplay.prototype;
13973
13974    /**
13975     * Builds the default DOM `className`.
13976     *
13977     * @return {string}
13978     *         The DOM `className` for this object.
13979     */
13980    _proto.buildCSSClass = function buildCSSClass() {
13981      return 'vjs-current-time';
13982    }
13983    /**
13984     * Update current time display
13985     *
13986     * @param {EventTarget~Event} [event]
13987     *        The `timeupdate` event that caused this function to run.
13988     *
13989     * @listens Player#timeupdate
13990     */
13991    ;
13992
13993    _proto.updateContent = function updateContent(event) {
13994      // Allows for smooth scrubbing, when player can't keep up.
13995      var time;
13996
13997      if (this.player_.ended()) {
13998        time = this.player_.duration();
13999      } else {
14000        time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14001      }
14002
14003      this.updateTextNode_(time);
14004    };
14005
14006    return CurrentTimeDisplay;
14007  }(TimeDisplay);
14008  /**
14009   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
14010   *
14011   * @type {string}
14012   * @private
14013   */
14014
14015
14016  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
14017  /**
14018   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
14019   *
14020   * @type {string}
14021   * @private
14022   *
14023   * @deprecated in v7; controlText_ is not used in non-active display Components
14024   */
14025
14026  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
14027  Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
14028
14029  /**
14030   * Displays the duration
14031   *
14032   * @extends Component
14033   */
14034
14035  var DurationDisplay =
14036  /*#__PURE__*/
14037  function (_TimeDisplay) {
14038    inheritsLoose(DurationDisplay, _TimeDisplay);
14039
14040    /**
14041     * Creates an instance of this class.
14042     *
14043     * @param {Player} player
14044     *        The `Player` that this class should be attached to.
14045     *
14046     * @param {Object} [options]
14047     *        The key/value store of player options.
14048     */
14049    function DurationDisplay(player, options) {
14050      var _this;
14051
14052      _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
14053      // as they should always display the changed duration as
14054      // it has changed
14055
14056      _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
14057      // but the durationchange on the user agent will not fire.
14058      // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
14059
14060
14061      _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
14062      // listeners could have broken dependent applications/libraries. These
14063      // can likely be removed for 7.0.
14064
14065
14066      _this.on(player, 'loadedmetadata', _this.updateContent);
14067
14068      return _this;
14069    }
14070    /**
14071     * Builds the default DOM `className`.
14072     *
14073     * @return {string}
14074     *         The DOM `className` for this object.
14075     */
14076
14077
14078    var _proto = DurationDisplay.prototype;
14079
14080    _proto.buildCSSClass = function buildCSSClass() {
14081      return 'vjs-duration';
14082    }
14083    /**
14084     * Update duration time display.
14085     *
14086     * @param {EventTarget~Event} [event]
14087     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
14088     *        this function to be called.
14089     *
14090     * @listens Player#durationchange
14091     * @listens Player#timeupdate
14092     * @listens Player#loadedmetadata
14093     */
14094    ;
14095
14096    _proto.updateContent = function updateContent(event) {
14097      var duration = this.player_.duration();
14098      this.updateTextNode_(duration);
14099    };
14100
14101    return DurationDisplay;
14102  }(TimeDisplay);
14103  /**
14104   * The text that is added to the `DurationDisplay` for screen reader users.
14105   *
14106   * @type {string}
14107   * @private
14108   */
14109
14110
14111  DurationDisplay.prototype.labelText_ = 'Duration';
14112  /**
14113   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
14114   *
14115   * @type {string}
14116   * @private
14117   *
14118   * @deprecated in v7; controlText_ is not used in non-active display Components
14119   */
14120
14121  DurationDisplay.prototype.controlText_ = 'Duration';
14122  Component.registerComponent('DurationDisplay', DurationDisplay);
14123
14124  /**
14125   * The separator between the current time and duration.
14126   * Can be hidden if it's not needed in the design.
14127   *
14128   * @extends Component
14129   */
14130
14131  var TimeDivider =
14132  /*#__PURE__*/
14133  function (_Component) {
14134    inheritsLoose(TimeDivider, _Component);
14135
14136    function TimeDivider() {
14137      return _Component.apply(this, arguments) || this;
14138    }
14139
14140    var _proto = TimeDivider.prototype;
14141
14142    /**
14143     * Create the component's DOM element
14144     *
14145     * @return {Element}
14146     *         The element that was created.
14147     */
14148    _proto.createEl = function createEl() {
14149      return _Component.prototype.createEl.call(this, 'div', {
14150        className: 'vjs-time-control vjs-time-divider',
14151        innerHTML: '<div><span>/</span></div>'
14152      }, {
14153        // this element and its contents can be hidden from assistive techs since
14154        // it is made extraneous by the announcement of the control text
14155        // for the current time and duration displays
14156        'aria-hidden': true
14157      });
14158    };
14159
14160    return TimeDivider;
14161  }(Component);
14162
14163  Component.registerComponent('TimeDivider', TimeDivider);
14164
14165  /**
14166   * Displays the time left in the video
14167   *
14168   * @extends Component
14169   */
14170
14171  var RemainingTimeDisplay =
14172  /*#__PURE__*/
14173  function (_TimeDisplay) {
14174    inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
14175
14176    /**
14177     * Creates an instance of this class.
14178     *
14179     * @param {Player} player
14180     *        The `Player` that this class should be attached to.
14181     *
14182     * @param {Object} [options]
14183     *        The key/value store of player options.
14184     */
14185    function RemainingTimeDisplay(player, options) {
14186      var _this;
14187
14188      _this = _TimeDisplay.call(this, player, options) || this;
14189
14190      _this.on(player, 'durationchange', _this.updateContent);
14191
14192      return _this;
14193    }
14194    /**
14195     * Builds the default DOM `className`.
14196     *
14197     * @return {string}
14198     *         The DOM `className` for this object.
14199     */
14200
14201
14202    var _proto = RemainingTimeDisplay.prototype;
14203
14204    _proto.buildCSSClass = function buildCSSClass() {
14205      return 'vjs-remaining-time';
14206    }
14207    /**
14208     * Create the `Component`'s DOM element with the "minus" characted prepend to the time
14209     *
14210     * @return {Element}
14211     *         The element that was created.
14212     */
14213    ;
14214
14215    _proto.createEl = function createEl$1() {
14216      var el = _TimeDisplay.prototype.createEl.call(this);
14217
14218      el.insertBefore(createEl('span', {}, {
14219        'aria-hidden': true
14220      }, '-'), this.contentEl_);
14221      return el;
14222    }
14223    /**
14224     * Update remaining time display.
14225     *
14226     * @param {EventTarget~Event} [event]
14227     *        The `timeupdate` or `durationchange` event that caused this to run.
14228     *
14229     * @listens Player#timeupdate
14230     * @listens Player#durationchange
14231     */
14232    ;
14233
14234    _proto.updateContent = function updateContent(event) {
14235      if (typeof this.player_.duration() !== 'number') {
14236        return;
14237      }
14238
14239      var time; // @deprecated We should only use remainingTimeDisplay
14240      // as of video.js 7
14241
14242      if (this.player_.ended()) {
14243        time = 0;
14244      } else if (this.player_.remainingTimeDisplay) {
14245        time = this.player_.remainingTimeDisplay();
14246      } else {
14247        time = this.player_.remainingTime();
14248      }
14249
14250      this.updateTextNode_(time);
14251    };
14252
14253    return RemainingTimeDisplay;
14254  }(TimeDisplay);
14255  /**
14256   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
14257   *
14258   * @type {string}
14259   * @private
14260   */
14261
14262
14263  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
14264  /**
14265   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
14266   *
14267   * @type {string}
14268   * @private
14269   *
14270   * @deprecated in v7; controlText_ is not used in non-active display Components
14271   */
14272
14273  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
14274  Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
14275
14276  /**
14277   * Displays the live indicator when duration is Infinity.
14278   *
14279   * @extends Component
14280   */
14281
14282  var LiveDisplay =
14283  /*#__PURE__*/
14284  function (_Component) {
14285    inheritsLoose(LiveDisplay, _Component);
14286
14287    /**
14288     * Creates an instance of this class.
14289     *
14290     * @param {Player} player
14291     *        The `Player` that this class should be attached to.
14292     *
14293     * @param {Object} [options]
14294     *        The key/value store of player options.
14295     */
14296    function LiveDisplay(player, options) {
14297      var _this;
14298
14299      _this = _Component.call(this, player, options) || this;
14300
14301      _this.updateShowing();
14302
14303      _this.on(_this.player(), 'durationchange', _this.updateShowing);
14304
14305      return _this;
14306    }
14307    /**
14308     * Create the `Component`'s DOM element
14309     *
14310     * @return {Element}
14311     *         The element that was created.
14312     */
14313
14314
14315    var _proto = LiveDisplay.prototype;
14316
14317    _proto.createEl = function createEl$1() {
14318      var el = _Component.prototype.createEl.call(this, 'div', {
14319        className: 'vjs-live-control vjs-control'
14320      });
14321
14322      this.contentEl_ = createEl('div', {
14323        className: 'vjs-live-display',
14324        innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
14325      }, {
14326        'aria-live': 'off'
14327      });
14328      el.appendChild(this.contentEl_);
14329      return el;
14330    };
14331
14332    _proto.dispose = function dispose() {
14333      this.contentEl_ = null;
14334
14335      _Component.prototype.dispose.call(this);
14336    }
14337    /**
14338     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
14339     * it accordingly
14340     *
14341     * @param {EventTarget~Event} [event]
14342     *        The {@link Player#durationchange} event that caused this function to run.
14343     *
14344     * @listens Player#durationchange
14345     */
14346    ;
14347
14348    _proto.updateShowing = function updateShowing(event) {
14349      if (this.player().duration() === Infinity) {
14350        this.show();
14351      } else {
14352        this.hide();
14353      }
14354    };
14355
14356    return LiveDisplay;
14357  }(Component);
14358
14359  Component.registerComponent('LiveDisplay', LiveDisplay);
14360
14361  /**
14362   * Displays the live indicator when duration is Infinity.
14363   *
14364   * @extends Component
14365   */
14366
14367  var SeekToLive =
14368  /*#__PURE__*/
14369  function (_Button) {
14370    inheritsLoose(SeekToLive, _Button);
14371
14372    /**
14373     * Creates an instance of this class.
14374     *
14375     * @param {Player} player
14376     *        The `Player` that this class should be attached to.
14377     *
14378     * @param {Object} [options]
14379     *        The key/value store of player options.
14380     */
14381    function SeekToLive(player, options) {
14382      var _this;
14383
14384      _this = _Button.call(this, player, options) || this;
14385
14386      _this.updateLiveEdgeStatus();
14387
14388      if (_this.player_.liveTracker) {
14389        _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
14390      }
14391
14392      return _this;
14393    }
14394    /**
14395     * Create the `Component`'s DOM element
14396     *
14397     * @return {Element}
14398     *         The element that was created.
14399     */
14400
14401
14402    var _proto = SeekToLive.prototype;
14403
14404    _proto.createEl = function createEl$1() {
14405      var el = _Button.prototype.createEl.call(this, 'button', {
14406        className: 'vjs-seek-to-live-control vjs-control'
14407      });
14408
14409      this.textEl_ = createEl('span', {
14410        className: 'vjs-seek-to-live-text',
14411        innerHTML: this.localize('LIVE')
14412      }, {
14413        'aria-hidden': 'true'
14414      });
14415      el.appendChild(this.textEl_);
14416      return el;
14417    }
14418    /**
14419     * Update the state of this button if we are at the live edge
14420     * or not
14421     */
14422    ;
14423
14424    _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
14425      // default to live edge
14426      if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
14427        this.setAttribute('aria-disabled', true);
14428        this.addClass('vjs-at-live-edge');
14429        this.controlText('Seek to live, currently playing live');
14430      } else {
14431        this.setAttribute('aria-disabled', false);
14432        this.removeClass('vjs-at-live-edge');
14433        this.controlText('Seek to live, currently behind live');
14434      }
14435    }
14436    /**
14437     * On click bring us as near to the live point as possible.
14438     * This requires that we wait for the next `live-seekable-change`
14439     * event which will happen every segment length seconds.
14440     */
14441    ;
14442
14443    _proto.handleClick = function handleClick() {
14444      this.player_.liveTracker.seekToLiveEdge();
14445    }
14446    /**
14447     * Dispose of the element and stop tracking
14448     */
14449    ;
14450
14451    _proto.dispose = function dispose() {
14452      if (this.player_.liveTracker) {
14453        this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
14454      }
14455
14456      this.textEl_ = null;
14457
14458      _Button.prototype.dispose.call(this);
14459    };
14460
14461    return SeekToLive;
14462  }(Button);
14463
14464  SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
14465  Component.registerComponent('SeekToLive', SeekToLive);
14466
14467  /**
14468   * Keep a number between a min and a max value
14469   *
14470   * @param {number} number
14471   *        The number to clamp
14472   *
14473   * @param {number} min
14474   *        The minimum value
14475   * @param {number} max
14476   *        The maximum value
14477   *
14478   * @return {number}
14479   *         the clamped number
14480   */
14481  var clamp = function clamp(number, min, max) {
14482    number = Number(number);
14483    return Math.min(max, Math.max(min, isNaN(number) ? min : number));
14484  };
14485
14486  /**
14487   * The base functionality for a slider. Can be vertical or horizontal.
14488   * For instance the volume bar or the seek bar on a video is a slider.
14489   *
14490   * @extends Component
14491   */
14492
14493  var Slider =
14494  /*#__PURE__*/
14495  function (_Component) {
14496    inheritsLoose(Slider, _Component);
14497
14498    /**
14499    * Create an instance of this class
14500    *
14501    * @param {Player} player
14502    *        The `Player` that this class should be attached to.
14503    *
14504    * @param {Object} [options]
14505    *        The key/value store of player options.
14506    */
14507    function Slider(player, options) {
14508      var _this;
14509
14510      _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
14511
14512      _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
14513
14514      _this.vertical(!!_this.options_.vertical);
14515
14516      _this.enable();
14517
14518      return _this;
14519    }
14520    /**
14521     * Are controls are currently enabled for this slider or not.
14522     *
14523     * @return {boolean}
14524     *         true if controls are enabled, false otherwise
14525     */
14526
14527
14528    var _proto = Slider.prototype;
14529
14530    _proto.enabled = function enabled() {
14531      return this.enabled_;
14532    }
14533    /**
14534     * Enable controls for this slider if they are disabled
14535     */
14536    ;
14537
14538    _proto.enable = function enable() {
14539      if (this.enabled()) {
14540        return;
14541      }
14542
14543      this.on('mousedown', this.handleMouseDown);
vendor: 5,709 bytes, lines 14544-14724
14544      this.on('touchstart', this.handleMouseDown);
14545      this.on('keydown', this.handleKeyDown);
14546      this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired
14547
14548      this.on(this.player_, 'controlsvisible', this.update);
14549
14550      if (this.playerEvent) {
14551        this.on(this.player_, this.playerEvent, this.update);
14552      }
14553
14554      this.removeClass('disabled');
14555      this.setAttribute('tabindex', 0);
14556      this.enabled_ = true;
14557    }
14558    /**
14559     * Disable controls for this slider if they are enabled
14560     */
14561    ;
14562
14563    _proto.disable = function disable() {
14564      if (!this.enabled()) {
14565        return;
14566      }
14567
14568      var doc = this.bar.el_.ownerDocument;
14569      this.off('mousedown', this.handleMouseDown);
14570      this.off('touchstart', this.handleMouseDown);
14571      this.off('keydown', this.handleKeyDown);
14572      this.off('click', this.handleClick);
14573      this.off(this.player_, 'controlsvisible', this.update);
14574      this.off(doc, 'mousemove', this.handleMouseMove);
14575      this.off(doc, 'mouseup', this.handleMouseUp);
14576      this.off(doc, 'touchmove', this.handleMouseMove);
14577      this.off(doc, 'touchend', this.handleMouseUp);
14578      this.removeAttribute('tabindex');
14579      this.addClass('disabled');
14580
14581      if (this.playerEvent) {
14582        this.off(this.player_, this.playerEvent, this.update);
14583      }
14584
14585      this.enabled_ = false;
14586    }
14587    /**
14588     * Create the `Slider`s DOM element.
14589     *
14590     * @param {string} type
14591     *        Type of element to create.
14592     *
14593     * @param {Object} [props={}]
14594     *        List of properties in Object form.
14595     *
14596     * @param {Object} [attributes={}]
14597     *        list of attributes in Object form.
14598     *
14599     * @return {Element}
14600     *         The element that gets created.
14601     */
14602    ;
14603
14604    _proto.createEl = function createEl(type, props, attributes) {
14605      if (props === void 0) {
14606        props = {};
14607      }
14608
14609      if (attributes === void 0) {
14610        attributes = {};
14611      }
14612
14613      // Add the slider element class to all sub classes
14614      props.className = props.className + ' vjs-slider';
14615      props = assign({
14616        tabIndex: 0
14617      }, props);
14618      attributes = assign({
14619        'role': 'slider',
14620        'aria-valuenow': 0,
14621        'aria-valuemin': 0,
14622        'aria-valuemax': 100,
14623        'tabIndex': 0
14624      }, attributes);
14625      return _Component.prototype.createEl.call(this, type, props, attributes);
14626    }
14627    /**
14628     * Handle `mousedown` or `touchstart` events on the `Slider`.
14629     *
14630     * @param {EventTarget~Event} event
14631     *        `mousedown` or `touchstart` event that triggered this function
14632     *
14633     * @listens mousedown
14634     * @listens touchstart
14635     * @fires Slider#slideractive
14636     */
14637    ;
14638
14639    _proto.handleMouseDown = function handleMouseDown(event) {
14640      var doc = this.bar.el_.ownerDocument;
14641
14642      if (event.type === 'mousedown') {
14643        event.preventDefault();
14644      } // Do not call preventDefault() on touchstart in Chrome
14645      // to avoid console warnings. Use a 'touch-action: none' style
14646      // instead to prevent unintented scrolling.
14647      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
14648
14649
14650      if (event.type === 'touchstart' && !IS_CHROME) {
14651        event.preventDefault();
14652      }
14653
14654      blockTextSelection();
14655      this.addClass('vjs-sliding');
14656      /**
14657       * Triggered when the slider is in an active state
14658       *
14659       * @event Slider#slideractive
14660       * @type {EventTarget~Event}
14661       */
14662
14663      this.trigger('slideractive');
14664      this.on(doc, 'mousemove', this.handleMouseMove);
14665      this.on(doc, 'mouseup', this.handleMouseUp);
14666      this.on(doc, 'touchmove', this.handleMouseMove);
14667      this.on(doc, 'touchend', this.handleMouseUp);
14668      this.handleMouseMove(event);
14669    }
14670    /**
14671     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
14672     * The `mousemove` and `touchmove` events will only only trigger this function during
14673     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
14674     * {@link Slider#handleMouseUp}.
14675     *
14676     * @param {EventTarget~Event} event
14677     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
14678     *        this function
14679     *
14680     * @listens mousemove
14681     * @listens touchmove
14682     */
14683    ;
14684
14685    _proto.handleMouseMove = function handleMouseMove(event) {}
14686    /**
14687     * Handle `mouseup` or `touchend` events on the `Slider`.
14688     *
14689     * @param {EventTarget~Event} event
14690     *        `mouseup` or `touchend` event that triggered this function.
14691     *
14692     * @listens touchend
14693     * @listens mouseup
14694     * @fires Slider#sliderinactive
14695     */
14696    ;
14697
14698    _proto.handleMouseUp = function handleMouseUp() {
14699      var doc = this.bar.el_.ownerDocument;
14700      unblockTextSelection();
14701      this.removeClass('vjs-sliding');
14702      /**
14703       * Triggered when the slider is no longer in an active state.
14704       *
14705       * @event Slider#sliderinactive
14706       * @type {EventTarget~Event}
14707       */
14708
14709      this.trigger('sliderinactive');
14710      this.off(doc, 'mousemove', this.handleMouseMove);
14711      this.off(doc, 'mouseup', this.handleMouseUp);
14712      this.off(doc, 'touchmove', this.handleMouseMove);
14713      this.off(doc, 'touchend', this.handleMouseUp);
14714      this.update();
14715    }
14716    /**
14717     * Update the progress bar of the `Slider`.
14718     *
14719     * @return {number}
14720     *          The percentage of progress the progress bar represents as a
14721     *          number from 0 to 1.
14722     */
14723    ;
14724
14725    _proto.update = function update() {
14726      var _this2 = this;
14727
14728      // In VolumeBar init we have a setTimeout for update that pops and update
14729      // to the end of the execution stack. The player is destroyed before then
14730      // update will cause an error
14731      // If there's no bar...
14732      if (!this.el_ || !this.bar) {
14733        return;
14734      } // clamp progress between 0 and 1
14735      // and only round to four decimal places, as we round to two below
14736
14737
14738      var progress = this.getProgress();
14739
14740      if (progress === this.progress_) {
14741        return progress;
14742      }
14743
14744      this.progress_ = progress;
14745      this.requestAnimationFrame(function () {
14746        // Set the new bar width or height
14747        var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value
14748
14749        _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
14750      });
14751      return progress;
14752    }
14753    /**
14754     * Get the percentage of the bar that should be filled
14755     * but clamped and rounded.
14756     *
14757     * @return {number}
14758     *         percentage filled that the slider is
14759     */
14760    ;
14761
14762    _proto.getProgress = function getProgress() {
14763      return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
14764    }
14765    /**
14766     * Calculate distance for slider
14767     *
14768     * @param {EventTarget~Event} event
14769     *        The event that caused this function to run.
14770     *
14771     * @return {number}
14772     *         The current position of the Slider.
14773     *         - position.x for vertical `Slider`s
14774     *         - position.y for horizontal `Slider`s
14775     */
14776    ;
14777
14778    _proto.calculateDistance = function calculateDistance(event) {
14779      var position = getPointerPosition(this.el_, event);
14780
14781      if (this.vertical()) {
14782        return position.y;
14783      }
14784
14785      return position.x;
14786    }
14787    /**
14788     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
14789     * arrow keys. This function will only be called when the slider has focus. See
14790     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
14791     *
14792     * @param {EventTarget~Event} event
14793     *        the `keydown` event that caused this function to run.
14794     *
14795     * @listens keydown
14796     */
14797    ;
14798
14799    _proto.handleKeyDown = function handleKeyDown(event) {
14800      // Left and Down Arrows
14801      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
14802        event.preventDefault();
14803        event.stopPropagation();
14804        this.stepBack(); // Up and Right Arrows
14805      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
14806        event.preventDefault();
14807        event.stopPropagation();
14808        this.stepForward();
14809      } else {
14810        // Pass keydown handling up for unsupported keys
14811        _Component.prototype.handleKeyDown.call(this, event);
14812      }
14813    }
14814    /**
14815     * Listener for click events on slider, used to prevent clicks
14816     *   from bubbling up to parent elements like button menus.
14817     *
14818     * @param {Object} event
14819     *        Event that caused this object to run
14820     */
14821    ;
14822
14823    _proto.handleClick = function handleClick(event) {
14824      event.stopPropagation();
14825      event.preventDefault();
14826    }
14827    /**
14828     * Get/set if slider is horizontal for vertical
14829     *
14830     * @param {boolean} [bool]
14831     *        - true if slider is vertical,
14832     *        - false is horizontal
14833     *
14834     * @return {boolean}
14835     *         - true if slider is vertical, and getting
14836     *         - false if the slider is horizontal, and getting
14837     */
14838    ;
14839
14840    _proto.vertical = function vertical(bool) {
14841      if (bool === undefined) {
14842        return this.vertical_ || false;
14843      }
14844
14845      this.vertical_ = !!bool;
14846
14847      if (this.vertical_) {
14848        this.addClass('vjs-slider-vertical');
14849      } else {
14850        this.addClass('vjs-slider-horizontal');
14851      }
14852    };
14853
14854    return Slider;
14855  }(Component);
14856
14857  Component.registerComponent('Slider', Slider);
14858
14859  var percentify = function percentify(time, end) {
14860    return clamp(time / end * 100, 0, 100).toFixed(2) + '%';
14861  };
14862  /**
14863   * Shows loading progress
14864   *
14865   * @extends Component
14866   */
14867
14868
14869  var LoadProgressBar =
14870  /*#__PURE__*/
14871  function (_Component) {
14872    inheritsLoose(LoadProgressBar, _Component);
14873
14874    /**
14875     * Creates an instance of this class.
14876     *
14877     * @param {Player} player
14878     *        The `Player` that this class should be attached to.
14879     *
14880     * @param {Object} [options]
14881     *        The key/value store of player options.
14882     */
14883    function LoadProgressBar(player, options) {
14884      var _this;
14885
14886      _this = _Component.call(this, player, options) || this;
14887      _this.partEls_ = [];
14888
14889      _this.on(player, 'progress', _this.update);
14890
14891      return _this;
14892    }
14893    /**
14894     * Create the `Component`'s DOM element
14895     *
14896     * @return {Element}
14897     *         The element that was created.
14898     */
14899
14900
14901    var _proto = LoadProgressBar.prototype;
14902
14903    _proto.createEl = function createEl$1() {
14904      var el = _Component.prototype.createEl.call(this, 'div', {
14905        className: 'vjs-load-progress'
14906      });
14907
14908      var wrapper = createEl('span', {
14909        className: 'vjs-control-text'
14910      });
14911      var loadedText = createEl('span', {
14912        textContent: this.localize('Loaded')
14913      });
14914      var separator = document.createTextNode(': ');
14915      this.percentageEl_ = createEl('span', {
14916        className: 'vjs-control-text-loaded-percentage',
14917        textContent: '0%'
14918      });
14919      el.appendChild(wrapper);
14920      wrapper.appendChild(loadedText);
14921      wrapper.appendChild(separator);
14922      wrapper.appendChild(this.percentageEl_);
14923      return el;
14924    };
14925
14926    _proto.dispose = function dispose() {
14927      this.partEls_ = null;
14928      this.percentageEl_ = null;
14929
14930      _Component.prototype.dispose.call(this);
14931    }
14932    /**
14933     * Update progress bar
14934     *
14935     * @param {EventTarget~Event} [event]
14936     *        The `progress` event that caused this function to run.
14937     *
14938     * @listens Player#progress
14939     */
14940    ;
14941
14942    _proto.update = function update(event) {
14943      var _this2 = this;
14944
14945      this.requestAnimationFrame(function () {
14946        var liveTracker = _this2.player_.liveTracker;
14947
14948        var buffered = _this2.player_.buffered();
14949
14950        var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration();
14951
14952        var bufferedEnd = _this2.player_.bufferedEnd();
14953
14954        var children = _this2.partEls_;
14955        var percent = percentify(bufferedEnd, duration);
14956
14957        if (_this2.percent_ !== percent) {
14958          // update the width of the progress bar
14959          _this2.el_.style.width = percent; // update the control-text
14960
14961          textContent(_this2.percentageEl_, percent);
14962          _this2.percent_ = percent;
14963        } // add child elements to represent the individual buffered time ranges
14964
14965
14966        for (var i = 0; i < buffered.length; i++) {
14967          var start = buffered.start(i);
14968          var end = buffered.end(i);
14969          var part = children[i];
14970
14971          if (!part) {
14972            part = _this2.el_.appendChild(createEl());
14973            children[i] = part;
14974          } //  only update if changed
14975
14976
14977          if (part.dataset.start === start && part.dataset.end === end) {
14978            continue;
14979          }
14980
14981          part.dataset.start = start;
14982          part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd)
14983
14984          part.style.left = percentify(start, bufferedEnd);
14985          part.style.width = percentify(end - start, bufferedEnd);
14986        } // remove unused buffered range elements
14987
14988
14989        for (var _i = children.length; _i > buffered.length; _i--) {
14990          _this2.el_.removeChild(children[_i - 1]);
14991        }
14992
14993        children.length = buffered.length;
14994      });
14995    };
14996
14997    return LoadProgressBar;
14998  }(Component);
14999
15000  Component.registerComponent('LoadProgressBar', LoadProgressBar);
15001
15002  /**
15003   * Time tooltips display a time above the progress bar.
15004   *
15005   * @extends Component
15006   */
15007
15008  var TimeTooltip =
15009  /*#__PURE__*/
15010  function (_Component) {
15011    inheritsLoose(TimeTooltip, _Component);
15012
15013    /**
15014     * Creates an instance of this class.
15015     *
15016     * @param {Player} player
15017     *        The {@link Player} that this class should be attached to.
15018     *
15019     * @param {Object} [options]
15020     *        The key/value store of player options.
15021     */
15022    function TimeTooltip(player, options) {
15023      var _this;
15024
15025      _this = _Component.call(this, player, options) || this;
15026      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15027      return _this;
15028    }
15029    /**
15030     * Create the time tooltip DOM element
15031     *
15032     * @return {Element}
15033     *         The element that was created.
15034     */
15035
15036
15037    var _proto = TimeTooltip.prototype;
15038
15039    _proto.createEl = function createEl() {
15040      return _Component.prototype.createEl.call(this, 'div', {
15041        className: 'vjs-time-tooltip'
15042      }, {
15043        'aria-hidden': 'true'
15044      });
15045    }
15046    /**
15047     * Updates the position of the time tooltip relative to the `SeekBar`.
15048     *
15049     * @param {Object} seekBarRect
15050     *        The `ClientRect` for the {@link SeekBar} element.
15051     *
15052     * @param {number} seekBarPoint
15053     *        A number from 0 to 1, representing a horizontal reference point
15054     *        from the left edge of the {@link SeekBar}
15055     */
15056    ;
15057
15058    _proto.update = function update(seekBarRect, seekBarPoint, content) {
15059      var tooltipRect = getBoundingClientRect(this.el_);
15060      var playerRect = getBoundingClientRect(this.player_.el());
15061      var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
15062      // for example, if the player isn't in the DOM for testing
15063
15064      if (!playerRect || !tooltipRect) {
15065        return;
15066      } // This is the space left of the `seekBarPoint` available within the bounds
15067      // of the player. We calculate any gap between the left edge of the player
15068      // and the left edge of the `SeekBar` and add the number of pixels in the
15069      // `SeekBar` before hitting the `seekBarPoint`
15070
15071
15072      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
15073      // of the player. We calculate the number of pixels from the `seekBarPoint`
15074      // to the right edge of the `SeekBar` and add to that any gap between the
15075      // right edge of the `SeekBar` and the player.
15076
15077      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
15078      // further to the right to center it over the `seekBarPoint`.
15079
15080      var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
15081      // the results of the space calculations above.
15082
15083      if (spaceLeftOfPoint < pullTooltipBy) {
15084        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15085      }
15085 else if (spaceRightOfPoint < pullTooltipBy) {
15086        pullTooltipBy = spaceRightOfPoint;
15087      } // Due to the imprecision of decimal/ratio based calculations and varying
15088      // rounding behaviors, there are cases where the spacing adjustment is off
15089      // by a pixel or two. This adds insurance to these calculations.
15090
15091
15092      if (pullTooltipBy < 0) {
15093        pullTooltipBy = 0;
15094      } else if (pullTooltipBy > tooltipRect.width) {
15095        pullTooltipBy = tooltipRect.width;
15096      }
15097
15098      this.el_.style.right = "-" + pullTooltipBy + "px";
15099      this.write(content);
15100    }
15101    /**
15102     * Write the time to the tooltip DOM element.
15103     *
15104     * @param {string} content
15105     *        The formatted time for the tooltip.
15106     */
15107    ;
15108
15109    _proto.write = function write(content) {
15110      textContent(this.el_, content);
15111    }
15112    /**
15113     * Updates the position of the time tooltip relative to the `SeekBar`.
15114     *
15115     * @param {Object} seekBarRect
15116     *        The `ClientRect` for the {@link SeekBar} element.
15117     *
15118     * @param {number} seekBarPoint
15119     *        A number from 0 to 1, representing a horizontal reference point
15120     *        from the left edge of the {@link SeekBar}
15121     *
15122     * @param {number} time
15123     *        The time to update the tooltip to, not used during live playback
15124     *
15125     * @param {Function} cb
15126     *        A function that will be called during the request animation frame
15127     *        for tooltips that need to do additional animations from the default
15128     */
15129    ;
15130
15131    _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
15132      var _this2 = this;
15133
15134      // If there is an existing rAF ID, cancel it so we don't over-queue.
15135      if (this.rafId_) {
15136        this.cancelAnimationFrame(this.rafId_);
15137      }
15138
15139      this.rafId_ = this.requestAnimationFrame(function () {
15140        var content;
15141
15142        var duration = _this2.player_.duration();
15143
15144        if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) {
15145          var liveWindow = _this2.player_.liveTracker.liveWindow();
15146
15147          var secondsBehind = liveWindow - seekBarPoint * liveWindow;
15148          content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
15149        } else {
15150          content = formatTime(time, duration);
15151        }
15152
15153        _this2.update(seekBarRect, seekBarPoint, content);
15154
15155        if (cb) {
15156          cb();
15157        }
15158      });
15159    };
15160
15161    return TimeTooltip;
15162  }(Component);
15163
15164  Component.registerComponent('TimeTooltip', TimeTooltip);
15165
15166  /**
15167   * Used by {@link SeekBar} to display media playback progress as part of the
15168   * {@link ProgressControl}.
15169   *
15170   * @extends Component
15171   */
15172
15173  var PlayProgressBar =
15174  /*#__PURE__*/
15175  function (_Component) {
15176    inheritsLoose(PlayProgressBar, _Component);
15177
15178    /**
15179     * Creates an instance of this class.
15180     *
15181     * @param {Player} player
15182     *        The {@link Player} that this class should be attached to.
15183     *
15184     * @param {Object} [options]
15185     *        The key/value store of player options.
15186     */
15187    function PlayProgressBar(player, options) {
15188      var _this;
15189
15190      _this = _Component.call(this, player, options) || this;
15191      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15192      return _this;
15193    }
15194    /**
15195     * Create the the DOM element for this class.
15196     *
15197     * @return {Element}
15198     *         The element that was created.
15199     */
15200
15201
15202    var _proto = PlayProgressBar.prototype;
15203
15204    _proto.createEl = function createEl() {
15205      return _Component.prototype.createEl.call(this, 'div', {
15206        className: 'vjs-play-progress vjs-slider-bar'
15207      }, {
15208        'aria-hidden': 'true'
15209      });
15210    }
15211    /**
15212     * Enqueues updates to its own DOM as well as the DOM of its
15213     * {@link TimeTooltip} child.
15214     *
15215     * @param {Object} seekBarRect
15216     *        The `ClientRect` for the {@link SeekBar} element.
15217     *
15218     * @param {number} seekBarPoint
15219     *        A number from 0 to 1, representing a horizontal reference point
15220     *        from the left edge of the {@link SeekBar}
15221     */
15222    ;
15223
15224    _proto.update = function update(seekBarRect, seekBarPoint) {
15225      var timeTooltip = this.getChild('timeTooltip');
15226
15227      if (!timeTooltip) {
15228        return;
15229      }
15230
15231      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15232      timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
15233    };
15234
15235    return PlayProgressBar;
15236  }(Component);
15237  /**
15238   * Default options for {@link PlayProgressBar}.
15239   *
15240   * @type {Object}
15241   * @private
15242   */
15243
15244
15245  PlayProgressBar.prototype.options_ = {
15246    children: []
15247  }; // Time tooltips should not be added to a player on mobile devices
15248
15249  if (!IS_IOS && !IS_ANDROID) {
15250    PlayProgressBar.prototype.options_.children.push('timeTooltip');
15251  }
15252
15253  Component.registerComponent('PlayProgressBar', PlayProgressBar);
15254
15255  /**
15256   * The {@link MouseTimeDisplay} component tracks mouse movement over the
15257   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
15258   * indicating the time which is represented by a given point in the
15259   * {@link ProgressControl}.
15260   *
15261   * @extends Component
15262   */
15263
15264  var MouseTimeDisplay =
15265  /*#__PURE__*/
15266  function (_Component) {
15267    inheritsLoose(MouseTimeDisplay, _Component);
15268
15269    /**
15270     * Creates an instance of this class.
15271     *
15272     * @param {Player} player
15273     *        The {@link Player} that this class should be attached to.
15274     *
15275     * @param {Object} [options]
15276     *        The key/value store of player options.
15277     */
15278    function MouseTimeDisplay(player, options) {
15279      var _this;
15280
15281      _this = _Component.call(this, player, options) || this;
15282      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
15283      return _this;
15284    }
15285    /**
15286     * Create the DOM element for this class.
15287     *
15288     * @return {Element}
15289     *         The element that was created.
15290     */
15291
15292
15293    var _proto = MouseTimeDisplay.prototype;
15294
15295    _proto.createEl = function createEl() {
15296      return _Component.prototype.createEl.call(this, 'div', {
15297        className: 'vjs-mouse-display'
15298      });
15299    }
15300    /**
15301     * Enqueues updates to its own DOM as well as the DOM of its
15302     * {@link TimeTooltip} child.
15303     *
15304     * @param {Object} seekBarRect
15305     *        The `ClientRect` for the {@link SeekBar} element.
15306     *
15307     * @param {number} seekBarPoint
15308     *        A number from 0 to 1, representing a horizontal reference point
15309     *        from the left edge of the {@link SeekBar}
15310     */
15311    ;
15312
15313    _proto.update = function update(seekBarRect, seekBarPoint) {
15314      var _this2 = this;
15315
15316      var time = seekBarPoint * this.player_.duration();
15317      this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
15318        _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
15319      });
15320    };
15321
15322    return MouseTimeDisplay;
15323  }(Component);
15324  /**
15325   * Default options for `MouseTimeDisplay`
15326   *
15327   * @type {Object}
15328   * @private
15329   */
15330
15331
15332  MouseTimeDisplay.prototype.options_ = {
15333    children: ['timeTooltip']
15334  };
15335  Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
15336
15337  var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
15338
15339  var PAGE_KEY_MULTIPLIER = 12;
15340  /**
15341   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
15342   * as its `bar`.
15343   *
15344   * @extends Slider
15345   */
15346
15347  var SeekBar =
15348  /*#__PURE__*/
15349  function (_Slider) {
15350    inheritsLoose(SeekBar, _Slider);
15351
15352    /**
15353     * Creates an instance of this class.
15354     *
15355     * @param {Player} player
15356     *        The `Player` that this class should be attached to.
15357     *
15358     * @param {Object} [options]
15359     *        The key/value store of player options.
15360     */
15361    function SeekBar(player, options) {
15362      var _this;
15363
15364      _this = _Slider.call(this, player, options) || this;
15365
15366      _this.setEventHandlers_();
15367
15368      return _this;
15369    }
15370    /**
15371     * Sets the event handlers
15372     *
15373     * @private
15374     */
15375
15376
15377    var _proto = SeekBar.prototype;
15378
15379    _proto.setEventHandlers_ = function setEventHandlers_() {
15380      this.update_ = bind(this, this.update);
15381      this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
15382      this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
15383
15384      if (this.player_.liveTracker) {
15385        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
15386      } // when playing, let's ensure we smoothly update the play progress bar
15387      // via an interval
15388
15389
15390      this.updateInterval = null;
15391      this.on(this.player_, ['playing'], this.enableInterval_);
15392      this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
15393      // also, this causes the CPU to spike and eventually crash the page on IE11.
15394
15395      if ('hidden' in document && 'visibilityState' in document) {
15396        this.on(document, 'visibilitychange', this.toggleVisibility_);
15397      }
15398    };
15399
15400    _proto.toggleVisibility_ = function toggleVisibility_(e) {
15401      if (document.hidden) {
15402        this.disableInterval_(e);
15403      } else {
15404        this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
15405
15406        this.update();
15407      }
15408    };
15409
15410    _proto.enableInterval_ = function enableInterval_() {
15411      if (this.updateInterval) {
15412        return;
15413      }
15414
15415      this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
15416    };
15417
15418    _proto.disableInterval_ = function disableInterval_(e) {
15419      if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
15420        return;
15421      }
15422
15423      if (!this.updateInterval) {
15424        return;
15425      }
15426
15427      this.clearInterval(this.updateInterval);
15428      this.updateInterval = null;
15429    }
15430    /**
15431     * Create the `Component`'s DOM element
15432     *
15433     * @return {Element}
15434     *         The element that was created.
15435     */
15436    ;
15437
15438    _proto.createEl = function createEl() {
15439      return _Slider.prototype.createEl.call(this, 'div', {
15440        className: 'vjs-progress-holder'
15441      }, {
15442        'aria-label': this.localize('Progress Bar')
15443      });
15444    }
15445    /**
15446     * This function updates the play progress bar and accessibility
15447     * attributes to whatever is passed in.
15448     *
15449     * @param {EventTarget~Event} [event]
15450     *        The `timeupdate` or `ended` event that caused this to run.
15451     *
15452     * @listens Player#timeupdate
15453     *
15454     * @return {number}
15455     *          The current percent at a number from 0-1
15456     */
15457    ;
15458
15459    _proto.update = function update(event) {
15460      var _this2 = this;
15461
15462      var percent = _Slider.prototype.update.call(this);
15463
15464      this.requestAnimationFrame(function () {
15465        var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_();
15466        var liveTracker = _this2.player_.liveTracker;
15467
15468        var duration = _this2.player_.duration();
15469
15470        if (liveTracker && liveTracker.isLive()) {
15471          duration = _this2.player_.liveTracker.liveCurrentTime();
15472        }
15473
15474        if (_this2.percent_ !== percent) {
15475          // machine readable value of progress bar (percentage complete)
15476          _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
15477
15478          _this2.percent_ = percent;
15479        }
15480
15481        if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) {
15482          // human readable value of progress bar (time complete)
15483          _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}
15483', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
15484
15485          _this2.currentTime_ = currentTime;
15486          _this2.duration_ = duration;
15487        } // update the progress bar time tooltip with the current time
15488
15489
15490        if (_this2.bar) {
15491          _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress());
15492        }
15493      });
15494      return percent;
15495    }
15496    /**
15497     * Get the value of current time but allows for smooth scrubbing,
15498     * when player can't keep up.
15499     *
15500     * @return {number}
15501     *         The current time value to display
15502     *
15503     * @private
15504     */
15505    ;
15506
15507    _proto.getCurrentTime_ = function getCurrentTime_() {
15508      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15509    }
15510    /**
15511     * Get the percentage of media played so far.
15512     *
15513     * @return {number}
15514     *         The percentage of media played so far (0 to 1).
15515     */
15516    ;
15517
15518    _proto.getPercent = function getPercent() {
15519      var currentTime = this.getCurrentTime_();
15520      var percent;
15521      var liveTracker = this.player_.liveTracker;
15522
15523      if (liveTracker && liveTracker.isLive()) {
15524        percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
15525
15526        if (liveTracker.atLiveEdge()) {
15527          percent = 1;
15528        }
15529      } else {
15530        percent = currentTime / this.player_.duration();
15531      }
15532
15533      return percent;
15534    }
15535    /**
15536     * Handle mouse down on seek bar
15537     *
15538     * @param {EventTarget~Event} event
15539     *        The `mousedown` event that caused this to run.
15540     *
15541     * @listens mousedown
15542     */
15543    ;
15544
15545    _proto.handleMouseDown = function handleMouseDown(event) {
15546      if (!isSingleLeftClick(event)) {
15547        return;
15548      } // Stop event propagation to prevent double fire in progress-control.js
15549
15550
15551      event.stopPropagation();
15552      this.player_.scrubbing(true);
15553      this.videoWasPlaying = !this.player_.paused();
15554      this.player_.pause();
15555
15556      _Slider.prototype.handleMouseDown.call(this, event);
15557    }
15558    /**
15559     * Handle mouse move on seek bar
15560     *
15561     * @param {EventTarget~Event} event
15562     *        The `mousemove` event that caused this to run.
15563     *
15564     * @listens mousemove
15565     */
15566    ;
15567
15568    _proto.handleMouseMove = function handleMouseMove(event) {
15569      if (!isSingleLeftClick(event)) {
15570        return;
15571      }
15572
15573      var newTime;
15574      var distance = this.calculateDistance(event);
15575      var liveTracker = this.player_.liveTracker;
15576
15577      if (!liveTracker || !liveTracker.isLive()) {
15578        newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
15579
15580        if (newTime === this.player_.duration()) {
15581          newTime = newTime - 0.1;
15582        }
15583      } else {
15584        var seekableStart = liveTracker.seekableStart();
15585        var seekableEnd = liveTracker.liveCurrentTime();
15586        newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
15587
15588        if (newTime >= seekableEnd) {
15589          newTime = seekableEnd;
15590        } // Compensate for precision differences so that currentTime is not less
15591        // than seekable start
15592
15593
15594        if (newTime <= seekableStart) {
15595          newTime = seekableStart + 0.1;
15596        } // On android seekableEnd can be Infinity sometimes,
15597        // this will cause newTime to be Infinity, which is
15598        // not a valid currentTime.
15599
15600
15601        if (newTime === Infinity) {
15602          return;
15603        }
15604      } // Set new time (tell player to seek to new time)
15605
15606
15607      this.player_.currentTime(newTime);
15608    };
15609
15610    _proto.enable = function enable() {
15611      _Slider.prototype.enable.call(this);
15612
15613      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15614
15615      if (!mouseTimeDisplay) {
15616        return;
15617      }
15618
15619      mouseTimeDisplay.show();
15620    };
15621
15622    _proto.disable = function disable() {
15623      _Slider.prototype.disable.call(this);
15624
15625      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15626
15627      if (!mouseTimeDisplay) {
15628        return;
15629      }
15630
15631      mouseTimeDisplay.hide();
15632    }
15633    /**
15634     * Handle mouse up on seek bar
15635     *
15636     * @param {EventTarget~Event} event
15637     *        The `mouseup` event that caused this to run.
15638     *
15639     * @listens mouseup
15640     */
15641    ;
15642
15643    _proto.handleMouseUp = function handleMouseUp(event) {
15644      _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
15645
15646
15647      if (event) {
15648        event.stopPropagation();
15649      }
15650
15651      this.player_.scrubbing(false);
15652      /**
15653       * Trigger timeupdate because we're done seeking and the time has changed.
15654       * This is particularly useful for if the player is paused to time the time displays.
15655       *
15656       * @event Tech#timeupdate
15657       * @type {EventTarget~Event}
15658       */
15659
15660      this.player_.trigger({
15661        type: 'timeupdate',
15662        target: this,
15663        manuallyTriggered: true
15664      });
15665
15666      if (this.videoWasPlaying) {
15667        silencePromise(this.player_.play());
15668      } else {
15669        // We're done seeking and the time has changed.
15670        // If the player is paused, make sure we display the correct time on the seek bar.
15671        this.update_();
15672      }
15673    }
15674    /**
15675     * Move more quickly fast forward for keyboard-only users
15676     */
15677    ;
15678
15679    _proto.stepForward = function stepForward() {
15680      this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
15681    }
15682    /**
15683     * Move more quickly rewind for keyboard-only users
15684     */
15685    ;
15686
15687    _proto.stepBack = function stepBack() {
15688      this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
15689    }
15690    /**
15691     * Toggles the playback state of the player
15692     * This gets called when enter or space is used on the seekbar
15693     *
15694     * @param {EventTarget~Event} event
15695     *        The `keydown` event that caused this function to be called
15696     *
15697     */
15698    ;
15699
15700    _proto.handleAction = function handleAction(event) {
15701      if (this.player_.paused()) {
15702        this.player_.play();
15703      } else {
15704        this.player_.pause();
15705      }
15706    }
15707    /**
15708     * Called when this SeekBar has focus and a key gets pressed down.
15709     * Supports the following keys:
15710     *
15711     *   Space or Enter key fire a click event
15712     *   Home key moves to start of the timeline
15713     *   End key moves to end of the timeline
15714     *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
15715     *   PageDown key moves back a larger step than ArrowDown
15716     *   PageUp key moves forward a large step
15717     *
15718     * @param {EventTarget~Event} event
15719     *        The `keydown` event that caused this function to be called.
15720     *
15721     * @listens keydown
15722     */
15723    ;
15724
15725    _proto.handleKeyDown = function handleKeyDown(event) {
15726      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
15727        event.preventDefault();
15728        event.stopPropagation();
15729        this.handleAction(event);
15730      } else if (keycode.isEventKey(event, 'Home')) {
15731        event.preventDefault();
15732        event.stopPropagation();
15733        this.player_.currentTime(0);
15734      } else if (keycode.isEventKey(event, 'End')) {
15735        event.preventDefault();
15736        event.stopPropagation();
15737        this.player_.currentTime(this.player_.duration());
15738      } else if (/^[0-9]$/.test(keycode(event))) {
15739        event.preventDefault();
15740        event.stopPropagation();
15741        var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
15742        this.player_.currentTime(this.player_.duration() * gotoFraction);
15743      } else if (keycode.isEventKey(event, 'PgDn')) {
15744        event.preventDefault();
15745        event.stopPropagation();
15746        this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15747      } else if (keycode.isEventKey(event, 'PgUp')) {
15748        event.preventDefault();
15749        event.stopPropagation();
15750        this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15751      } else {
15752        // Pass keydown handling up for unsupported keys
15753        _Slider.prototype.handleKeyDown.call(this, event);
15754      }
15755    };
15756
15757    return SeekBar;
15758  }(Slider);
15759  /**
15760   * Default options for the `SeekBar`
15761   *
15762   * @type {Object}
15763   * @private
15764   */
15765
15766
15767  SeekBar.prototype.options_ = {
15768    children: ['loadProgressBar', 'playProgressBar'],
15769    barName: 'playProgressBar'
15770  }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
15771
15772  if (!IS_IOS && !IS_ANDROID) {
15773    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
15774  }
15775
15776  Component.registerComponent('SeekBar', SeekBar);
15777
15778  /**
15779   * The Progress Control component contains the seek bar, load progress,
15780   * and play progress.
15781   *
15782   * @extends Component
15783   */
15784
15785  var ProgressControl =
15786  /*#__PURE__*/
15787  function (_Component) {
15788    inheritsLoose(ProgressControl, _Component);
15789
15790    /**
15791     * Creates an instance of this class.
15792     *
15793     * @param {Player} player
15794     *        The `Player` that this class should be attached to.
15795     *
15796     * @param {Object} [options]
15797     *        The key/value store of player options.
15798     */
15799    function ProgressControl(player, options) {
15800      var _this;
15801
15802      _this = _Component.call(this, player, options) || this;
15803      _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
15804      _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
15805
15806      _this.enable();
15807
15808      return _this;
15809    }
15810    /**
15811     * Create the `Component`'s DOM element
15812     *
15813     * @return {Element}
15814     *         The element that was created.
15815     */
15816
15817
15818    var _proto = ProgressControl.prototype;
15819
15820    _proto.createEl = function createEl() {
15821      return _Component.prototype.createEl.call(this, 'div', {
15822        className: 'vjs-progress-control vjs-control'
15823      });
15824    }
15825    /**
15826     * When the mouse moves over the `ProgressControl`, the pointer position
15827     * gets passed down to the `MouseTimeDisplay` component.
15828     *
15829     * @param {EventTarget~Event} event
15830     *        The `mousemove` event that caused this function to run.
15831     *
15832     * @listen mousemove
15833     */
15834    ;
15835
15836    _proto.handleMouseMove = function handleMouseMove(event) {
15837      var seekBar = this.getChild('seekBar');
15838
15839      if (!seekBar) {
15840        return;
15841      }
15842
15843      var playProgressBar = seekBar.getChild('playProgressBar');
15844      var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
15845
15846      if (!playProgressBar && !mouseTimeDisplay) {
15847        return;
15848      }
15849
15850      var seekBarEl = seekBar.el();
15851      var seekBarRect = getBoundingClientRect(seekBarEl);
15852      var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
15853      // that it's possible to trigger this behavior outside the boundaries of
15854      // the `SeekBar`. This ensures we stay within it at all times.
15855
15856      seekBarPoint = clamp(0, 1, seekBarPoint);
15857
15858      if (mouseTimeDisplay) {
15859        mouseTimeDisplay.update(seekBarRect, seekBarPoint);
15860      }
15861
15862      if (playProgressBar) {
15863        playProgressBar.update(seekBarRect, seekBar.getProgress());
15864      }
15865    }
15866    /**
15867     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
15868     *
15869     * @method ProgressControl#throttledHandleMouseSeek
15870     * @param {EventTarget~Event} event
15871     *        The `mousemove` event that caused this function to run.
15872     *
15873     * @listen mousemove
15874     * @listen touchmove
15875     */
15876
15877    /**
15878     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
15879     *
15880     * @param {EventTarget~Event} event
15881     *        `mousedown` or `touchstart` event that triggered this function
15882     *
15883     * @listens mousemove
15884     * @listens touchmove
15885     */
15886    ;
15887
15888    _proto.handleMouseSeek = function handleMouseSeek(event) {
15889      var seekBar = this.getChild('seekBar');
15890
15891      if (seekBar) {
15892        seekBar.handleMouseMove(event);
15893      }
15894    }
15895    /**
15896     * Are controls are currently enabled for this progress control.
15897     *
15898     * @return {boolean}
15899     *         true if controls are enabled, false otherwise
15900     */
15901    ;
15902
15903    _proto.enabled = function enabled() {
15904      return this.enabled_;
15905    }
15906    /**
15907     * Disable all controls on the progress control and its children
15908     */
15909    ;
15910
15911    _proto.disable = function disable() {
15912      this.children().forEach(function (child) {
15913        return child.disable && child.disable();
15914      });
15915
15916      if (!this.enabled()) {
15917        return;
15918      }
15919
15920      this.off(['mousedown', 'touchstart'], this.handleMouseDown);
15921      this.off(this.el_, 'mousemove', this.handleMouseMove);
15922      this.handleMouseUp();
15923      this.addClass('disabled');
15924      this.enabled_ = false;
15925    }
15926    /**
15927     * Enable all controls on the progress control and its children
15928     */
15929    ;
15930
15931    _proto.enable = function enable() {
15932      this.children().forEach(function (child) {
15933        return child.enable && child.enable();
15934      });
15935
15936      if (this.enabled()) {
15937        return;
15938      }
15939
15940      this.on(['mousedown', 'touchstart'], this.handleMouseDown);
15941      this.on(this.el_, 'mousemove', this.handleMouseMove);
15942      this.removeClass('disabled');
15943      this.enabled_ = true;
15944    }
15945    /**
15946     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
15947     *
15948     * @param {EventTarget~Event} event
15949     *        `mousedown` or `touchstart` event that triggered this function
15950     *
15951     * @listens mousedown
15952     * @listens touchstart
15953     */
15954    ;
15955
15956    _proto.handleMouseDown = function handleMouseDown(event) {
15957      var doc = this.el_.ownerDocument;
15958      var seekBar = this.getChild('seekBar');
15959
15960      if (seekBar) {
15961        seekBar.handleMouseDown(event);
15962      }
15963
15964      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
15965      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
15966      this.on(doc, 'mouseup', this.handleMouseUp);
15967      this.on(doc, 'touchend', this.handleMouseUp);
15968    }
15969    /**
15970     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
15971     *
15972     * @param {EventTarget~Event} event
15973     *        `mouseup` or `touchend` event that triggered this function.
15974     *
15975     * @listens touchend
15976     * @listens mouseup
15977     */
15978    ;
15979
15980    _proto.handleMouseUp = function handleMouseUp(event) {
15981      var doc = this.el_.ownerDocument;
15982      var seekBar = this.getChild('seekBar');
15983
15984      if (seekBar) {
15985        seekBar.handleMouseUp(event);
15986      }
15987
15988      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
15989      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
15990      this.off(doc, 'mouseup', this.handleMouseUp);
15991      this.off(doc, 'touchend', this.handleMouseUp);
15992    };
15993
15994    return ProgressControl;
15995  }(Component);
15996  /**
15997   * Default options for `ProgressControl`
15998   *
15999   * @type {Object}
16000   * @private
16001   */
16002
16003
16004  ProgressControl.prototype.options_ = {
16005    children: ['seekBar']
16006  };
16007  Component.registerComponent('ProgressControl', ProgressControl);
16008
16009  /**
16010   * Toggle Picture-in-Picture mode
16011   *
16012   * @extends Button
16013   */
16014
16015  var PictureInPictureToggle =
16016  /*#__PURE__*/
16017  function (_Button) {
16018    inheritsLoose(PictureInPictureToggle, _Button);
16019
16020    /**
16021     * Creates an instance of this class.
16022     *
16023     * @param {Player} player
16024     *        The `Player` that this class should be attached to.
16025     *
16026     * @param {Object} [options]
16027     *        The key/value store of player options.
16028     *
16029     * @listens Player#enterpictureinpicture
16030     * @listens Player#leavepictureinpicture
16031     */
16032    function PictureInPictureToggle(player, options) {
16033      var _this;
16034
16035      _this = _Button.call(this, player, options) || this;
16036
16037      _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); // TODO: Activate button on player loadedmetadata event.
16038      // TODO: Deactivate button on player emptied event.
16039      // TODO: Deactivate button if disablepictureinpicture attribute is present.
16040
16041
16042      if (!document.pictureInPictureEnabled) {
16043        _this.disable();
16044      }
16045
16046      return _this;
16047    }
16048    /**
16049     * Builds the default DOM `className`.
16050     *
16051     * @return {string}
16052     *         The DOM `className` for this object.
16053     */
16054
16055
16056    var _proto = PictureInPictureToggle.prototype;
16057
16058    _proto.buildCSSClass = function buildCSSClass() {
16059      return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this);
16060    }
16061    /**
16062     * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
16063     *
16064     * @param {EventTarget~Event} [event]
16065     *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
16066     *        called.
16067     *
16068     * @listens Player#enterpictureinpicture
16069     * @listens Player#leavepictureinpicture
16070     */
16071    ;
16072
16073    _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) {
16074      if (this.player_.isInPictureInPicture()) {
16075        this.controlText('Exit Picture-in-Picture');
16076      } else {
16077        this.controlText('Picture-in-Picture');
16078      }
16079    }
16080    /**
16081     * This gets called when an `PictureInPictureToggle` is "clicked". See
16082     * {@link ClickableComponent} for more detailed information on what a click can be.
16083     *
16084     * @param {EventTarget~Event} [event]
16085     *        The `keydown`, `tap`, or `click` event that caused this function to be
16086     *        called.
16087     *
16088     * @listens tap
16089     * @listens click
16090     */
16091    ;
16092
16093    _proto.handleClick = function handleClick(event) {
16094      if (!this.player_.isInPictureInPicture()) {
16095        this.player_.requestPictureInPicture();
16096      } else {
16097        this.player_.exitPictureInPicture();
16098      }
16099    };
16100
16101    return PictureInPictureToggle;
16102  }(Button);
16103  /**
16104   * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
16105   *
16106   * @type {string}
16107   * @private
16108   */
16109
16110
16111  PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
16112  Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
16113
16114  /**
16115   * Toggle fullscreen video
16116   *
16117   * @extends Button
16118   */
16119
16120  var FullscreenToggle =
16121  /*#__PURE__*/
16122  function (_Button) {
16123    inheritsLoose(FullscreenToggle, _Button);
16124
16125    /**
16126     * Creates an instance of this class.
16127     *
16128     * @param {Player} player
16129     *        The `Player` that this class should be attached to.
16130     *
16131     * @param {Object} [options]
16132     *        The key/value store of player options.
16133     */
16134    function FullscreenToggle(player, options) {
16135      var _this;
16136
16137      _this = _Button.call(this, player, options) || this;
16138
16139      _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
16140
16141      if (document[player.fsApi_.fullscreenEnabled] === false) {
16142        _this.disable();
16143      }
16144
16145      return _this;
16146    }
16147    /**
16148     * Builds the default DOM `className`.
16149     *
16150     * @return {string}
16151     *         The DOM `className` for this object.
16152     */
16153
16154
16155    var _proto = FullscreenToggle.prototype;
16156
16157    _proto.buildCSSClass = function buildCSSClass() {
16158      return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
16159    }
16160    /**
16161     * Handles fullscreenchange on the player and change control text accordingly.
16162     *
16163     * @param {EventTarget~Event} [event]
16164     *        The {@link Player#fullscreenchange} event that caused this function to be
16165     *        called.
16166     *
16167     * @listens Player#fullscreenchange
16168     */
16169    ;
16170
16171    _proto.handleFullscreenChange = function handleFullscreenChange(event) {
16172      if (this.player_.isFullscreen()) {
16173        this.controlText('Non-Fullscreen');
16174      } else {
16175        this.controlText('Fullscreen');
16176      }
16177    }
16178    /**
16179     * This gets called when an `FullscreenToggle` is "clicked". See
16180     * {@link ClickableComponent} for more detailed information on what a click can be.
16181     *
16182     * @param {EventTarget~Event} [event]
16183     *        The `keydown`, `tap`, or `click` event that caused this function to be
16184     *        called.
16185     *
16186     * @listens tap
16187     * @listens click
16188     */
16189    ;
16190
16191    _proto.handleClick = function handleClick(event) {
16192      if (!this.player_.isFullscreen()) {
16193        this.player_.requestFullscreen();
16194      } else {
16195        this.player_.exitFullscreen();
16196      }
16197    };
16198
16199    return FullscreenToggle;
16200  }(Button);
16201  /**
16202   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
16203   *
16204   * @type {string}
16205   * @private
16206   */
16207
16208
16209  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
16210  Component.registerComponent('FullscreenToggle', FullscreenToggle);
16211
16212  /**
16213   * Check if volume control is supported and if it isn't hide the
16214   * `Component` that was passed  using the `vjs-hidden` class.
16215   *
16216   * @param {Component} self
16217   *        The component that should be hidden if volume is unsupported
16218   *
16219   * @param {Player} player
16220   *        A reference to the player
16221   *
16222   * @private
16223   */
16224  var checkVolumeSupport = function checkVolumeSupport(self, player) {
16225    // hide volume controls when they're not supported by the current tech
16226    if (player.tech_ && !player.tech_.featuresVolumeControl) {
16227      self.addClass('vjs-hidden');
16228    }
16229
16230    self.on(player, 'loadstart', function () {
16231      if (!player.tech_.featuresVolumeControl) {
16232        self.addClass('vjs-hidden');
16233      } else {
16234        self.removeClass('vjs-hidden');
16235      }
16236    });
vendor: 5,375 bytes, lines 16237-16451
16237  };
16238
16239  /**
16240   * Shows volume level
16241   *
16242   * @extends Component
16243   */
16244
16245  var VolumeLevel =
16246  /*#__PURE__*/
16247  function (_Component) {
16248    inheritsLoose(VolumeLevel, _Component);
16249
16250    function VolumeLevel() {
16251      return _Component.apply(this, arguments) || this;
16252    }
16253
16254    var _proto = VolumeLevel.prototype;
16255
16256    /**
16257     * Create the `Component`'s DOM element
16258     *
16259     * @return {Element}
16260     *         The element that was created.
16261     */
16262    _proto.createEl = function createEl() {
16263      return _Component.prototype.createEl.call(this, 'div', {
16264        className: 'vjs-volume-level',
16265        innerHTML: '<span class="vjs-control-text"></span>'
16266      });
16267    };
16268
16269    return VolumeLevel;
16270  }(Component);
16271
16272  Component.registerComponent('VolumeLevel', VolumeLevel);
16273
16274  /**
16275   * The bar that contains the volume level and can be clicked on to adjust the level
16276   *
16277   * @extends Slider
16278   */
16279
16280  var VolumeBar =
16281  /*#__PURE__*/
16282  function (_Slider) {
16283    inheritsLoose(VolumeBar, _Slider);
16284
16285    /**
16286     * Creates an instance of this class.
16287     *
16288     * @param {Player} player
16289     *        The `Player` that this class should be attached to.
16290     *
16291     * @param {Object} [options]
16292     *        The key/value store of player options.
16293     */
16294    function VolumeBar(player, options) {
16295      var _this;
16296
16297      _this = _Slider.call(this, player, options) || this;
16298
16299      _this.on('slideractive', _this.updateLastVolume_);
16300
16301      _this.on(player, 'volumechange', _this.updateARIAAttributes);
16302
16303      player.ready(function () {
16304        return _this.updateARIAAttributes();
16305      });
16306      return _this;
16307    }
16308    /**
16309     * Create the `Component`'s DOM element
16310     *
16311     * @return {Element}
16312     *         The element that was created.
16313     */
16314
16315
16316    var _proto = VolumeBar.prototype;
16317
16318    _proto.createEl = function createEl() {
16319      return _Slider.prototype.createEl.call(this, 'div', {
16320        className: 'vjs-volume-bar vjs-slider-bar'
16321      }, {
16322        'aria-label': this.localize('Volume Level'),
16323        'aria-live': 'polite'
16324      });
16325    }
16326    /**
16327     * Handle mouse down on volume bar
16328     *
16329     * @param {EventTarget~Event} event
16330     *        The `mousedown` event that caused this to run.
16331     *
16332     * @listens mousedown
16333     */
16334    ;
16335
16336    _proto.handleMouseDown = function handleMouseDown(event) {
16337      if (!isSingleLeftClick(event)) {
16338        return;
16339      }
16340
16341      _Slider.prototype.handleMouseDown.call(this, event);
16342    }
16343    /**
16344     * Handle movement events on the {@link VolumeMenuButton}.
16345     *
16346     * @param {EventTarget~Event} event
16347     *        The event that caused this function to run.
16348     *
16349     * @listens mousemove
16350     */
16351    ;
16352
16353    _proto.handleMouseMove = function handleMouseMove(event) {
16354      if (!isSingleLeftClick(event)) {
16355        return;
16356      }
16357
16358      this.checkMuted();
16359      this.player_.volume(this.calculateDistance(event));
16360    }
16361    /**
16362     * If the player is muted unmute it.
16363     */
16364    ;
16365
16366    _proto.checkMuted = function checkMuted() {
16367      if (this.player_.muted()) {
16368        this.player_.muted(false);
16369      }
16370    }
16371    /**
16372     * Get percent of volume level
16373     *
16374     * @return {number}
16375     *         Volume level percent as a decimal number.
16376     */
16377    ;
16378
16379    _proto.getPercent = function getPercent() {
16380      if (this.player_.muted()) {
16381        return 0;
16382      }
16383
16384      return this.player_.volume();
16385    }
16386    /**
16387     * Increase volume level for keyboard users
16388     */
16389    ;
16390
16391    _proto.stepForward = function stepForward() {
16392      this.checkMuted();
16393      this.player_.volume(this.player_.volume() + 0.1);
16394    }
16395    /**
16396     * Decrease volume level for keyboard users
16397     */
16398    ;
16399
16400    _proto.stepBack = function stepBack() {
16401      this.checkMuted();
16402      this.player_.volume(this.player_.volume() - 0.1);
16403    }
16404    /**
16405     * Update ARIA accessibility attributes
16406     *
16407     * @param {EventTarget~Event} [event]
16408     *        The `volumechange` event that caused this function to run.
16409     *
16410     * @listens Player#volumechange
16411     */
16412    ;
16413
16414    _proto.updateARIAAttributes = function updateARIAAttributes(event) {
16415      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
16416      this.el_.setAttribute('aria-valuenow', ariaValue);
16417      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
16418    }
16419    /**
16420     * Returns the current value of the player volume as a percentage
16421     *
16422     * @private
16423     */
16424    ;
16425
16426    _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
16427      return Math.round(this.player_.volume() * 100);
16428    }
16429    /**
16430     * When user starts dragging the VolumeBar, store the volume and listen for
16431     * the end of the drag. When the drag ends, if the volume was set to zero,
16432     * set lastVolume to the stored volume.
16433     *
16434     * @listens slideractive
16435     * @private
16436     */
16437    ;
16438
16439    _proto.updateLastVolume_ = function updateLastVolume_() {
16440      var _this2 = this;
16441
16442      var volumeBeforeDrag = this.player_.volume();
16443      this.one('sliderinactive', function () {
16444        if (_this2.player_.volume() === 0) {
16445          _this2.player_.lastVolume_(volumeBeforeDrag);
16446        }
16447      });
16448    };
16449
16450    return VolumeBar;
16451  }
16451(Slider);
16452  /**
16453   * Default options for the `VolumeBar`
16454   *
16455   * @type {Object}
16456   * @private
16457   */
16458
16459
16460  VolumeBar.prototype.options_ = {
16461    children: ['volumeLevel'],
16462    barName: 'volumeLevel'
16463  };
16464  /**
16465   * Call the update event for this Slider when this event happens on the player.
16466   *
16467   * @type {string}
16468   */
16469
16470  VolumeBar.prototype.playerEvent = 'volumechange';
16471  Component.registerComponent('VolumeBar', VolumeBar);
16472
16473  /**
16474   * The component for controlling the volume level
16475   *
16476   * @extends Component
16477   */
16478
16479  var VolumeControl =
16480  /*#__PURE__*/
16481  function (_Component) {
16482    inheritsLoose(VolumeControl, _Component);
16483
16484    /**
16485     * Creates an instance of this class.
16486     *
16487     * @param {Player} player
16488     *        The `Player` that this class should be attached to.
16489     *
16490     * @param {Object} [options={}]
16491     *        The key/value store of player options.
16492     */
16493    function VolumeControl(player, options) {
16494      var _this;
16495
16496      if (options === void 0) {
16497        options = {};
16498      }
16499
16500      options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
16501      // the VolumeBar is turned on.
16502
16503      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
16504        options.volumeBar = options.volumeBar || {};
16505        options.volumeBar.vertical = options.vertical;
16506      }
16507
16508      _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
16509
16510      checkVolumeSupport(assertThisInitialized(_this), player);
16511      _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
16512
16513      _this.on('mousedown', _this.handleMouseDown);
16514
16515      _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
16516      // is dragging) or in focus we do not want to hide the VolumeBar
16517
16518
16519      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
16520        _this.volumeBar.addClass('vjs-slider-active');
16521
16522        _this.addClass('vjs-slider-active');
16523
16524        _this.trigger('slideractive');
16525      });
16526
16527      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
16528        _this.volumeBar.removeClass('vjs-slider-active');
16529
16530        _this.removeClass('vjs-slider-active');
16531
16532        _this.trigger('sliderinactive');
16533      });
16534
16535      return _this;
16536    }
16537    /**
16538     * Create the `Component`'s DOM element
16539     *
16540     * @return {Element}
16541     *         The element that was created.
16542     */
16543
16544
16545    var _proto = VolumeControl.prototype;
16546
16547    _proto.createEl = function createEl() {
16548      var orientationClass = 'vjs-volume-horizontal';
16549
16550      if (this.options_.vertical) {
16551        orientationClass = 'vjs-volume-vertical';
16552      }
16553
16554      return _Component.prototype.createEl.call(this, 'div', {
16555        className: "vjs-volume-control vjs-control " + orientationClass
16556      });
16557    }
16558    /**
16559     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16560     *
16561     * @param {EventTarget~Event} event
16562     *        `mousedown` or `touchstart` event that triggered this function
16563     *
16564     * @listens mousedown
16565     * @listens touchstart
16566     */
16567    ;
16568
16569    _proto.handleMouseDown = function handleMouseDown(event) {
16570      var doc = this.el_.ownerDocument;
16571      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
16572      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
16573      this.on(doc, 'mouseup', this.handleMouseUp);
16574      this.on(doc, 'touchend', this.handleMouseUp);
16575    }
16576    /**
16577     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
16578     *
16579     * @param {EventTarget~Event} event
16580     *        `mouseup` or `touchend` event that triggered this function.
16581     *
16582     * @listens touchend
16583     * @listens mouseup
16584     */
16585    ;
16586
16587    _proto.handleMouseUp = function handleMouseUp(event) {
16588      var doc = this.el_.ownerDocument;
16589      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
16590      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
16591      this.off(doc, 'mouseup', this.handleMouseUp);
16592      this.off(doc, 'touchend', this.handleMouseUp);
16593    }
16594    /**
16595     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16596     *
16597     * @param {EventTarget~Event} event
16598     *        `mousedown` or `touchstart` event that triggered this function
16599     *
16600     * @listens mousedown
16601     * @listens touchstart
16602     */
16603    ;
16604
16605    _proto.handleMouseMove = function handleMouseMove(event) {
16606      this.volumeBar.handleMouseMove(event);
16607    };
16608
16609    return VolumeControl;
16610  }(Component);
16611  /**
16612   * Default options for the `VolumeControl`
16613   *
16614   * @type {Object}
16615   * @private
16616   */
16617
16618
16619  VolumeControl.prototype.options_ = {
16620    children: ['volumeBar']
16621  };
16622  Component.registerComponent('VolumeControl', VolumeControl);
16623
16624  /**
16625   * Check if muting volume is supported and if it isn't hide the mute toggle
16626   * button.
16627   *
16628   * @param {Component} self
16629   *        A reference to the mute toggle button
16630   *
16631   * @param {Player} player
16632   *        A reference to the player
16633   *
16634   * @private
16635   */
16636  var checkMuteSupport = function checkMuteSupport(self, player) {
16637    // hide mute toggle button if it's not supported by the current tech
16638    if (player.tech_ && !player.tech_.featuresMuteControl) {
16639      self.addClass('vjs-hidden');
16640    }
16641
16642    self.on(player, 'loadstart', function () {
16643      if (!player.tech_.featuresMuteControl) {
16644        self.addClass('vjs-hidden');
16645      } else {
16646        self.removeClass('vjs-hidden');
16647      }
16648    });
16649  };
16650
16651  /**
16652   * A button component for muting the audio.
16653   *
16654   * @extends Button
16655   */
16656
16657  var MuteToggle =
16658  /*#__PURE__*/
16659  function (_Button) {
16660    inheritsLoose(MuteToggle, _Button);
16661
16662    /**
16663     * Creates an instance of this class.
16664     *
16665     * @param {Player} player
16666     *        The `Player` that this class should be attached to.
16667     *
16668     * @param {Object} [options]
16669     *        The key/value store of player options.
16670     */
16671    function MuteToggle(player, options) {
16672      var _this;
16673
16674      _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
16675
16676      checkMuteSupport(assertThisInitialized(_this), player);
16677
16678      _this.on(player, ['loadstart', 'volumechange'], _this.update);
16679
16680      return _this;
16681    }
16682    /**
16683     * Builds the default DOM `className`.
16684     *
16685     * @return {string}
16686     *         The DOM `className` for this object.
16687     */
16688
16689
16690    var _proto = MuteToggle.prototype;
16691
16692    _proto.buildCSSClass = function buildCSSClass() {
16693      return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
16694    }
16695    /**
16696     * This gets called when an `MuteToggle` is "clicked". See
16697     * {@link ClickableComponent} for more detailed information on what a click can be.
16698     *
16699     * @param {EventTarget~Event} [event]
16700     *        The `keydown`, `tap`, or `click` event that caused this function to be
16701     *        called.
16702     *
16703     * @listens tap
16704     * @listens click
16705     */
16706    ;
16707
16708    _proto.handleClick = function handleClick(event) {
16709      var vol = this.player_.volume();
16710      var lastVolume = this.player_.lastVolume_();
16711
16712      if (vol === 0) {
16713        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
16714        this.player_.volume(volumeToSet);
16715        this.player_.muted(false);
16716      } else {
16717        this.player_.muted(this.player_.muted() ? false : true);
16718      }
16719    }
16720    /**
16721     * Update the `MuteToggle` button based on the state of `volume` and `muted`
16722     * on the player.
16723     *
16724     * @param {EventTarget~Event} [event]
16725     *        The {@link Player#loadstart} event if this function was called
16726     *        through an event.
16727     *
16728     * @listens Player#loadstart
16729     * @listens Player#volumechange
16730     */
16731    ;
16732
16733    _proto.update = function update(event) {
16734      this.updateIcon_();
16735      this.updateControlText_();
16736    }
16737    /**
16738     * Update the appearance of the `MuteToggle` icon.
16739     *
16740     * Possible states (given `level` variable below):
16741     * - 0: crossed out
16742     * - 1: zero bars of volume
16743     * - 2: one bar of volume
16744     * - 3: two bars of volume
16745     *
16746     * @private
16747     */
16748    ;
16749
16750    _proto.updateIcon_ = function updateIcon_() {
16751      var vol = this.player_.volume();
16752      var level = 3; // in iOS when a player is loaded with muted attribute
16753      // and volume is changed with a native mute button
16754      // we want to make sure muted state is updated
16755
16756      if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
16757        this.player_.muted(this.player_.tech_.el_.muted);
16758      }
16759
16760      if (vol === 0 || this.player_.muted()) {
16761        level = 0;
16762      } else if (vol < 0.33) {
16763        level = 1;
16764      } else if (vol < 0.67) {
16765        level = 2;
16766      } // TODO improve muted icon classes
16767
16768
16769      for (var i = 0; i < 4; i++) {
16770        removeClass(this.el_, "vjs-vol-" + i);
16771      }
16772
16773      addClass(this.el_, "vjs-vol-" + level);
16774    }
16775    /**
16776     * If `muted` has changed on the player, update the control text
16777     * (`title` attribute on `vjs-mute-control` element and content of
16778     * `vjs-control-text` element).
16779     *
16780     * @private
16781     */
16782    ;
16783
16784    _proto.updateControlText_ = function updateControlText_() {
16785      var soundOff = this.player_.muted() || this.player_.volume() === 0;
16786      var text = soundOff ? 'Unmute' : 'Mute';
16787
16788      if (this.controlText() !== text) {
16789        this.controlText(text);
16790      }
16791    };
16792
16793    return MuteToggle;
16794  }(Button);
16795  /**
16796   * The text that should display over the `MuteToggle`s controls. Added for localization.
16797   *
16798   * @type {string}
16799   * @private
16800   */
16801
16802
16803  MuteToggle.prototype.controlText_ = 'Mute';
16804  Component.registerComponent('MuteToggle', MuteToggle);
16805
16806  /**
16807   * A Component to contain the MuteToggle and VolumeControl so that
16808   * they can work together.
16809   *
16810   * @extends Component
16811   */
16812
16813  var VolumePanel =
16814  /*#__PURE__*/
16815  function (_Component) {
16816    inheritsLoose(VolumePanel, _Component);
16817
16818    /**
16819     * Creates an instance of this class.
16820     *
16821     * @param {Player} player
16822     *        The `Player` that this class should be attached to.
16823     *
16824     * @param {Object} [options={}]
16825     *        The key/value store of player options.
16826     */
16827    function VolumePanel(player, options) {
16828      var _this;
16829
16830      if (options === void 0) {
16831        options = {};
16832      }
16833
16834      if (typeof options.inline !== 'undefined') {
16835        options.inline = options.inline;
16836      } else {
16837        options.inline = true;
16838      } // pass the inline option down to the VolumeControl as vertical if
16839      // the VolumeControl is on.
16840
16841
16842      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
16843        options.volumeControl = options.volumeControl || {};
16844        options.volumeControl.vertical = !options.inline;
16845      }
16846
16847      _this = _Component.call(this, player, options) || this;
16848
16849      _this.on(player, ['loadstart'], _this.volumePanelState_);
16850
16851      _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress);
16852
16853      _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp);
16854
16855      _this.on('keydown', _this.handleKeyPress);
16856
16857      _this.on('mouseover', _this.handleMouseOver);
16858
16859      _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and
16860      // is dragging) we do not want to hide the VolumeBar
16861
16862
16863      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
16864
16865      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
16866
16867      return _this;
16868    }
16869    /**
16870     * Add vjs-slider-active class to the VolumePanel
16871     *
16872     * @listens VolumeControl#slideractive
16873     * @private
16874     */
16875
16876
16877    var _proto = VolumePanel.prototype;
16878
16879    _proto.sliderActive_ = function sliderActive_() {
16880      this.addClass('vjs-slider-active');
16881    }
16882    /**
16883     * Removes vjs-slider-active class to the VolumePanel
16884     *
16885     * @listens VolumeControl#sliderinactive
16886     * @private
16887     */
16888    ;
16889
16890    _proto.sliderInactive_ = function sliderInactive_() {
16891      this.removeClass('vjs-slider-active');
16892    }
16893    /**
16894     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
16895     * depending on MuteToggle and VolumeControl state
16896     *
16897     * @listens Player#loadstart
16898     * @private
16899     */
16900    ;
16901
16902    _proto.volumePanelState_ = function volumePanelState_() {
16903      // hide volume panel if neither volume control or mute toggle
16904      // are displayed
16905      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
16906        this.addClass('vjs-hidden');
16907      } // if only mute toggle is visible we don't want
16908      // volume panel expanding when hovered or active
16909
16910
16911      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
16912        this.addClass('vjs-mute-toggle-only');
16913      }
16914    }
16915    /**
16916     * Create the `Component`'s DOM element
16917     *
16918     * @return {Element}
16919     *         The element that was created.
16920     */
16921    ;
16922
16923    _proto.createEl = function createEl() {
16924      var orientationClass = 'vjs-volume-panel-horizontal';
16925
16926      if (!this.options_.inline) {
16927        orientationClass = 'vjs-volume-panel-vertical';
16928      }
16929
16930      return _Component.prototype.createEl.call(this, 'div', {
16931        className: "vjs-volume-panel vjs-control " + orientationClass
16932      });
16933    }
16934    /**
16935     * Dispose of the `volume-panel` and all child components.
16936     */
16937    ;
16938
16939    _proto.dispose = function dispose() {
16940      this.handleMouseOut();
16941
16942      _Component.prototype.dispose.call(this);
16943    }
16944    /**
16945     * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
16946     * the volume panel and sets focus on `MuteToggle`.
16947     *
16948     * @param {EventTarget~Event} event
16949     *        The `keyup` event that caused this function to be called.
16950     *
16951     * @listens keyup
16952     */
16953    ;
16954
16955    _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) {
16956      if (keycode.isEventKey(event, 'Esc')) {
16957        this.muteToggle.focus();
16958      }
16959    }
16960    /**
16961     * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
16962     * Turns on listening for `mouseover` event. When they happen it
16963     * calls `this.handleMouseOver`.
16964     *
16965     * @param {EventTarget~Event} event
16966     *        The `mouseover` event that caused this function to be called.
16967     *
16968     * @listens mouseover
16969     */
16970    ;
16971
16972    _proto.handleMouseOver = function handleMouseOver(event) {
16973      this.addClass('vjs-hover');
16974      on(document, 'keyup', bind(this, this.handleKeyPress));
16975    }
16976    /**
16977     * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
16978     * Turns on listening for `mouseout` event. When they happen it
16979     * calls `this.handleMouseOut`.
16980     *
16981     * @param {EventTarget~Event} event
16982     *        The `mouseout` event that caused this function to be called.
16983     *
16984     * @listens mouseout
16985     */
16986    ;
16987
16988    _proto.handleMouseOut = function handleMouseOut(event) {
16989      this.removeClass('vjs-hover');
16990      off(document, 'keyup', bind(this, this.handleKeyPress));
16991    }
16992    /**
16993     * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
16994     * looking for ESC, which hides the `VolumeControl`.
16995     *
16996     * @param {EventTarget~Event} event
16997     *        The keypress that triggered this event.
16998     *
16999     * @listens keydown | keyup
17000     */
17001    ;
17002
17003    _proto.handleKeyPress = function handleKeyPress(event) {
17004      if (keycode.isEventKey(event, 'Esc')) {
17005        this.handleMouseOut();
17006      }
17007    };
17008
17009    return VolumePanel;
17010  }(Component);
17011  /**
17012   * Default options for the `VolumeControl`
17013   *
17014   * @type {Object}
17015   * @private
17016   */
17017
17018
17019  VolumePanel.prototype.options_ = {
17020    children: ['muteToggle', 'volumeControl']
17021  };
17022  Component.registerComponent('VolumePanel', VolumePanel);
17023
17024  /**
17025   * The Menu component is used to build popup menus, including subtitle and
17026   * captions selection menus.
17027   *
17028   * @extends Component
17029   */
17030
17031  var Menu =
17032  /*#__PURE__*/
17033  function (_Component) {
17034    inheritsLoose(Menu, _Component);
17035
17036    /**
17037     * Create an instance of this class.
17038     *
17039     * @param {Player} player
17040     *        the player that this component should attach to
17041     *
17042     * @param {Object} [options]
17043     *        Object of option names and values
17044     *
17045     */
17046    function Menu(player, options) {
17047      var _this;
17048
17049      _this = _Component.call(this, player, options) || this;
17050
17051      if (options) {
17052        _this.menuButton_ = options.menuButton;
17053      }
17054
17055      _this.focusedChild_ = -1;
17056
17057      _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container.
17058
17059
17060      _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur);
17061      _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick);
17062      return _this;
17063    }
17064    /**
17065     * Add event listeners to the {@link MenuItem}.
17066     *
17067     * @param {Object} component
17068     *        The instance of the `MenuItem` to add listeners to.
17069     *
17070     */
17071
17072
17073    var _proto = Menu.prototype;
17074
17075    _proto.addEventListenerForItem = function addEventListenerForItem(component) {
17076      if (!(component instanceof Component)) {
17077        return;
17078      }
17079
17080      this.on(component, 'blur', this.boundHandleBlur_);
17081      this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
17082    }
17083    /**
17084     * Remove event listeners from the {@link MenuItem}.
17085     *
17086     * @param {Object} component
17087     *        The instance of the `MenuItem` to remove listeners.
17088     *
17089     */
17090    ;
17091
17092    _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
17093      if (!(component instanceof Component)) {
17094        return;
17095      }
17096
17097      this.off(component, 'blur', this.boundHandleBlur_);
17098      this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
17099    }
17100    /**
17101     * This method will be called indirectly when the component has been added
17102     * before the component adds to the new menu instance by `addItem`.
17103     * In this case, the original menu instance will remove the component
17104     * by calling `removeChild`.
17105     *
17106     * @param {Object} component
17107     *        The instance of the `MenuItem`
17108     */
17109    ;
17110
17111    _proto.removeChild = function removeChild(component) {
17112      if (typeof component === 'string') {
17113        component = this.getChild(component);
17114      }
17115
17116      this.removeEventListenerForItem(component);
17117
17118      _Component.prototype.removeChild.call(this, component);
17119    }
17120    /**
17121     * Add a {@link MenuItem} to the menu.
17122     *
17123     * @param {Object|string} component
17124     *        The name or instance of the `MenuItem` to add.
17125     *
17126     */
17127    ;
17128
17129    _proto.addItem = function addItem(component) {
17130      var childComponent = this.addChild(component);
17131
17132      if (childComponent) {
17133        this.addEventListenerForItem(childComponent);
17134      }
17135    }
17136    /**
17137     * Create the `Menu`s DOM element.
17138     *
17139     * @return {Element}
17140     *         the element that was created
17141     */
17142    ;
17143
17144    _proto.createEl = function createEl$1() {
17145      var contentElType = this.options_.contentElType || 'ul';
17146      this.contentEl_ = createEl(contentElType, {
17147        className: 'vjs-menu-content'
17148      });
17149      this.contentEl_.setAttribute('role', 'menu');
17150
17151      var el = _Component.prototype.createEl.call(this, 'div', {
17152        append: this.contentEl_,
17153        className: 'vjs-menu'
17154      });
17155
17156      el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
17157      // where a click on the parent is significant
17158
17159      on(el, 'click', function (event) {
17160        event.preventDefault();
17161        event.stopImmediatePropagation();
17162      });
17163      return el;
17164    };
17165
17166    _proto.dispose = function dispose() {
17167      this.contentEl_ = null;
17168      this.boundHandleBlur_ = null;
17169      this.boundHandleTapClick_ = null;
17170
17171      _Component.prototype.dispose.call(this);
17172    }
17173    /**
17174     * Called when a `MenuItem` loses focus.
17175     *
17176     * @param {EventTarget~Event} event
17177     *        The `blur` event that caused this function to be called.
17178     *
17179     * @listens blur
17180     */
17181    ;
17182
17183    _proto.handleBlur = function handleBlur(event) {
17184      var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
17185
17186      if (!this.children().some(function (element) {
17187        return element.el() === relatedTarget;
17188      })) {
17189        var btn = this.menuButton_;
17190
17191        if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
17192          btn.unpressButton();
17193        }
17194      }
17195    }
17196    /**
17197     * Called when a `MenuItem` gets clicked or tapped.
17198     *
17199     * @param {EventTarget~Event} event
17200     *        The `click` or `tap` event that caused this function to be called.
17201     *
17202     * @listens click,tap
17203     */
17204    ;
17205
17206    _proto.handleTapClick = function handleTapClick(event) {
17207      // Unpress the associated MenuButton, and move focus back to it
17208      if (this.menuButton_) {
17209        this.menuButton_.unpressButton();
17210        var childComponents = this.children();
17211
17212        if (!Array.isArray(childComponents)) {
17213          return;
17214        }
17215
17216        var foundComponent = childComponents.filter(function (component) {
17217          return component.el() === event.target;
17218        })[0];
17219
17220        if (!foundComponent) {
17221          return;
17222        } // don't focus menu button if item is a caption settings item
17223        // because focus will move elsewhere
17224
17225
17226        if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
17227          this.menuButton_.focus();
17228        }
17229      }
17230    }
17231    /**
17232     * Handle a `keydown` event on this menu. This listener is added in the constructor.
17233     *
17234     * @param {EventTarget~Event} event
17235     *        A `keydown` event that happened on the menu.
17236     *
17237     * @listens keydown
17238     */
17239    ;
17240
17241    _proto.handleKeyDown = function handleKeyDown(event) {
17242      // Left and Down Arrows
17243      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
17244        event.preventDefault();
17245        event.stopPropagation();
17246        this.stepForward(); // Up and Right Arrows
17247      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
17248        event.preventDefault();
17249        event.stopPropagation();
17250        this.stepBack();
17251      }
17252    }
17253    /**
17254     * Move to next (lower) menu item for keyboard users.
17255     */
17256    ;
17257
17258    _proto.stepForward = function stepForward() {
17259      var stepChild = 0;
17260
17261      if (this.focusedChild_ !== undefined) {
17262        stepChild = this.focusedChild_ + 1;
17263      }
17264
17265      this.focus(stepChild);
17266    }
17267    /**
17268     * Move to previous (higher) menu item for keyboard users.
17269     */
17270    ;
17271
17272    _proto.stepBack = function stepBack() {
17273      var stepChild = 0;
17274
17275      if (this.focusedChild_ !== undefined) {
17276        stepChild = this.focusedChild_ - 1;
17277      }
17278
17279      this.focus(stepChild);
17280    }
17281    /**
17282     * Set focus on a {@link MenuItem} in the `Menu`.
17283     *
17284     * @param {Object|string} [item=0]
17285     *        Index of child item set focus on.
17286     */
17287    ;
17288
17289    _proto.focus = function focus(item) {
17290      if (item === void 0) {
17291        item = 0;
17292      }
17293
17294      var children = this.children().slice();
17295      var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
17296
17297      if (haveTitle) {
17298        children.shift();
17299      }
17300
17301      if (children.length > 0) {
17302        if (item < 0) {
17303          item = 0;
17304        } else if (item >= children.length) {
17305          item = children.length - 1;
17306        }
17307
17308        this.focusedChild_ = item;
17309        children[item].el_.focus();
17310      }
17311    };
17312
17313    return Menu;
17314  }(Component);
17315
17316  Component.registerComponent('Menu', Menu);
17317
17318  /**
17319   * A `MenuButton` class for any popup {@link Menu}.
17320   *
17321   * @extends Component
17322   */
17323
17324  var MenuButton =
17325  /*#__PURE__*/
17326  function (_Component) {
17327    inheritsLoose(MenuButton, _Component);
17328
17329    /**
17330     * Creates an instance of this class.
17331     *
17332     * @param {Player} player
17333     *        The `Player` that this class should be attached to.
17334     *
17335     * @param {Object} [options={}]
17336     *        The key/value store of player options.
17337     */
17338    function MenuButton(player, options) {
17339      var _this;
17340
17341      if (options === void 0) {
17342        options = {};
17343      }
17344
17345      _this = _Component.call(this, player, options) || this;
17346      _this.menuButton_ = new Button(player, options);
17347
17348      _this.menuButton_.controlText(_this.controlText_);
17349
17350      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
17351
17352
17353      var buttonClass = Button.prototype.buildCSSClass();
17354      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
17355
17356      _this.menuButton_.removeClass('vjs-control');
17357
17358      _this.addChild(_this.menuButton_);
17359
17360      _this.update();
17361
17362      _this.enabled_ = true;
17363
17364      _this.on(_this.menuButton_, 'tap', _this.handleClick);
17365
17366      _this.on(_this.menuButton_, 'click', _this.handleClick);
17367
17368      _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown);
17369
17370      _this.on(_this.menuButton_, 'mouseenter', function () {
17371        _this.addClass('vjs-hover');
17372
17373        _this.menu.show();
17374
17375        on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp));
17376      });
17377
17378      _this.on('mouseleave', _this.handleMouseLeave);
17379
17380      _this.on('keydown', _this.handleSubmenuKeyDown);
17381
17382      return _this;
17383    }
17384    /**
17385     * Update the menu based on the current state of its items.
17386     */
17387
17388
vendor: 4,553 bytes, lines 17389-17548
17389    var _proto = MenuButton.prototype;
17390
17391    _proto.update = function update() {
17392      var menu = this.createMenu();
17393
17394      if (this.menu) {
17395        this.menu.dispose();
17396        this.removeChild(this.menu);
17397      }
17398
17399      this.menu = menu;
17400      this.addChild(menu);
17401      /**
17402       * Track the state of the menu button
17403       *
17404       * @type {Boolean}
17405       * @private
17406       */
17407
17408      this.buttonPressed_ = false;
17409      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17410
17411      if (this.items && this.items.length <= this.hideThreshold_) {
17412        this.hide();
17413      } else {
17414        this.show();
17415      }
17416    }
17417    /**
17418     * Create the menu and add all items to it.
17419     *
17420     * @return {Menu}
17421     *         The constructed menu
17422     */
17423    ;
17424
17425    _proto.createMenu = function createMenu() {
17426      var menu = new Menu(this.player_, {
17427        menuButton: this
17428      });
17429      /**
17430       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
17431       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
17432       * it here because every time we run `createMenu` we need to reset the value.
17433       *
17434       * @protected
17435       * @type {Number}
17436       */
17437
17438      this.hideThreshold_ = 0; // Add a title list item to the top
17439
17440      if (this.options_.title) {
17441        var titleEl = createEl('li', {
17442          className: 'vjs-menu-title',
17443          innerHTML: toTitleCase(this.options_.title),
17444          tabIndex: -1
17445        });
17446        this.hideThreshold_ += 1;
17447        var titleComponent = new Component(this.player_, {
17448          el: titleEl
17449        });
17450        menu.addItem(titleComponent);
17451      }
17452
17453      this.items = this.createItems();
17454
17455      if (this.items) {
17456        // Add menu items to the menu
17457        for (var i = 0; i < this.items.length; i++) {
17458          menu.addItem(this.items[i]);
17459        }
17460      }
17461
17462      return menu;
17463    }
17464    /**
17465     * Create the list of menu items. Specific to each subclass.
17466     *
17467     * @abstract
17468     */
17469    ;
17470
17471    _proto.createItems = function createItems() {}
17472    /**
17473     * Create the `MenuButtons`s DOM element.
17474     *
17475     * @return {Element}
17476     *         The element that gets created.
17477     */
17478    ;
17479
17480    _proto.createEl = function createEl() {
17481      return _Component.prototype.createEl.call(this, 'div', {
17482        className: this.buildWrapperCSSClass()
17483      }, {});
17484    }
17485    /**
17486     * Allow sub components to stack CSS class names for the wrapper element
17487     *
17488     * @return {string}
17489     *         The constructed wrapper DOM `className`
17490     */
17491    ;
17492
17493    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
17494      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17495
17496      if (this.options_.inline === true) {
17497        menuButtonClass += '-inline';
17498      } else {
17499        menuButtonClass += '-popup';
17500      } // TODO: Fix the CSS so that this isn't necessary
17501
17502
17503      var buttonClass = Button.prototype.buildCSSClass();
17504      return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
17505    }
17506    /**
17507     * Builds the default DOM `className`.
17508     *
17509     * @return {string}
17510     *         The DOM `className` for this object.
17511     */
17512    ;
17513
17514    _proto.buildCSSClass = function buildCSSClass() {
17515      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17516
17517      if (this.options_.inline === true) {
17518        menuButtonClass += '-inline';
17519      } else {
17520        menuButtonClass += '-popup';
17521      }
17522
17523      return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
17524    }
17525    /**
17526     * Get or set the localized control text that will be used for accessibility.
17527     *
17528     * > NOTE: This will come from the internal `menuButton_` element.
17529     *
17530     * @param {string} [text]
17531     *        Control text for element.
17532     *
17533     * @param {Element} [el=this.menuButton_.el()]
17534     *        Element to set the title on.
17535     *
17536     * @return {string}
17537     *         - The control text when getting
17538     */
17539    ;
17540
17541    _proto.controlText = function controlText(text, el) {
17542      if (el === void 0) {
17543        el = this.menuButton_.el();
17544      }
17545
17546      return this.menuButton_.controlText(text, el);
17547    }
17548    /**
17549     * Dispose of the `menu-button` and all child components.
17550     */
17551    ;
17552
17553    _proto.dispose = function dispose() {
17554      this.handleMouseLeave();
17555
17556      _Component.prototype.dispose.call(this);
17557    }
17558    /**
17559     * Handle a click on a `MenuButton`.
17560     * See {@link ClickableComponent#handleClick} for instances where this is called.
17561     *
17562     * @param {EventTarget~Event} event
17563     *        The `keydown`, `tap`, or `click` event that caused this function to be
17564     *        called.
17565     *
17566     * @listens tap
17567     * @listens click
17568     */
17569    ;
17570
17571    _proto.handleClick = function handleClick(event) {
17572      if (this.buttonPressed_) {
17573        this.unpressButton();
17574      } else {
17575        this.pressButton();
17576      }
17577    }
17578    /**
17579     * Handle `mouseleave` for `MenuButton`.
17580     *
17581     * @param {EventTarget~Event} event
17582     *        The `mouseleave` event that caused this function to be called.
17583     *
17584     * @listens mouseleave
17585     */
17586    ;
17587
17588    _proto.handleMouseLeave = function handleMouseLeave(event) {
17589      this.removeClass('vjs-hover');
17590      off(document, 'keyup', bind(this, this.handleMenuKeyUp));
17591    }
17592    /**
17593     * Set the focus to the actual button, not to this element
17594     */
17595    ;
17596
17597    _proto.focus = function focus() {
17598      this.menuButton_.focus();
17599    }
17600    /**
17601     * Remove the focus from the actual button, not this element
17602     */
17603    ;
17604
17605    _proto.blur = function blur() {
17606      this.menuButton_.blur();
17607    }
17608    /**
17609     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
17610     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17611     *
17612     * @param {EventTarget~Event} event
17613     *        The `keydown` event that caused this function to be called.
17614     *
17615     * @listens keydown
17616     */
17617    ;
17618
17619    _proto.handleKeyDown = function handleKeyDown(event) {
17620      // Escape or Tab unpress the 'button'
17621      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17622        if (this.buttonPressed_) {
17623          this.unpressButton();
17624        } // Don't preventDefault for Tab key - we still want to lose focus
17625
17626
17627        if (!keycode.isEventKey(event, 'Tab')) {
17628          event.preventDefault(); // Set focus back to the menu button's button
17629
17630          this.menuButton_.focus();
17631        } // Up Arrow or Down Arrow also 'press' the button to open the menu
17632
17633      } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
17634        if (!this.buttonPressed_) {
17635          event.preventDefault();
17636          this.pressButton();
17637        }
17638      }
17639    }
17640    /**
17641     * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
17642     * the constructor.
17643     *
17644     * @param {EventTarget~Event} event
17645     *        Key press event
17646     *
17647     * @listens keyup
17648     */
17649    ;
17650
17651    _proto.handleMenuKeyUp = function handleMenuKeyUp(event) {
17652      // Escape hides popup menu
17653      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17654        this.removeClass('vjs-hover');
17655      }
17656    }
17657    /**
17658     * This method name now delegates to `handleSubmenuKeyDown`. This means
17659     * anyone calling `handleSubmenuKeyPress` will not see their method calls
17660     * stop working.
17661     *
17662     * @param {EventTarget~Event} event
17663     *        The event that caused this function to be called.
17664     */
17665    ;
17666
17667    _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
17668      this.handleSubmenuKeyDown(event);
17669    }
17670    /**
17671     * Handle a `keydown` event on a sub-menu. The listener for this is added in
17672     * the constructor.
17673     *
17674     * @param {EventTarget~Event} event
17675     *        Key press event
17676     *
17677     * @listens keydown
17678     */
17679    ;
17680
17681    _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
17682      // Escape or Tab unpress the 'button'
17683      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17684        if (this.buttonPressed_) {
17685          this.unpressButton();
17686        } // Don't preventDefault for Tab key - we still want to lose focus
17687
17688
17689        if (!keycode.isEventKey(event, 'Tab')) {
17690          event.preventDefault(); // Set focus back to the menu button's button
17691
17692          this.menuButton_.focus();
17693        }
17694      }
17695    }
17696    /**
17697     * Put the current `MenuButton` into a pressed state.
17698     */
17699    ;
17700
17701    _proto.pressButton = function pressButton() {
17702      if (this.enabled_) {
17703        this.buttonPressed_ = true;
17704        this.menu.show();
17705        this.menu.lockShowing();
17706        this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
17707        // undesired scrolling behavior when the player is in an iframe
17708
17709        if (IS_IOS && isInFrame()) {
17710          // Return early so that the menu isn't focused
17711          return;
17712        }
17713
17714        this.menu.focus();
17715      }
17716    }
17717    /**
17718     * Take the current `MenuButton` out of a pressed state.
17719     */
17720    ;
17721
17722    _proto.unpressButton = function unpressButton() {
17723      if (this.enabled_) {
17724        this.buttonPressed_ = false;
17725        this.menu.unlockShowing();
17726        this.menu.hide();
17727        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17728      }
17729    }
17730    /**
17731     * Disable the `MenuButton`. Don't allow it to be clicked.
17732     */
17733    ;
17734
17735    _proto.disable = function disable() {
17736      this.unpressButton();
17737      this.enabled_ = false;
17738      this.addClass('vjs-disabled');
17739      this.menuButton_.disable();
17740    }
17741    /**
17742     * Enable the `MenuButton`. Allow it to be clicked.
17743     */
17744    ;
17745
17746    _proto.enable = function enable() {
17747      this.enabled_ = true;
17748      this.removeClass('vjs-disabled');
17749      this.menuButton_.enable();
17750    };
17751
17752    return MenuButton;
17753  }(Component);
17754
17755  Component.registerComponent('MenuButton', MenuButton);
17756
17757  /**
17758   * The base class for buttons that toggle specific  track types (e.g. subtitles).
17759   *
17760   * @extends MenuButton
17761   */
17762
17763  var TrackButton =
17764  /*#__PURE__*/
17765  function (_MenuButton) {
17766    inheritsLoose(TrackButton, _MenuButton);
17767
17768    /**
17769     * Creates an instance of this class.
17770     *
17771     * @param {Player} player
17772     *        The `Player` that this class should be attached to.
17773     *
17774     * @param {Object} [options]
17775     *        The key/value store of player options.
17776     */
17777    function TrackButton(player, options) {
17778      var _this;
17779
17780      var tracks = options.tracks;
17781      _this = _MenuButton.call(this, player, options) || this;
17782
17783      if (_this.items.length <= 1) {
17784        _this.hide();
17785      }
17786
17787      if (!tracks) {
17788        return assertThisInitialized(_this);
17789      }
17790
17791      var updateHandler = bind(assertThisInitialized(_this), _this.update);
17792      tracks.addEventListener('removetrack', updateHandler);
17793      tracks.addEventListener('addtrack', updateHandler);
17794
17795      _this.player_.on('ready', updateHandler);
17796
17797      _this.player_.on('dispose', function () {
17798        tracks.removeEventListener('removetrack', updateHandler);
17799        tracks.removeEventListener('addtrack', updateHandler);
17800      });
17801
17802      return _this;
17803    }
17804
17805    return TrackButton;
17806  }(MenuButton);
17807
17808  Component.registerComponent('TrackButton', TrackButton);
17809
17810  /**
17811   * @file menu-keys.js
17812   */
17813
17814  /**
17815    * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
17816    * Note that 'Enter' and 'Space' are not included here (otherwise they would
17817    * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
17818    * @typedef MenuKeys
17819    * @array
17820    */
17821  var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
17822
17823  /**
17824   * The component for a menu item. `<li>`
17825   *
17826   * @extends ClickableComponent
17827   */
17828
17829  var MenuItem =
17830  /*#__PURE__*/
17831  function (_ClickableComponent) {
17832    inheritsLoose(MenuItem, _ClickableComponent);
17833
17834    /**
17835     * Creates an instance of the this class.
17836     *
17837     * @param {Player} player
17838     *        The `Player` that this class should be attached to.
17839     *
17840     * @param {Object} [options={}]
17841     *        The key/value store of player options.
17842     *
17843     */
17844    function MenuItem(player, options) {
17845      var _this;
17846
17847      _this = _ClickableComponent.call(this, player, options) || this;
17848      _this.selectable = options.selectable;
17849      _this.isSelected_ = options.selected || false;
17850      _this.multiSelectable = options.multiSelectable;
17851
17852      _this.selected(_this.isSelected_);
17853
17854      if (_this.selectable) {
17855        if (_this.multiSelectable) {
17856          _this.el_.setAttribute('role', 'menuitemcheckbox');
17857        } else {
17858          _this.el_.setAttribute('role', 'menuitemradio');
17859        }
17860      } else {
17861        _this.el_.setAttribute('role', 'menuitem');
17862      }
17863
17864      return _this;
17865    }
17866    /**
17867     * Create the `MenuItem's DOM element
17868     *
17869     * @param {string} [type=li]
17870     *        Element's node type, not actually used, always set to `li`.
17871     *
17872     * @param {Object} [props={}]
17873     *        An object of properties that should be set on the element
17874     *
17875     * @param {Object} [attrs={}]
17876     *        An object of attributes that should be set on the element
17877     *
17878     * @return {Element}
17879     *         The element that gets created.
17880     */
17881
17882
17883    var _proto = MenuItem.prototype;
17884
17885    _proto.createEl = function createEl(type, props, attrs) {
17886      // The control is textual, not just an icon
17887      this.nonIconControl = true;
17888      return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
17889        className: 'vjs-menu-item',
17890        innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
17891        tabIndex: -1
17892      }, props), attrs);
17893    }
17894    /**
17895     * Ignore keys which are used by the menu, but pass any other ones up. See
17896     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17897     *
17898     * @param {EventTarget~Event} event
17899     *        The `keydown` event that caused this function to be called.
17900     *
17901     * @listens keydown
17902     */
17903    ;
17904
17905    _proto.handleKeyDown = function handleKeyDown(event) {
17906      if (!MenuKeys.some(function (key) {
17907        return keycode.isEventKey(event, key);
17908      })) {
17909        // Pass keydown handling up for unused keys
17910        _ClickableComponent.prototype.handleKeyDown.call(this, event);
17911      }
17912    }
17913    /**
17914     * Any click on a `MenuItem` puts it into the selected state.
17915     * See {@link ClickableComponent#handleClick} for instances where this is called.
17916     *
17917     * @param {EventTarget~Event} event
17918     *        The `keydown`, `tap`, or `click` event that caused this function to be
17919     *        called.
17920     *
17921     * @listens tap
17922     * @listens click
17923     */
17924    ;
17925
17926    _proto.handleClick = function handleClick(event) {
17927      this.selected(true);
17928    }
17929    /**
17930     * Set the state for this menu item as selected or not.
17931     *
17932     * @param {boolean} selected
17933     *        if the menu item is selected or not
17934     */
17935    ;
17936
17937    _proto.selected = function selected(_selected) {
17938      if (this.selectable) {
17939        if (_selected) {
17940          this.addClass('vjs-selected');
17941          this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
17942          // so indicate selected state to screen reader in the control text.
17943
17944          this.controlText(', selected');
17945          this.isSelected_ = true;
17946        } else {
17947          this.removeClass('vjs-selected');
17948          this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
17949
17950          this.controlText('');
17951          this.isSelected_ = false;
17952        }
17953      }
17954    };
17955
17956    return MenuItem;
17957  }(ClickableComponent);
17958
17959  Component.registerComponent('MenuItem', MenuItem);
17960
17961  /**
17962   * The specific menu item type for selecting a language within a text track kind
17963   *
17964   * @extends MenuItem
17965   */
17966
17967  var TextTrackMenuItem =
17968  /*#__PURE__*/
17969  function (_MenuItem) {
17970    inheritsLoose(TextTrackMenuItem, _MenuItem);
17971
17972    /**
17973     * Creates an instance of this class.
17974     *
17975     * @param {Player} player
17976     *        The `Player` that this class should be attached to.
17977     *
17978     * @param {Object} [options]
17979     *        The key/value store of player options.
17980     */
17981    function TextTrackMenuItem(player, options) {
17982      var _this;
17983
17984      var track = options.track;
17985      var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
17986
17987      options.label = track.label || track.language || 'Unknown';
17988      options.selected = track.mode === 'showing';
17989      _this = _MenuItem.call(this, player, options) || this;
17990      _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
17991      // out empty kinds.
17992
17993      _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
17994
17995      var changeHandler = function changeHandler() {
17996        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
17997          args[_key] = arguments[_key];
17998        }
17999
18000        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
18001      };
18002
18003      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
18004        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
18005          args[_key2] = arguments[_key2];
18006        }
18007
18008        _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args);
18009      };
18010
18011      player.on(['loadstart', 'texttrackchange'], changeHandler);
18012      tracks.addEventListener('change', changeHandler);
18013      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
18014
18015      _this.on('dispose', function () {
18016        player.off(['loadstart', 'texttrackchange'], changeHandler);
18017        tracks.removeEventListener('change', changeHandler);
18018        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
18019      }); // iOS7 doesn't dispatch change events to TextTrackLists when an
18020      // associated track's mode changes. Without something like
18021      // Object.observe() (also not present on iOS7), it's not
18022      // possible to detect changes to the mode attribute and polyfill
18023      // the change event. As a poor substitute, we manually dispatch
18024      // change events whenever the controls modify the mode.
18025
18026
18027      if (tracks.onchange === undefined) {
18028        var event;
18029
18030        _this.on(['tap', 'click'], function () {
18031          if (typeof window$1.Event !== 'object') {
18032            // Android 2.3 throws an Illegal Constructor error for window.Event
18033            try {
18034              event = new window$1.Event('change');
18035            } catch (err) {// continue regardless of error
18036            }
18037          }
18038
18039          if (!event) {
18040            event = document.createEvent('Event');
18041            event.initEvent('change', true, true);
18042          }
18043
18044          tracks.dispatchEvent(event);
18045        });
18046      } // set the default state based on current tracks
18047
18048
18049      _this.handleTracksChange();
18050
18051      return _this;
18052    }
18053    /**
18054     * This gets called when an `TextTrackMenuItem` is "clicked". See
18055     * {@link ClickableComponent} for more detailed information on what a click can be.
18056     *
18057     * @param {EventTarget~Event} event
vendor: 8,459 bytes, lines 18058-18334
18058     *        The `keydown`, `tap`, or `click` event that caused this function to be
18059     *        called.
18060     *
18061     * @listens tap
18062     * @listens click
18063     */
18064
18065
18066    var _proto = TextTrackMenuItem.prototype;
18067
18068    _proto.handleClick = function handleClick(event) {
18069      var referenceTrack = this.track;
18070      var tracks = this.player_.textTracks();
18071
18072      _MenuItem.prototype.handleClick.call(this, event);
18073
18074      if (!tracks) {
18075        return;
18076      }
18077
18078      for (var i = 0; i < tracks.length; i++) {
18079        var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
18080        // skip it. We do not want to affect tracks of incompatible kind(s).
18081
18082        if (this.kinds.indexOf(track.kind) === -1) {
18083          continue;
18084        } // If this text track is the component's track and it is not showing,
18085        // set it to showing.
18086
18087
18088        if (track === referenceTrack) {
18089          if (track.mode !== 'showing') {
18090            track.mode = 'showing';
18091          } // If this text track is not the component's track and it is not
18092          // disabled, set it to disabled.
18093
18094        } else if (track.mode !== 'disabled') {
18095          track.mode = 'disabled';
18096        }
18097      }
18098    }
18099    /**
18100     * Handle text track list change
18101     *
18102     * @param {EventTarget~Event} event
18103     *        The `change` event that caused this function to be called.
18104     *
18105     * @listens TextTrackList#change
18106     */
18107    ;
18108
18109    _proto.handleTracksChange = function handleTracksChange(event) {
18110      var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
18111      // screen readers to read the appended control text unnecessarily
18112
18113      if (shouldBeSelected !== this.isSelected_) {
18114        this.selected(shouldBeSelected);
18115      }
18116    };
18117
18118    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
18119      if (this.track.mode === 'showing') {
18120        var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
18121
18122        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
18123          return;
18124        }
18125
18126        this.player_.cache_.selectedLanguage = {
18127          enabled: true,
18128          language: this.track.language,
18129          kind: this.track.kind
18130        };
18131      }
18132    };
18133
18134    _proto.dispose = function dispose() {
18135      // remove reference to track object on dispose
18136      this.track = null;
18137
18138      _MenuItem.prototype.dispose.call(this);
18139    };
18140
18141    return TextTrackMenuItem;
18142  }(MenuItem);
18143
18144  Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
18145
18146  /**
18147   * A special menu item for turning of a specific type of text track
18148   *
18149   * @extends TextTrackMenuItem
18150   */
18151
18152  var OffTextTrackMenuItem =
18153  /*#__PURE__*/
18154  function (_TextTrackMenuItem) {
18155    inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
18156
18157    /**
18158     * Creates an instance of this class.
18159     *
18160     * @param {Player} player
18161     *        The `Player` that this class should be attached to.
18162     *
18163     * @param {Object} [options]
18164     *        The key/value store of player options.
18165     */
18166    function OffTextTrackMenuItem(player, options) {
18167      // Create pseudo track info
18168      // Requires options['kind']
18169      options.track = {
18170        player: player,
18171        // it is no longer necessary to store `kind` or `kinds` on the track itself
18172        // since they are now stored in the `kinds` property of all instances of
18173        // TextTrackMenuItem, but this will remain for backwards compatibility
18174        kind: options.kind,
18175        kinds: options.kinds,
18176        "default": false,
18177        mode: 'disabled'
18178      };
18179
18180      if (!options.kinds) {
18181        options.kinds = [options.kind];
18182      }
18183
18184      if (options.label) {
18185        options.track.label = options.label;
18186      } else {
18187        options.track.label = options.kinds.join(' and ') + ' off';
18188      } // MenuItem is selectable
18189
18190
18191      options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18192
18193      options.multiSelectable = false;
18194      return _TextTrackMenuItem.call(this, player, options) || this;
18195    }
18196    /**
18197     * Handle text track change
18198     *
18199     * @param {EventTarget~Event} event
18200     *        The event that caused this function to run
18201     */
18202
18203
18204    var _proto = OffTextTrackMenuItem.prototype;
18205
18206    _proto.handleTracksChange = function handleTracksChange(event) {
18207      var tracks = this.player().textTracks();
18208      var shouldBeSelected = true;
18209
18210      for (var i = 0, l = tracks.length; i < l; i++) {
18211        var track = tracks[i];
18212
18213        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
18214          shouldBeSelected = false;
18215          break;
18216        }
18217      } // Prevent redundant selected() calls because they may cause
18218      // screen readers to read the appended control text unnecessarily
18219
18220
18221      if (shouldBeSelected !== this.isSelected_) {
18222        this.selected(shouldBeSelected);
18223      }
18224    };
18225
18226    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
18227      var tracks = this.player().textTracks();
18228      var allHidden = true;
18229
18230      for (var i = 0, l = tracks.length; i < l; i++) {
18231        var track = tracks[i];
18232
18233        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
18234          allHidden = false;
18235          break;
18236        }
18237      }
18238
18239      if (allHidden) {
18240        this.player_.cache_.selectedLanguage = {
18241          enabled: false
18242        };
18243      }
18244    };
18245
18246    return OffTextTrackMenuItem;
18247  }(TextTrackMenuItem);
18248
18249  Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
18250
18251  /**
18252   * The base class for buttons that toggle specific text track types (e.g. subtitles)
18253   *
18254   * @extends MenuButton
18255   */
18256
18257  var TextTrackButton =
18258  /*#__PURE__*/
18259  function (_TrackButton) {
18260    inheritsLoose(TextTrackButton, _TrackButton);
18261
18262    /**
18263     * Creates an instance of this class.
18264     *
18265     * @param {Player} player
18266     *        The `Player` that this class should be attached to.
18267     *
18268     * @param {Object} [options={}]
18269     *        The key/value store of player options.
18270     */
18271    function TextTrackButton(player, options) {
18272      if (options === void 0) {
18273        options = {};
18274      }
18275
18276      options.tracks = player.textTracks();
18277      return _TrackButton.call(this, player, options) || this;
18278    }
18279    /**
18280     * Create a menu item for each text track
18281     *
18282     * @param {TextTrackMenuItem[]} [items=[]]
18283     *        Existing array of items to use during creation
18284     *
18285     * @return {TextTrackMenuItem[]}
18286     *         Array of menu items that were created
18287     */
18288
18289
18290    var _proto = TextTrackButton.prototype;
18291
18292    _proto.createItems = function createItems(items, TrackMenuItem) {
18293      if (items === void 0) {
18294        items = [];
18295      }
18296
18297      if (TrackMenuItem === void 0) {
18298        TrackMenuItem = TextTrackMenuItem;
18299      }
18300
18301      // Label is an override for the [track] off label
18302      // USed to localise captions/subtitles
18303      var label;
18304
18305      if (this.label_) {
18306        label = this.label_ + " off";
18307      } // Add an OFF menu item to turn all tracks off
18308
18309
18310      items.push(new OffTextTrackMenuItem(this.player_, {
18311        kinds: this.kinds_,
18312        kind: this.kind_,
18313        label: label
18314      }));
18315      this.hideThreshold_ += 1;
18316      var tracks = this.player_.textTracks();
18317
18318      if (!Array.isArray(this.kinds_)) {
18319        this.kinds_ = [this.kind_];
18320      }
18321
18322      for (var i = 0; i < tracks.length; i++) {
18323        var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
18324
18325        if (this.kinds_.indexOf(track.kind) > -1) {
18326          var item = new TrackMenuItem(this.player_, {
18327            track: track,
18328            kinds: this.kinds_,
18329            kind: this.kind_,
18330            // MenuItem is selectable
18331            selectable: true,
18332            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18333            multiSelectable: false
18334          });
18335          item.addClass("vjs-" + track.kind + "-menu-item");
18336          items.push(item);
18337        }
18338      }
18339
18340      return items;
18341    };
18342
18343    return TextTrackButton;
18344  }(TrackButton);
18345
18346  Component.registerComponent('TextTrackButton', TextTrackButton);
18347
18348  /**
18349   * The chapter track menu item
18350   *
18351   * @extends MenuItem
18352   */
18353
18354  var ChaptersTrackMenuItem =
18355  /*#__PURE__*/
18356  function (_MenuItem) {
18357    inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
18358
18359    /**
18360     * Creates an instance of this class.
18361     *
18362     * @param {Player} player
18363     *        The `Player` that this class should be attached to.
18364     *
18365     * @param {Object} [options]
18366     *        The key/value store of player options.
18367     */
18368    function ChaptersTrackMenuItem(player, options) {
18369      var _this;
18370
18371      var track = options.track;
18372      var cue = options.cue;
18373      var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
18374
18375      options.selectable = true;
18376      options.multiSelectable = false;
18377      options.label = cue.text;
18378      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
18379      _this = _MenuItem.call(this, player, options) || this;
18380      _this.track = track;
18381      _this.cue = cue;
18382      track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update));
18383      return _this;
18384    }
18385    /**
18386     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
18387     * {@link ClickableComponent} for more detailed information on what a click can be.
18388     *
18389     * @param {EventTarget~Event} [event]
18390     *        The `keydown`, `tap`, or `click` event that caused this function to be
18391     *        called.
18392     *
18393     * @listens tap
18394     * @listens click
18395     */
18396
18397
18398    var _proto = ChaptersTrackMenuItem.prototype;
18399
18400    _proto.handleClick = function handleClick(event) {
18401      _MenuItem.prototype.handleClick.call(this);
18402
18403      this.player_.currentTime(this.cue.startTime);
18404      this.update(this.cue.startTime);
18405    }
18406    /**
18407     * Update chapter menu item
18408     *
18409     * @param {EventTarget~Event} [event]
18410     *        The `cuechange` event that caused this function to run.
18411     *
18412     * @listens TextTrack#cuechange
18413     */
18414    ;
18415
18416    _proto.update = function update(event) {
18417      var cue = this.cue;
18418      var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
18419
18420      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
18421    };
18422
18423    return ChaptersTrackMenuItem;
18424  }(MenuItem);
18425
18426  Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
18427
18428  /**
18429   * The button component for toggling and selecting chapters
18430   * Chapters act much differently than other text tracks
18431   * Cues are navigation vs. other tracks of alternative languages
18432   *
18433   * @extends TextTrackButton
18434   */
18435
18436  var ChaptersButton =
18437  /*#__PURE__*/
18438  function (_TextTrackButton) {
18439    inheritsLoose(ChaptersButton, _TextTrackButton);
18440
18441    /**
18442     * Creates an instance of this class.
18443     *
18444     * @param {Player} player
18445     *        The `Player` that this class should be attached to.
18446     *
18447     * @param {Object} [options]
18448     *        The key/value store of player options.
18449     *
18450     * @param {Component~ReadyCallback} [ready]
18451     *        The function to call when this function is ready.
18452     */
18453    function ChaptersButton(player, options, ready) {
18454      return _TextTrackButton.call(this, player, options, ready) || this;
18455    }
18456    /**
18457     * Builds the default DOM `className`.
18458     *
18459     * @return {string}
18460     *         The DOM `className` for this object.
18461     */
18462
18463
18464    var _proto = ChaptersButton.prototype;
18465
18466    _proto.buildCSSClass = function buildCSSClass() {
18467      return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18468    };
18469
18470    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18471      return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18472    }
18473    /**
18474     * Update the menu based on the current state of its items.
18475     *
18476     * @param {EventTarget~Event} [event]
18477     *        An event that triggered this function to run.
18478     *
18479     * @listens TextTrackList#addtrack
18480     * @listens TextTrackList#removetrack
18481     * @listens TextTrackList#change
18482     */
18483    ;
18484
18485    _proto.update = function update(event) {
18486      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 7,580 bytes, lines 18487-18763
18487        this.setTrack(this.findChaptersTrack());
18488      }
18489
18490      _TextTrackButton.prototype.update.call(this);
18491    }
18492    /**
18493     * Set the currently selected track for the chapters button.
18494     *
18495     * @param {TextTrack} track
18496     *        The new track to select. Nothing will change if this is the currently selected
18497     *        track.
18498     */
18499    ;
18500
18501    _proto.setTrack = function setTrack(track) {
18502      if (this.track_ === track) {
18503        return;
18504      }
18505
18506      if (!this.updateHandler_) {
18507        this.updateHandler_ = this.update.bind(this);
18508      } // here this.track_ refers to the old track instance
18509
18510
18511      if (this.track_) {
18512        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18513
18514        if (remoteTextTrackEl) {
18515          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
18516        }
18517
18518        this.track_ = null;
18519      }
18520
18521      this.track_ = track; // here this.track_ refers to the new track instance
18522
18523      if (this.track_) {
18524        this.track_.mode = 'hidden';
18525
18526        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18527
18528        if (_remoteTextTrackEl) {
18529          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
18530        }
18531      }
18532    }
18533    /**
18534     * Find the track object that is currently in use by this ChaptersButton
18535     *
18536     * @return {TextTrack|undefined}
18537     *         The current track or undefined if none was found.
18538     */
18539    ;
18540
18541    _proto.findChaptersTrack = function findChaptersTrack() {
18542      var tracks = this.player_.textTracks() || [];
18543
18544      for (var i = tracks.length - 1; i >= 0; i--) {
18545        // We will always choose the last track as our chaptersTrack
18546        var track = tracks[i];
18547
18548        if (track.kind === this.kind_) {
18549          return track;
18550        }
18551      }
18552    }
18553    /**
18554     * Get the caption for the ChaptersButton based on the track label. This will also
18555     * use the current tracks localized kind as a fallback if a label does not exist.
18556     *
18557     * @return {string}
18558     *         The tracks current label or the localized track kind.
18559     */
18560    ;
18561
18562    _proto.getMenuCaption = function getMenuCaption() {
18563      if (this.track_ && this.track_.label) {
18564        return this.track_.label;
18565      }
18566
18567      return this.localize(toTitleCase(this.kind_));
18568    }
18569    /**
18570     * Create menu from chapter track
18571     *
18572     * @return {Menu}
18573     *         New menu for the chapter buttons
18574     */
18575    ;
18576
18577    _proto.createMenu = function createMenu() {
18578      this.options_.title = this.getMenuCaption();
18579      return _TextTrackButton.prototype.createMenu.call(this);
18580    }
18581    /**
18582     * Create a menu item for each text track
18583     *
18584     * @return {TextTrackMenuItem[]}
18585     *         Array of menu items
18586     */
18587    ;
18588
18589    _proto.createItems = function createItems() {
18590      var items = [];
18591
18592      if (!this.track_) {
18593        return items;
18594      }
18595
18596      var cues = this.track_.cues;
18597
18598      if (!cues) {
18599        return items;
18600      }
18601
18602      for (var i = 0, l = cues.length; i < l; i++) {
18603        var cue = cues[i];
18604        var mi = new ChaptersTrackMenuItem(this.player_, {
18605          track: this.track_,
18606          cue: cue
18607        });
18608        items.push(mi);
18609      }
18610
18611      return items;
18612    };
18613
18614    return ChaptersButton;
18615  }(TextTrackButton);
18616  /**
18617   * `kind` of TextTrack to look for to associate it with this menu.
18618   *
18619   * @type {string}
18620   * @private
18621   */
18622
18623
18624  ChaptersButton.prototype.kind_ = 'chapters';
18625  /**
18626   * The text that should display over the `ChaptersButton`s controls. Added for localization.
18627   *
18628   * @type {string}
18629   * @private
18630   */
18631
18632  ChaptersButton.prototype.controlText_ = 'Chapters';
18633  Component.registerComponent('ChaptersButton', ChaptersButton);
18634
18635  /**
18636   * The button component for toggling and selecting descriptions
18637   *
18638   * @extends TextTrackButton
18639   */
18640
18641  var DescriptionsButton =
18642  /*#__PURE__*/
18643  function (_TextTrackButton) {
18644    inheritsLoose(DescriptionsButton, _TextTrackButton);
18645
18646    /**
18647     * Creates an instance of this class.
18648     *
18649     * @param {Player} player
18650     *        The `Player` that this class should be attached to.
18651     *
18652     * @param {Object} [options]
18653     *        The key/value store of player options.
18654     *
18655     * @param {Component~ReadyCallback} [ready]
18656     *        The function to call when this component is ready.
18657     */
18658    function DescriptionsButton(player, options, ready) {
18659      var _this;
18660
18661      _this = _TextTrackButton.call(this, player, options, ready) || this;
18662      var tracks = player.textTracks();
18663      var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange);
18664      tracks.addEventListener('change', changeHandler);
18665
18666      _this.on('dispose', function () {
18667        tracks.removeEventListener('change', changeHandler);
18668      });
18669
18670      return _this;
18671    }
18672    /**
18673     * Handle text track change
18674     *
18675     * @param {EventTarget~Event} event
18676     *        The event that caused this function to run
18677     *
18678     * @listens TextTrackList#change
18679     */
18680
18681
18682    var _proto = DescriptionsButton.prototype;
18683
18684    _proto.handleTracksChange = function handleTracksChange(event) {
18685      var tracks = this.player().textTracks();
18686      var disabled = false; // Check whether a track of a different kind is showing
18687
18688      for (var i = 0, l = tracks.length; i < l; i++) {
18689        var track = tracks[i];
18690
18691        if (track.kind !== this.kind_ && track.mode === 'showing') {
18692          disabled = true;
18693          break;
18694        }
18695      } // If another track is showing, disable this menu button
18696
18697
18698      if (disabled) {
18699        this.disable();
18700      } else {
18701        this.enable();
18702      }
18703    }
18704    /**
18705     * Builds the default DOM `className`.
18706     *
18707     * @return {string}
18708     *         The DOM `className` for this object.
18709     */
18710    ;
18711
18712    _proto.buildCSSClass = function buildCSSClass() {
18713      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18714    };
18715
18716    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18717      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18718    };
18719
18720    return DescriptionsButton;
18721  }(TextTrackButton);
18722  /**
18723   * `kind` of TextTrack to look for to associate it with this menu.
18724   *
18725   * @type {string}
18726   * @private
18727   */
18728
18729
18730  DescriptionsButton.prototype.kind_ = 'descriptions';
18731  /**
18732   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
18733   *
18734   * @type {string}
18735   * @private
18736   */
18737
18738  DescriptionsButton.prototype.controlText_ = 'Descriptions';
18739  Component.registerComponent('DescriptionsButton', DescriptionsButton);
18740
18741  /**
18742   * The button component for toggling and selecting subtitles
18743   *
18744   * @extends TextTrackButton
18745   */
18746
18747  var SubtitlesButton =
18748  /*#__PURE__*/
18749  function (_TextTrackButton) {
18750    inheritsLoose(SubtitlesButton, _TextTrackButton);
18751
18752    /**
18753     * Creates an instance of this class.
18754     *
18755     * @param {Player} player
18756     *        The `Player` that this class should be attached to.
18757     *
18758     * @param {Object} [options]
18759     *        The key/value store of player options.
18760     *
18761     * @param {Component~ReadyCallback} [ready]
18762     *        The function to call when this component is ready.
18763     */
18764    function SubtitlesButton(player, options, ready) {
18765      return _TextTrackButton.call(this, player, options, ready) || this;
18766    }
18767    /**
18768     * Builds the default DOM `className`.
18769     *
18770     * @return {string}
18771     *         The DOM `className` for this object.
18772     */
18773
18774
18775    var _proto = SubtitlesButton.prototype;
18776
18777    _proto.buildCSSClass = function buildCSSClass() {
18778      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18779    };
18780
18781    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18782      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18783    };
18784
18785    return SubtitlesButton;
18786  }(TextTrackButton);
18787  /**
18788   * `kind` of TextTrack to look for to associate it with this menu.
18789   *
18790   * @type {string}
18791   * @private
18792   */
18793
18794
18795  SubtitlesButton.prototype.kind_ = 'subtitles';
18796  /**
18797   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
18798   *
18799   * @type {string}
18800   * @private
18801   */
18802
18803  SubtitlesButton.prototype.controlText_ = 'Subtitles';
18804  Component.registerComponent('SubtitlesButton', SubtitlesButton);
18805
18806  /**
18807   * The menu item for caption track settings menu
18808   *
18809   * @extends TextTrackMenuItem
18810   */
18811
18812  var CaptionSettingsMenuItem =
18813  /*#__PURE__*/
18814  function (_TextTrackMenuItem) {
18815    inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
18816
18817    /**
18818     * Creates an instance of this class.
18819     *
18820     * @param {Player} player
18821     *        The `Player` that this class should be attached to.
18822     *
18823     * @param {Object} [options]
18824     *        The key/value store of player options.
18825     */
18826    function CaptionSettingsMenuItem(player, options) {
18827      var _this;
18828
18829      options.track = {
18830        player: player,
18831        kind: options.kind,
18832        label: options.kind + ' settings',
18833        selectable: false,
18834        "default": false,
18835        mode: 'disabled'
18836      }; // CaptionSettingsMenuItem has no concept of 'selected'
18837
18838      options.selectable = false;
18839      options.name = 'CaptionSettingsMenuItem';
18840      _this = _TextTrackMenuItem.call(this, player, options) || this;
18841
18842      _this.addClass('vjs-texttrack-settings');
18843
18844      _this.controlText(', opens ' + options.kind + ' settings dialog');
18845
18846      return _this;
18847    }
18848    /**
18849     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
18850     * {@link ClickableComponent} for more detailed information on what a click can be.
18851     *
18852     * @param {EventTarget~Event} [event]
18853     *        The `keydown`, `tap`, or `click` event that caused this function to be
18854     *        called.
18855     *
18856     * @listens tap
18857     * @listens click
18858     */
18859
18860
18861    var _proto = CaptionSettingsMenuItem.prototype;
18862
18863    _proto.handleClick = function handleClick(event) {
18864      this.player().getChild('textTrackSettings').open();
18865    };
18866
18867    return CaptionSettingsMenuItem;
18868  }(TextTrackMenuItem);
18869
18870  Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
18871
18872  /**
18873   * The button component for toggling and selecting captions
18874   *
18875   * @extends TextTrackButton
18876   */
18877
18878  var CaptionsButton =
18879  /*#__PURE__*/
18880  function (_TextTrackButton) {
18881    inheritsLoose(CaptionsButton, _TextTrackButton);
18882
18883    /**
18884     * Creates an instance of this class.
18885     *
18886     * @param {Player} player
18887     *        The `Player` that this class should be attached to.
18888     *
18889     * @param {Object} [options]
18890     *        The key/value store of player options.
18891     *
18892     * @param {Component~ReadyCallback} [ready]
18893     *        The function to call when this component is ready.
18894     */
18895    function CaptionsButton(player, options, ready) {
18896      return _TextTrackButton.call(this, player, options, ready) || this;
18897    }
18898    /**
18899     * Builds the default DOM `className`.
18900     *
18901     * @return {string}
18902     *         The DOM `className` for this object.
18903     */
18904
18905
18906    var _proto = CaptionsButton.prototype;
18907
18908    _proto.buildCSSClass = function buildCSSClass() {
18909      return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18910    };
18911
18912    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18913      return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18914    }
18915    /**
18916     * Create caption menu items
18917     *
18918     * @return {CaptionSettingsMenuItem[]}
18919     *         The array of current menu items.
18920     */
18921    ;
18922
18923    _proto.createItems = function createItems() {
18924      var items = [];
18925
18926      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18927        items.push(new CaptionSettingsMenuItem(this.player_, {
18928          kind: this.kind_
18929        }));
18930        this.hideThreshold_ += 1;
18931      }
18932
18933      return _TextTrackButton.prototype.createItems.call(this, items);
18934    };
18935
18936    return CaptionsButton;
18937  }(TextTrackButton);
18938  /**
18939   * `kind` of TextTrack to look for to associate it with this menu.
18940   *
18941   * @type {string}
18942   * @private
18943   */
18944
18945
18946  CaptionsButton.prototype.kind_ = 'captions';
18947  /**
18948   * The text that should display over the `CaptionsButton`s controls. Added for localization.
18949   *
18950   * @type {string}
18951   * @private
18952   */
18953
18954  CaptionsButton.prototype.controlText_ = 'Captions';
18955  Component.registerComponent('CaptionsButton', CaptionsButton);
18956
18957  /**
18958   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
18959   * in the SubsCapsMenu.
18960   *
18961   * @extends TextTrackMenuItem
18962   */
18963
18964  var SubsCapsMenuItem =
18965  /*#__PURE__*/
18966  function (_TextTrackMenuItem) {
18967    inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
18968
18969    function SubsCapsMenuItem() {
18970      return _TextTrackMenuItem.apply(this, arguments) || this;
18971    }
18972
18973    var _proto = SubsCapsMenuItem.prototype;
18974
18975    _proto.createEl = function createEl(type, props, attrs) {
18976      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
18977
18978      if (this.options_.track.kind === 'captions') {
18979        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n      ";
18980      }
18981
18982      innerHTML += '</span>';
18983
18984      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
18985        innerHTML: innerHTML
18986      }, props), attrs);
18987
18988      return el;
18989    };
18990
18991    return SubsCapsMenuItem;
18992  }(TextTrackMenuItem);
18993
18994  Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
18995
18996  /**
18997   * The button component for toggling and selecting captions and/or subtitles
18998   *
18999   * @extends TextTrackButton
19000   */
19001
19002  var SubsCapsButton =
19003  /*#__PURE__*/
19004  function (_TextTrackButton) {
19005    inheritsLoose(SubsCapsButton, _TextTrackButton);
19006
19007    function SubsCapsButton(player, options) {
19008      var _this;
19009
19010      if (options === void 0) {
19011        options = {};
19012      }
19013
19014      _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
19015      // "captions and subtitles" other locales use "subtitles"
19016
19017      _this.label_ = 'subtitles';
19018
19019      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
19020        _this.label_ = 'captions';
19021      }
19022
19023      _this.menuButton_.controlText(toTitleCase(_this.label_));
19024
19025      return _this;
19026    }
19027    /**
19028     * Builds the default DOM `className`.
19029     *
19030     * @return {string}
19031     *         The DOM `className` for this object.
19032     */
19033
19034
19035    var _proto = SubsCapsButton.prototype;
19036
19037    _proto.buildCSSClass = function buildCSSClass() {
19038      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
19039    };
19040
19041    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19042      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
19043    }
19044    /**
19045     * Create caption/subtitles menu items
19046     *
19047     * @return {CaptionSettingsMenuItem[]}
19048     *         The array of current menu items.
19049     */
19050    ;
19051
19052    _proto.createItems = function createItems() {
19053      var items = [];
19054
19055      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
19056        items.push(new CaptionSettingsMenuItem(this.player_, {
19057          kind: this.label_
19058        }));
19059        this.hideThreshold_ += 1;
19060      }
19061
19062      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
19063      return items;
19064    };
19065
19066    return SubsCapsButton;
19067  }(TextTrackButton);
19068  /**
19069   * `kind`s of TextTrack to look for to associate it with this menu.
19070   *
19071   * @type {array}
19072   * @private
19073   */
19074
19075
19076  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
19077  /**
19078   * The text that should display over the `SubsCapsButton`s controls.
19079   *
19080   *
19081   * @type {string}
19082   * @private
19083   */
19084
19085  SubsCapsButton.prototype.controlText_ = 'Subtitles';
19086  Component.registerComponent('SubsCapsButton', SubsCapsButton);
19087
19088  /**
19089   * An {@link AudioTrack} {@link MenuItem}
19090   *
19091   * @extends MenuItem
19092   */
19093
19094  var AudioTrackMenuItem =
19095  /*#__PURE__*/
19096  function (_MenuItem) {
19097    inheritsLoose(AudioTrackMenuItem, _MenuItem);
19098
19099    /**
19100     * Creates an instance of this class.
19101     *
19102     * @param {Player} player
19103     *        The `Player` that this class should be attached to.
19104     *
19105     * @param {Object} [options]
19106     *        The key/value store of player options.
19107     */
19108    function AudioTrackMenuItem(player, options) {
19109      var _this;
19110
19111      var track = options.track;
19112      var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
19113
19114      options.label = track.label || track.language || 'Unknown';
19115      options.selected = track.enabled;
19116      _this = _MenuItem.call(this, player, options) || this;
19117      _this.track = track;
19118
vendor: 6,281 bytes, lines 19119-19331
19119      _this.addClass("vjs-" + track.kind + "-menu-item");
19120
19121      var changeHandler = function changeHandler() {
19122        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
19123          args[_key] = arguments[_key];
19124        }
19125
19126        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
19127      };
19128
19129      tracks.addEventListener('change', changeHandler);
19130
19131      _this.on('dispose', function () {
19132        tracks.removeEventListener('change', changeHandler);
19133      });
19134
19135      return _this;
19136    }
19137
19138    var _proto = AudioTrackMenuItem.prototype;
19139
19140    _proto.createEl = function createEl(type, props, attrs) {
19141      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
19142
19143      if (this.options_.track.kind === 'main-desc') {
19144        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n      ";
19145      }
19146
19147      innerHTML += '</span>';
19148
19149      var el = _MenuItem.prototype.createEl.call(this, type, assign({
19150        innerHTML: innerHTML
19151      }, props), attrs);
19152
19153      return el;
19154    }
19155    /**
19156     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
19157     * for more detailed information on what a click can be.
19158     *
19159     * @param {EventTarget~Event} [event]
19160     *        The `keydown`, `tap`, or `click` event that caused this function to be
19161     *        called.
19162     *
19163     * @listens tap
19164     * @listens click
19165     */
19166    ;
19167
19168    _proto.handleClick = function handleClick(event) {
19169      var tracks = this.player_.audioTracks();
19170
19171      _MenuItem.prototype.handleClick.call(this, event);
19172
19173      for (var i = 0; i < tracks.length; i++) {
19174        var track = tracks[i];
19175        track.enabled = track === this.track;
19176      }
19177    }
19178    /**
19179     * Handle any {@link AudioTrack} change.
19180     *
19181     * @param {EventTarget~Event} [event]
19182     *        The {@link AudioTrackList#change} event that caused this to run.
19183     *
19184     * @listens AudioTrackList#change
19185     */
19186    ;
19187
19188    _proto.handleTracksChange = function handleTracksChange(event) {
19189      this.selected(this.track.enabled);
19190    };
19191
19192    return AudioTrackMenuItem;
19193  }(MenuItem);
19194
19195  Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
19196
19197  /**
19198   * The base class for buttons that toggle specific {@link AudioTrack} types.
19199   *
19200   * @extends TrackButton
19201   */
19202
19203  var AudioTrackButton =
19204  /*#__PURE__*/
19205  function (_TrackButton) {
19206    inheritsLoose(AudioTrackButton, _TrackButton);
19207
19208    /**
19209     * Creates an instance of this class.
19210     *
19211     * @param {Player} player
19212     *        The `Player` that this class should be attached to.
19213     *
19214     * @param {Object} [options={}]
19215     *        The key/value store of player options.
19216     */
19217    function AudioTrackButton(player, options) {
19218      if (options === void 0) {
19219        options = {};
19220      }
19221
19222      options.tracks = player.audioTracks();
19223      return _TrackButton.call(this, player, options) || this;
19224    }
19225    /**
19226     * Builds the default DOM `className`.
19227     *
19228     * @return {string}
19229     *         The DOM `className` for this object.
19230     */
19231
19232
19233    var _proto = AudioTrackButton.prototype;
19234
19235    _proto.buildCSSClass = function buildCSSClass() {
19236      return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
19237    };
19238
19239    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19240      return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
19241    }
19242    /**
19243     * Create a menu item for each audio track
19244     *
19245     * @param {AudioTrackMenuItem[]} [items=[]]
19246     *        An array of existing menu items to use.
19247     *
19248     * @return {AudioTrackMenuItem[]}
19249     *         An array of menu items
19250     */
19251    ;
19252
19253    _proto.createItems = function createItems(items) {
19254      if (items === void 0) {
19255        items = [];
19256      }
19257
19258      // if there's only one audio track, there no point in showing it
19259      this.hideThreshold_ = 1;
19260      var tracks = this.player_.audioTracks();
19261
19262      for (var i = 0; i < tracks.length; i++) {
19263        var track = tracks[i];
19264        items.push(new AudioTrackMenuItem(this.player_, {
19265          track: track,
19266          // MenuItem is selectable
19267          selectable: true,
19268          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
19269          multiSelectable: false
19270        }));
19271      }
19272
19273      return items;
19274    };
19275
19276    return AudioTrackButton;
19277  }(TrackButton);
19278  /**
19279   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
19280   *
19281   * @type {string}
19282   * @private
19283   */
19284
19285
19286  AudioTrackButton.prototype.controlText_ = 'Audio Track';
19287  Component.registerComponent('AudioTrackButton', AudioTrackButton);
19288
19289  /**
19290   * The specific menu item type for selecting a playback rate.
19291   *
19292   * @extends MenuItem
19293   */
19294
19295  var PlaybackRateMenuItem =
19296  /*#__PURE__*/
19297  function (_MenuItem) {
19298    inheritsLoose(PlaybackRateMenuItem, _MenuItem);
19299
19300    /**
19301     * Creates an instance of this class.
19302     *
19303     * @param {Player} player
19304     *        The `Player` that this class should be attached to.
19305     *
19306     * @param {Object} [options]
19307     *        The key/value store of player options.
19308     */
19309    function PlaybackRateMenuItem(player, options) {
19310      var _this;
19311
19312      var label = options.rate;
19313      var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
19314
19315      options.label = label;
19316      options.selected = rate === 1;
19317      options.selectable = true;
19318      options.multiSelectable = false;
19319      _this = _MenuItem.call(this, player, options) || this;
19320      _this.label = label;
19321      _this.rate = rate;
19322
19323      _this.on(player, 'ratechange', _this.update);
19324
19325      return _this;
19326    }
19327    /**
19328     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
19329     * {@link ClickableComponent} for more detailed information on what a click can be.
19330     *
19331     * @param {EventTarget~Event} [event]
19332     *        The `keydown`, `tap`, or `click` event that caused this function to be
19333     *        called.
19334     *
19335     * @listens tap
19336     * @listens click
19337     */
19338
19339
19340    var _proto = PlaybackRateMenuItem.prototype;
19341
19342    _proto.handleClick = function handleClick(event) {
19343      _MenuItem.prototype.handleClick.call(this);
19344
19345      this.player().playbackRate(this.rate);
19346    }
19347    /**
19348     * Update the PlaybackRateMenuItem when the playbackrate changes.
19349     *
19350     * @param {EventTarget~Event} [event]
19351     *        The `ratechange` event that caused this function to run.
19352     *
19353     * @listens Player#ratechange
19354     */
19355    ;
19356
19357    _proto.update = function update(event) {
19358      this.selected(this.player().playbackRate() === this.rate);
19359    };
19360
19361    return PlaybackRateMenuItem;
19362  }(MenuItem);
19363  /**
19364   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
19365   *
19366   * @type {string}
19367   * @private
19368   */
19369
19370
19371  PlaybackRateMenuItem.prototype.contentElType = 'button';
19372  Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
19373
19374  /**
19375   * The component for controlling the playback rate.
19376   *
19377   * @extends MenuButton
19378   */
19379
19380  var PlaybackRateMenuButton =
19381  /*#__PURE__*/
19382  function (_MenuButton) {
19383    inheritsLoose(PlaybackRateMenuButton, _MenuButton);
19384
19385    /**
19386     * Creates an instance of this class.
19387     *
19388     * @param {Player} player
19389     *        The `Player` that this class should be attached to.
19390     *
19391     * @param {Object} [options]
19392     *        The key/value store of player options.
19393     */
19394    function PlaybackRateMenuButton(player, options) {
19395      var _this;
19396
19397      _this = _MenuButton.call(this, player, options) || this;
19398
19399      _this.updateVisibility();
19400
19401      _this.updateLabel();
19402
19403      _this.on(player, 'loadstart', _this.updateVisibility);
19404
19405      _this.on(player, 'ratechange', _this.updateLabel);
19406
19407      return _this;
19408    }
19409    /**
19410     * Create the `Component`'s DOM element
19411     *
19412     * @return {Element}
19413     *         The element that was created.
19414     */
19415
19416
19417    var _proto = PlaybackRateMenuButton.prototype;
19418
19419    _proto.createEl = function createEl$1() {
19420      var el = _MenuButton.prototype.createEl.call(this);
19421
19422      this.labelEl_ = createEl('div', {
19423        className: 'vjs-playback-rate-value',
19424        innerHTML: '1x'
19425      });
19426      el.appendChild(this.labelEl_);
19427      return el;
19428    };
19429
19430    _proto.dispose = function dispose() {
19431      this.labelEl_ = null;
19432
19433      _MenuButton.prototype.dispose.call(this);
19434    }
19435    /**
19436     * Builds the default DOM `className`.
19437     *
19438     * @return {string}
19439     *         The DOM `className` for this object.
19440     */
19441    ;
19442
19443    _proto.buildCSSClass = function buildCSSClass() {
19444      return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
19445    };
19446
19447    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
19448      return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
19449    }
19450    /**
19451     * Create the playback rate menu
19452     *
19453     * @return {Menu}
19454     *         Menu object populated with {@link PlaybackRateMenuItem}s
19455     */
19456    ;
19457
19458    _proto.createMenu = function createMenu() {
19459      var menu = new Menu(this.player());
19460      var rates = this.playbackRates();
19461
19462      if (rates) {
19463        for (var i = rates.length - 1; i >= 0; i--) {
19464          menu.addChild(new PlaybackRateMenuItem(this.player(), {
19465            rate: rates[i] + 'x'
19466          }));
19467        }
19468      }
19469
19470      return menu;
19471    }
19472    /**
19473     * Updates ARIA accessibility attributes
19474     */
19475    ;
19476
19477    _proto.updateARIAAttributes = function updateARIAAttributes() {
19478      // Current playback rate
19479      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
19480    }
19481    /**
19482     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
19483     * {@link ClickableComponent} for more detailed information on what a click can be.
19484     *
19485     * @param {EventTarget~Event} [event]
vendor: 23,095 bytes, lines 19486-20232
19486     *        The `keydown`, `tap`, or `click` event that caused this function to be
19487     *        called.
19488     *
19489     * @listens tap
19490     * @listens click
19491     */
19492    ;
19493
19494    _proto.handleClick = function handleClick(event) {
19495      // select next rate option
19496      var currentRate = this.player().playbackRate();
19497      var rates = this.playbackRates(); // this will select first one if the last one currently selected
19498
19499      var newRate = rates[0];
19500
19501      for (var i = 0; i < rates.length; i++) {
19502        if (rates[i] > currentRate) {
19503          newRate = rates[i];
19504          break;
19505        }
19506      }
19507
19508      this.player().playbackRate(newRate);
19509    }
19510    /**
19511     * Get possible playback rates
19512     *
19513     * @return {Array}
19514     *         All possible playback rates
19515     */
19516    ;
19517
19518    _proto.playbackRates = function playbackRates() {
19519      return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
19520    }
19521    /**
19522     * Get whether playback rates is supported by the tech
19523     * and an array of playback rates exists
19524     *
19525     * @return {boolean}
19526     *         Whether changing playback rate is supported
19527     */
19528    ;
19529
19530    _proto.playbackRateSupported = function playbackRateSupported() {
19531      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
19532    }
19533    /**
19534     * Hide playback rate controls when they're no playback rate options to select
19535     *
19536     * @param {EventTarget~Event} [event]
19537     *        The event that caused this function to run.
19538     *
19539     * @listens Player#loadstart
19540     */
19541    ;
19542
19543    _proto.updateVisibility = function updateVisibility(event) {
19544      if (this.playbackRateSupported()) {
19545        this.removeClass('vjs-hidden');
19546      } else {
19547        this.addClass('vjs-hidden');
19548      }
19549    }
19550    /**
19551     * Update button label when rate changed
19552     *
19553     * @param {EventTarget~Event} [event]
19554     *        The event that caused this function to run.
19555     *
19556     * @listens Player#ratechange
19557     */
19558    ;
19559
19560    _proto.updateLabel = function updateLabel(event) {
19561      if (this.playbackRateSupported()) {
19562        this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
19563      }
19564    };
19565
19566    return PlaybackRateMenuButton;
19567  }(MenuButton);
19568  /**
19569   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
19570   *
19571   * @type {string}
19572   * @private
19573   */
19574
19575
19576  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
19577  Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
19578
19579  /**
19580   * Just an empty spacer element that can be used as an append point for plugins, etc.
19581   * Also can be used to create space between elements when necessary.
19582   *
19583   * @extends Component
19584   */
19585
19586  var Spacer =
19587  /*#__PURE__*/
19588  function (_Component) {
19589    inheritsLoose(Spacer, _Component);
19590
19591    function Spacer() {
19592      return _Component.apply(this, arguments) || this;
19593    }
19594
19595    var _proto = Spacer.prototype;
19596
19597    /**
19598    * Builds the default DOM `className`.
19599    *
19600    * @return {string}
19601    *         The DOM `className` for this object.
19602    */
19603    _proto.buildCSSClass = function buildCSSClass() {
19604      return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
19605    }
19606    /**
19607     * Create the `Component`'s DOM element
19608     *
19609     * @return {Element}
19610     *         The element that was created.
19611     */
19612    ;
19613
19614    _proto.createEl = function createEl() {
19615      return _Component.prototype.createEl.call(this, 'div', {
19616        className: this.buildCSSClass()
19617      });
19618    };
19619
19620    return Spacer;
19621  }(Component);
19622
19623  Component.registerComponent('Spacer', Spacer);
19624
19625  /**
19626   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
19627   *
19628   * @extends Spacer
19629   */
19630
19631  var CustomControlSpacer =
19632  /*#__PURE__*/
19633  function (_Spacer) {
19634    inheritsLoose(CustomControlSpacer, _Spacer);
19635
19636    function CustomControlSpacer() {
19637      return _Spacer.apply(this, arguments) || this;
19638    }
19639
19640    var _proto = CustomControlSpacer.prototype;
19641
19642    /**
19643     * Builds the default DOM `className`.
19644     *
19645     * @return {string}
19646     *         The DOM `className` for this object.
19647     */
19648    _proto.buildCSSClass = function buildCSSClass() {
19649      return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
19650    }
19651    /**
19652     * Create the `Component`'s DOM element
19653     *
19654     * @return {Element}
19655     *         The element that was created.
19656     */
19657    ;
19658
19659    _proto.createEl = function createEl() {
19660      var el = _Spacer.prototype.createEl.call(this, {
19661        className: this.buildCSSClass()
19662      }); // No-flex/table-cell mode requires there be some content
19663      // in the cell to fill the remaining space of the table.
19664
19665
19666      el.innerHTML = "\xA0";
19667      return el;
19668    };
19669
19670    return CustomControlSpacer;
19671  }(Spacer);
19672
19673  Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
19674
19675  /**
19676   * Container of main controls.
19677   *
19678   * @extends Component
19679   */
19680
19681  var ControlBar =
19682  /*#__PURE__*/
19683  function (_Component) {
19684    inheritsLoose(ControlBar, _Component);
19685
19686    function ControlBar() {
19687      return _Component.apply(this, arguments) || this;
19688    }
19689
19690    var _proto = ControlBar.prototype;
19691
19692    /**
19693     * Create the `Component`'s DOM element
19694     *
19695     * @return {Element}
19696     *         The element that was created.
19697     */
19698    _proto.createEl = function createEl() {
19699      return _Component.prototype.createEl.call(this, 'div', {
19700        className: 'vjs-control-bar',
19701        dir: 'ltr'
19702      });
19703    };
19704
19705    return ControlBar;
19706  }(Component);
19707  /**
19708   * Default options for `ControlBar`
19709   *
19710   * @type {Object}
19711   * @private
19712   */
19713
19714
19715  ControlBar.prototype.options_ = {
19716    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
19717  };
19718
19719  if ('exitPictureInPicture' in document) {
19720    ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle');
19721  }
19722
19723  Component.registerComponent('ControlBar', ControlBar);
19724
19725  /**
19726   * A display that indicates an error has occurred. This means that the video
19727   * is unplayable.
19728   *
19729   * @extends ModalDialog
19730   */
19731
19732  var ErrorDisplay =
19733  /*#__PURE__*/
19734  function (_ModalDialog) {
19735    inheritsLoose(ErrorDisplay, _ModalDialog);
19736
19737    /**
19738     * Creates an instance of this class.
19739     *
19740     * @param  {Player} player
19741     *         The `Player` that this class should be attached to.
19742     *
19743     * @param  {Object} [options]
19744     *         The key/value store of player options.
19745     */
19746    function ErrorDisplay(player, options) {
19747      var _this;
19748
19749      _this = _ModalDialog.call(this, player, options) || this;
19750
19751      _this.on(player, 'error', _this.open);
19752
19753      return _this;
19754    }
19755    /**
19756     * Builds the default DOM `className`.
19757     *
19758     * @return {string}
19759     *         The DOM `className` for this object.
19760     *
19761     * @deprecated Since version 5.
19762     */
19763
19764
19765    var _proto = ErrorDisplay.prototype;
19766
19767    _proto.buildCSSClass = function buildCSSClass() {
19768      return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
19769    }
19770    /**
19771     * Gets the localized error message based on the `Player`s error.
19772     *
19773     * @return {string}
19774     *         The `Player`s error message localized or an empty string.
19775     */
19776    ;
19777
19778    _proto.content = function content() {
19779      var error = this.player().error();
19780      return error ? this.localize(error.message) : '';
19781    };
19782
19783    return ErrorDisplay;
19784  }(ModalDialog);
19785  /**
19786   * The default options for an `ErrorDisplay`.
19787   *
19788   * @private
19789   */
19790
19791
19792  ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
19793    pauseOnOpen: false,
19794    fillAlways: true,
19795    temporary: false,
19796    uncloseable: true
19797  });
19798  Component.registerComponent('ErrorDisplay', ErrorDisplay);
19799
19800  var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
19801  var COLOR_BLACK = ['#000', 'Black'];
19802  var COLOR_BLUE = ['#00F', 'Blue'];
19803  var COLOR_CYAN = ['#0FF', 'Cyan'];
19804  var COLOR_GREEN = ['#0F0', 'Green'];
19805  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
19806  var COLOR_RED = ['#F00', 'Red'];
19807  var COLOR_WHITE = ['#FFF', 'White'];
19808  var COLOR_YELLOW = ['#FF0', 'Yellow'];
19809  var OPACITY_OPAQUE = ['1', 'Opaque'];
19810  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
19811  var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
19812  //
19813  // Possible keys include:
19814  //
19815  // `default`:
19816  //   The default option index. Only needs to be provided if not zero.
19817  // `parser`:
19818  //   A function which is used to parse the value from the selected option in
19819  //   a customized way.
19820  // `selector`:
19821  //   The selector used to find the associated <select> element.
19822
19823  var selectConfigs = {
19824    backgroundColor: {
19825      selector: '.vjs-bg-color > select',
19826      id: 'captions-background-color-%s',
19827      label: 'Color',
19828      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19829    },
19830    backgroundOpacity: {
19831      selector: '.vjs-bg-opacity > select',
19832      id: 'captions-background-opacity-%s',
19833      label: 'Transparency',
19834      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
19835    },
19836    color: {
19837      selector: '.vjs-fg-color > select',
19838      id: 'captions-foreground-color-%s',
19839      label: 'Color',
19840      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19841    },
19842    edgeStyle: {
19843      selector: '.vjs-edge-style > select',
19844      id: '%s',
19845      label: 'Text Edge Style',
19846      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
19847    },
19848    fontFamily: {
19849      selector: '.vjs-font-family > select',
19850      id: 'captions-font-family-%s',
19851      label: 'Font Family',
19852      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
19853    },
19854    fontPercent: {
19855      selector: '.vjs-font-percent > select',
19856      id: 'captions-font-size-%s',
19857      label: 'Font Size',
19858      options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
19859      "default": 2,
19860      parser: function parser(v) {
19861        return v === '1.00' ? null : Number(v);
19862      }
19863    },
19864    textOpacity: {
19865      selector: '.vjs-text-opacity > select',
19866      id: 'captions-foreground-opacity-%s',
19867      label: 'Transparency',
19868      options: [OPACITY_OPAQUE, OPACITY_SEMI]
19869    },
19870    // Options for this object are defined below.
19871    windowColor: {
19872      selector: '.vjs-window-color > select',
19873      id: 'captions-window-color-%s',
19874      label: 'Color'
19875    },
19876    // Options for this object are defined below.
19877    windowOpacity: {
19878      selector: '.vjs-window-opacity > select',
19879      id: 'captions-window-opacity-%s',
19880      label: 'Transparency',
19881      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
19882    }
19883  };
19884  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
19885  /**
19886   * Get the actual value of an option.
19887   *
19888   * @param  {string} value
19889   *         The value to get
19890   *
19891   * @param  {Function} [parser]
19892   *         Optional function to adjust the value.
19893   *
19894   * @return {Mixed}
19895   *         - Will be `undefined` if no value exists
19896   *         - Will be `undefined` if the given value is "none".
19897   *         - Will be the actual value otherwise.
19898   *
19899   * @private
19900   */
19901
19902  function parseOptionValue(value, parser) {
19903    if (parser) {
19904      value = parser(value);
19905    }
19906
19907    if (value && value !== 'none') {
19908      return value;
19909    }
19910  }
19911  /**
19912   * Gets the value of the selected <option> element within a <select> element.
19913   *
19914   * @param  {Element} el
19915   *         the element to look in
19916   *
19917   * @param  {Function} [parser]
19918   *         Optional function to adjust the value.
19919   *
19920   * @return {Mixed}
19921   *         - Will be `undefined` if no value exists
19922   *         - Will be `undefined` if the given value is "none".
19923   *         - Will be the actual value otherwise.
19924   *
19925   * @private
19926   */
19927
19928
19929  function getSelectedOptionValue(el, parser) {
19930    var value = el.options[el.options.selectedIndex].value;
19931    return parseOptionValue(value, parser);
19932  }
19933  /**
19934   * Sets the selected <option> element within a <select> element based on a
19935   * given value.
19936   *
19937   * @param {Element} el
19938   *        The element to look in.
19939   *
19940   * @param {string} value
19941   *        the property to look on.
19942   *
19943   * @param {Function} [parser]
19944   *        Optional function to adjust the value before comparing.
19945   *
19946   * @private
19947   */
19948
19949
19950  function setSelectedOption(el, value, parser) {
19951    if (!value) {
19952      return;
19953    }
19954
19955    for (var i = 0; i < el.options.length; i++) {
19956      if (parseOptionValue(el.options[i].value, parser) === value) {
19957        el.selectedIndex = i;
19958        break;
19959      }
19960    }
19961  }
19962  /**
19963   * Manipulate Text Tracks settings.
19964   *
19965   * @extends ModalDialog
19966   */
19967
19968
19969  var TextTrackSettings =
19970  /*#__PURE__*/
19971  function (_ModalDialog) {
19972    inheritsLoose(TextTrackSettings, _ModalDialog);
19973
19974    /**
19975     * Creates an instance of this class.
19976     *
19977     * @param {Player} player
19978     *         The `Player` that this class should be attached to.
19979     *
19980     * @param {Object} [options]
19981     *         The key/value store of player options.
19982     */
19983    function TextTrackSettings(player, options) {
19984      var _this;
19985
19986      options.temporary = false;
19987      _this = _ModalDialog.call(this, player, options) || this;
19988      _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it
19989
19990      _this.fill();
19991
19992      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
19993      _this.endDialog = createEl('p', {
19994        className: 'vjs-control-text',
19995        textContent: _this.localize('End of dialog window.')
19996      });
19997
19998      _this.el().appendChild(_this.endDialog);
19999
20000      _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
20001
20002
20003      if (options.persistTextTrackSettings === undefined) {
20004        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
20005      }
20006
20007      _this.on(_this.$('.vjs-done-button'), 'click', function () {
20008        _this.saveSettings();
20009
20010        _this.close();
20011      });
20012
20013      _this.on(_this.$('.vjs-default-button'), 'click', function () {
20014        _this.setDefaults();
20015
20016        _this.updateDisplay();
20017      });
20018
20019      each(selectConfigs, function (config) {
20020        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
20021      });
20022
20023      if (_this.options_.persistTextTrackSettings) {
20024        _this.restoreSettings();
20025      }
20026
20027      return _this;
20028    }
20029
20030    var _proto = TextTrackSettings.prototype;
20031
20032    _proto.dispose = function dispose() {
20033      this.endDialog = null;
20034
20035      _ModalDialog.prototype.dispose.call(this);
20036    }
20037    /**
20038     * Create a <select> element with configured options.
20039     *
20040     * @param {string} key
20041     *        Configuration key to use during creation.
20042     *
20043     * @return {string}
20044     *         An HTML string.
20045     *
20046     * @private
20047     */
20048    ;
20049
20050    _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
20051      var _this2 = this;
20052
20053      if (legendId === void 0) {
20054        legendId = '';
20055      }
20056
20057      if (type === void 0) {
20058        type = 'label';
20059      }
20060
20061      var config = selectConfigs[key];
20062      var id = config.id.replace('%s', this.id_);
20063      var selectLabelledbyIds = [legendId, id].join(' ').trim();
20064      return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
20065        var optionId = id + '-' + o[1].replace(/\W+/g, '');
20066        return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
20067      })).concat('</select>').join('');
20068    }
20069    /**
20070     * Create foreground color element for the component
20071     *
20072     * @return {string}
20073     *         An HTML string.
20074     *
20075     * @private
20076     */
20077    ;
20078
20079    _proto.createElFgColor_ = function createElFgColor_() {
20080      var legendId = "captions-text-legend-" + this.id_;
20081      return ['<fieldset class="vjs-fg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
20082    }
20083    /**
20084     * Create background color element for the component
20085     *
20086     * @return {string}
20087     *         An HTML string.
20088     *
20089     * @private
20090     */
20091    ;
20092
20093    _proto.createElBgColor_ = function createElBgColor_() {
20094      var legendId = "captions-background-" + this.id_;
20095      return ['<fieldset class="vjs-bg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
20096    }
20097    /**
20098     * Create window color element for the component
20099     *
20100     * @return {string}
20101     *         An HTML string.
20102     *
20103     * @private
20104     */
20105    ;
20106
20107    _proto.createElWinColor_ = function createElWinColor_() {
20108      var legendId = "captions-window-" + this.id_;
20109      return ['<fieldset class="vjs-window-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
20110    }
20111    /**
20112     * Create color elements for the component
20113     *
20114     * @return {Element}
20115     *         The element that was created
20116     *
20117     * @private
20118     */
20119    ;
20120
20121    _proto.createElColors_ = function createElColors_() {
20122      return createEl('div', {
20123        className: 'vjs-track-settings-colors',
20124        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
20125      });
20126    }
20127    /**
20128     * Create font elements for the component
20129     *
20130     * @return {Element}
20131     *         The element that was created.
20132     *
20133     * @private
20134     */
20135    ;
20136
20137    _proto.createElFont_ = function createElFont_() {
20138      return createEl('div', {
20139        className: 'vjs-track-settings-font',
20140        innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('')
20141      });
20142    }
20143    /**
20144     * Create controls for the component
20145     *
20146     * @return {Element}
20147     *         The element that was created.
20148     *
20149     * @private
20150     */
20151    ;
20152
20153    _proto.createElControls_ = function createElControls_() {
20154      var defaultsDescription = this.localize('restore all settings to the default values');
20155      return createEl('div', {
20156        className: 'vjs-track-settings-controls',
20157        innerHTML: ["<button type=\"button\" class=\"vjs-default-button\" title=\"" + defaultsDescription + "\">", this.localize('Reset'), "<span class=\"vjs-control-text\"> " + defaultsDescription + "</span>", '</button>', "<button type=\"button\" class=\"vjs-done-button\">" + this.localize('Done') + "</button>"].join('')
20158      });
20159    };
20160
20161    _proto.content = function content() {
20162      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
20163    };
20164
20165    _proto.label = function label() {
20166      return this.localize('Caption Settings Dialog');
20167    };
20168
20169    _proto.description = function description() {
20170      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
20171    };
20172
20173    _proto.buildCSSClass = function buildCSSClass() {
20174      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
20175    }
20176    /**
20177     * Gets an object of text track settings (or null).
20178     *
20179     * @return {Object}
20180     *         An object with config values parsed from the DOM or localStorage.
20181     */
20182    ;
20183
20184    _proto.getValues = function getValues() {
20185      var _this3 = this;
20186
20187      return reduce(selectConfigs, function (accum, config, key) {
20188        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
20189
20190        if (value !== undefined) {
20191          accum[key] = value;
20192        }
20193
20194        return accum;
20195      }, {});
20196    }
20197    /**
20198     * Sets text track settings from an object of values.
20199     *
20200     * @param {Object} values
20201     *        An object with config values parsed from the DOM or localStorage.
20202     */
20203    ;
20204
20205    _proto.setValues = function setValues(values) {
20206      var _this4 = this;
20207
20208      each(selectConfigs, function (config, key) {
20209        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
20210      });
20211    }
20212    /**
20213     * Sets all `<select>` elements to their default values.
20214     */
20215    ;
20216
20217    _proto.setDefaults = function setDefaults() {
20218      var _this5 = this;
20219
20220      each(selectConfigs, function (config) {
20221        var index = config.hasOwnProperty('default') ? config["default"] : 0;
20222        _this5.$(config.selector).selectedIndex = index;
20223      });
20224    }
20225    /**
20226     * Restore texttrack settings from localStorage
20227     */
20228    ;
20229
20230    _proto.restoreSettings = function restoreSettings() {
20231      var values;
20232
20233      try {
20234        values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
20235      } catch (err) {
20236        log.warn(err);
20237      }
20238
20239      if (values) {
20240        this.setValues(values);
20241      }
20242    }
20243    /**
20244     * Save text track settings to localStorage
20245     */
20246    ;
20247
20248    _proto.saveSettings = function saveSettings() {
20249      if (!this.options_.persistTextTrackSettings) {
20250        return;
20251      }
20252
20253      var values = this.getValues();
20254
20255      try {
20256        if (Object.keys(values).length) {
20257          window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
20258        } else {
20259          window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
20260        }
20261      } catch (err) {
20262        log.warn(err);
20263      }
20264    }
20265    /**
20266     * Update display of text track settings
20267     */
20268    ;
20269
20270    _proto.updateDisplay = function updateDisplay() {
20271      var ttDisplay = this.player_.getChild('textTrackDisplay');
20272
20273      if (ttDisplay) {
20274        ttDisplay.updateDisplay();
20275      }
20276    }
20277    /**
20278     * conditionally blur the element and refocus the captions button
20279     *
20280     * @private
20281     */
20282    ;
20283
20284    _proto.conditionalBlur_ = function conditionalBlur_() {
20285      this.previouslyActiveEl_ = null;
20286      var cb = this.player_.controlBar;
20287      var subsCapsBtn = cb && cb.subsCapsButton;
20288      var ccBtn = cb && cb.captionsButton;
20289
20290      if (subsCapsBtn) {
20291        subsCapsBtn.focus();
20292      } else if (ccBtn) {
20293        ccBtn.focus();
20294      }
20295    };
20296
20297    return TextTrackSettings;
20298  }(ModalDialog);
20299
20300  Component.registerComponent('TextTrackSettings', TextTrackSettings);
20301
20302  /**
20303   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
20304   *
20305   * 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}.
20306   *
20307   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
20308   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
20309   * @example <caption>How to disable the resize manager</caption>
20310   * const player = videojs('#vid', {
20311   *   resizeManager: false
20312   * });
20313   *
20314   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
20315   *
20316   * @extends Component
20317   */
20318
20319  var ResizeManager =
20320  /*#__PURE__*/
20321  function (_Component) {
20322    inheritsLoose(ResizeManager, _Component);
20323
20324    /**
20325     * Create the ResizeManager.
20326     *
20327     * @param {Object} player
20328     *        The `Player` that this class should be attached to.
20329     *
20330     * @param {Object} [options]
20331     *        The key/value store of ResizeManager options.
20332     *
20333     * @param {Object} [options.ResizeObserver]
20334     *        A polyfill for ResizeObserver can be passed in here.
20335     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
20336     */
20337    function ResizeManager(player, options) {
20338      var _this;
20339
20340      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
20341
20342      if (options.ResizeObserver === null) {
20343        RESIZE_OBSERVER_AVAILABLE = false;
20344      } // Only create an element when ResizeObserver isn't available
20345
20346
20347      var options_ = mergeOptions({
20348        createEl: !RESIZE_OBSERVER_AVAILABLE,
20349        reportTouchActivity: false
20350      }, options);
20351      _this = _Component.call(this, player, options_) || this;
20352      _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
20353      _this.loadListener_ = null;
20354      _this.resizeObserver_ = null;
20355      _this.debouncedHandler_ = debounce(function () {
20356        _this.resizeHandler();
20357      }, 100, false, assertThisInitialized(_this));
20358
20359      if (RESIZE_OBSERVER_AVAILABLE) {
20360        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
20361
20362        _this.resizeObserver_.observe(player.el());
20363      } else {
20364        _this.loadListener_ = function () {
20365          if (!_this.el_ || !_this.el_.contentWindow) {
20366            return;
20367          }
20368
20369          var debouncedHandler_ = _this.debouncedHandler_;
20370
20371          var unloadListener_ = _this.unloadListener_ = function () {
20372            off(this, 'resize', debouncedHandler_);
20373            off(this, 'unload', unloadListener_);
20374            unloadListener_ = null;
20375          }; // safari and edge can unload the iframe before resizemanager dispose
20376          // we have to dispose of event handlers correctly before that happens
20377
20378
20379          on(_this.el_.contentWindow, 'unload', unloadListener_);
20380          on(_this.el_.contentWindow, 'resize', debouncedHandler_);
20381        };
20382
20383        _this.one('load', _this.loadListener_);
20384      }
20385
20386      return _this;
20387    }
20388
20389    var _proto = ResizeManager.prototype;
20390
20391    _proto.createEl = function createEl() {
20392      return _Component.prototype.createEl.call(this, 'iframe', {
20393        className: 'vjs-resize-manager',
20394        tabIndex: -1
20395      }, {
20396        'aria-hidden': 'true'
20397      });
20398    }
20399    /**
20400     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
20401     *
20402     * @fires Player#playerresize
20403     */
20404    ;
20405
20406    _proto.resizeHandler = function resizeHandler() {
20407      /**
20408       * Called when the player size has changed
20409       *
20410       * @event Player#playerresize
20411       * @type {EventTarget~Event}
20412       */
20413      // make sure player is still around to trigger
20414      // prevents this from causing an error after dispose
20415      if (!this.player_ || !this.player_.trigger) {
20416        return;
20417      }
20418
20419      this.player_.trigger('playerresize');
20420    };
20421
20422    _proto.dispose = function dispose() {
20423      if (this.debouncedHandler_) {
20424        this.debouncedHandler_.cancel();
20425      }
20426
20427      if (this.resizeObserver_) {
20428        if (this.player_.el()) {
20429          this.resizeObserver_.unobserve(this.player_.el());
20430        }
20431
20432        this.resizeObserver_.disconnect();
20433      }
20434
20435      if (this.loadListener_) {
20436        this.off('load', this.loadListener_);
20437      }
20438
20439      if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
20440        this.unloadListener_.call(this.el_.contentWindow);
20441      }
20442
20443      this.ResizeObserver = null;
20444      this.resizeObserver = null;
20445      this.debouncedHandler_ = null;
20446      this.loadListener_ = null;
20447
20448      _Component.prototype.dispose.call(this);
20449    };
20450
20451    return ResizeManager;
20452  }(Component);
20453
20454  Component.registerComponent('ResizeManager', ResizeManager);
20455
20456  /**
20457   * Computes the median of an array.
20458   *
20459   * @param {number[]} arr
20460   *        Input array of numbers.
20461   *
20462   * @return {number}
20463   *        Median value.
20464   */
20465  var median = function median(arr) {
20466    var mid = Math.floor(arr.length / 2);
20467    var sortedList = [].concat(arr).sort(function (a, b) {
20468      return a - b;
20469    });
20470    return arr.length % 2 !== 0 ? sortedList[mid] : (sortedList[mid - 1] + sortedList[mid]) / 2;
20471  };
20472
20473  /* track when we are at the live edge, and other helpers for live playback */
20474
20475  var LiveTracker =
20476  /*#__PURE__*/
20477  function (_Component) {
20478    inheritsLoose(LiveTracker, _Component);
20479
20480    function LiveTracker(player, options) {
20481      var _this;
20482
20483      // LiveTracker does not need an element
20484      var options_ = mergeOptions({
20485        createEl: false
20486      }, options);
20487      _this = _Component.call(this, player, options_) || this;
20488
20489      _this.reset_();
20490
20491      _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
20492      // also, tracking when the document is hidden can
20493      // cause the CPU to spike and eventually crash the page on IE11.
20494
20495
20496      if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
20497        _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
20498      }
20499
20500      return _this;
20501    }
20502
20503    var _proto = LiveTracker.prototype;
20504
20505    _proto.handleVisibilityChange = function handleVisibilityChange() {
20506      if (this.player_.duration() !== Infinity) {
20507        return;
20508      }
20509
20510      if (document.hidden) {
20511        this.stopTracking();
20512      } else {
20513        this.startTracking();
20514      }
20515    };
20516
20517    _proto.isBehind_ = function isBehind_() {
20518      // don't report that we are behind until a timeupdate has been seen
20519      if (!this.timeupdateSeen_) {
20520        return false;
20521      }
20522
20523      var liveCurrentTime = this.liveCurrentTime();
20524      var currentTime = this.player_.currentTime(); // the live edge window is the amount of seconds away from live
20525      // that a player can be, but still be considered live.
20526      // we add 0.07 because the live tracking happens every 30ms
20527      // and we want some wiggle room for short segment live playback
20528
20529      var liveEdgeWindow = this.seekableIncrement_ * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
20530      // can be Infinity, so we handle that case.
20531
20532      return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
20533    } // all the functionality for tracking when seek end changes
20534    // and for tracking how far past seek end we should be
20535    ;
20536
20537    _proto.trackLive_ = function trackLive_() {
20538      this.pastSeekEnd_ = this.pastSeekEnd_;
20539      var seekable = this.player_.seekable();
20539 // skip undefined seekable
20540
20541      if (!seekable || !seekable.length) {
20542        return;
20543      }
20544
20545      var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
20546      // once we detect that seekable has changed we check the new seek
20547      // end against current time, with a fudge value of half a second.
20548
20549      if (newSeekEnd !== this.lastSeekEnd_) {
20550        if (this.lastSeekEnd_) {
20551          // we try to get the best fit value for the seeking increment
20552          // variable from the last 12 values.
20553          this.seekableIncrementList_ = this.seekableIncrementList_.slice(-11);
20554          this.seekableIncrementList_.push(Math.abs(newSeekEnd - this.lastSeekEnd_));
20555
20556          if (this.seekableIncrementList_.length > 3) {
20557            this.seekableIncrement_ = median(this.seekableIncrementList_);
20558          }
20559        }
20560
20561        this.pastSeekEnd_ = 0;
20562        this.lastSeekEnd_ = newSeekEnd;
20563        this.trigger('seekableendchange');
20564      } // we should reset pastSeekEnd when the value
20565      // is much higher than seeking increment.
20566
20567
20568      if (this.pastSeekEnd() > this.seekableIncrement_ * 1.5) {
20569        this.pastSeekEnd_ = 0;
20570      } else {
20571        this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
20572      }
20573
20574      if (this.isBehind_() !== this.behindLiveEdge()) {
20575        this.behindLiveEdge_ = this.isBehind_();
20576        this.trigger('liveedgechange');
20577      }
20578    }
20579    /**
20580     * handle a durationchange event on the player
20581     * and start/stop tracking accordingly.
20582     */
20583    ;
20584
20585    _proto.handleDurationchange = function handleDurationchange() {
20586      if (this.player_.duration() === Infinity) {
20587        this.startTracking();
20588      } else {
20589        this.stopTracking();
20590      }
20591    }
20592    /**
20593     * start tracking live playback
20594     */
20595    ;
20596
20597    _proto.startTracking = function startTracking() {
20598      var _this2 = this;
20599
20600      if (this.isTracking()) {
20601        return;
20602      } // If we haven't seen a timeupdate, we need to check whether playback
20603      // began before this component started tracking. This can happen commonly
20604      // when using autoplay.
20605
20606
20607      if (!this.timeupdateSeen_) {
20608        this.timeupdateSeen_ = this.player_.hasStarted();
20609      }
20610
20611      this.trackingInterval_ = this.setInterval(this.trackLive_, UPDATE_REFRESH_INTERVAL);
20612      this.trackLive_();
20613      this.on(this.player_, 'play', this.trackLive_);
20614      this.on(this.player_, 'pause', this.trackLive_); // this is to prevent showing that we are not live
20615      // before a video starts to play
20616
20617      if (!this.timeupdateSeen_) {
20618        this.one(this.player_, 'play', this.handlePlay);
20619
20620        this.handleTimeupdate = function () {
20621          _this2.timeupdateSeen_ = true;
20622          _this2.handleTimeupdate = null;
20623        };
20624
20625        this.one(this.player_, 'timeupdate', this.handleTimeupdate);
20626      }
20627    };
20628
20629    _proto.handlePlay = function handlePlay() {
20630      this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
20631    }
20632    /**
20633     * Stop tracking, and set all internal variables to
20634     * their initial value.
20635     */
20636    ;
20637
20638    _proto.reset_ = function reset_() {
20639      this.pastSeekEnd_ = 0;
20640      this.lastSeekEnd_ = null;
20641      this.behindLiveEdge_ = null;
20642      this.timeupdateSeen_ = false;
20643      this.clearInterval(this.trackingInterval_);
20644      this.trackingInterval_ = null;
20645      this.seekableIncrement_ = 12;
20646      this.seekableIncrementList_ = [];
20647      this.off(this.player_, 'play', this.trackLive_);
20648      this.off(this.player_, 'pause', this.trackLive_);
20649      this.off(this.player_, 'play', this.handlePlay);
20650      this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
20651
20652      if (this.handleTimeupdate) {
20653        this.off(this.player_, 'timeupdate', this.handleTimeupdate);
20654        this.handleTimeupdate = null;
20655      }
20656    }
20657    /**
20658     * stop tracking live playback
20659     */
20660    ;
20661
20662    _proto.stopTracking = function stopTracking() {
20663      if (!this.isTracking()) {
20664        return;
20665      }
20666
20667      this.reset_();
20668    }
20669    /**
20670     * A helper to get the player seekable end
20671     * so that we don't have to null check everywhere
20672     */
20673    ;
20674
20675    _proto.seekableEnd = function seekableEnd() {
20676      var seekable = this.player_.seekable();
20677      var seekableEnds = [];
20678      var i = seekable ? seekable.length : 0;
20679
20680      while (i--) {
20681        seekableEnds.push(seekable.end(i));
20682      } // grab the furthest seekable end after sorting, or if there are none
20683      // default to Infinity
20684
20685
20686      return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
20687    }
20688    /**
20689     * A helper to get the player seekable start
20690     * so that we don't have to null check everywhere
20691     */
20692    ;
20693
20694    _proto.seekableStart = function seekableStart() {
20695      var seekable = this.player_.seekable();
20696      var seekableStarts = [];
20697      var i = seekable ? seekable.length : 0;
20698
20699      while (i--) {
20700        seekableStarts.push(seekable.start(i));
20701      } // grab the first seekable start after sorting, or if there are none
20702      // default to 0
20703
20704
20705      return seekableStarts.length ? seekableStarts.sort()[0] : 0;
20706    }
20707    /**
20708     * Get the live time window
20709     */
20710    ;
20711
20712    _proto.liveWindow = function liveWindow() {
20713      var liveCurrentTime = this.liveCurrentTime();
20714
20715      if (liveCurrentTime === Infinity) {
20716        return Infinity;
20717      }
20718
20719      return liveCurrentTime - this.seekableStart();
20720    }
20721    /**
20722     * Determines if the player is live, only checks if this component
20723     * is tracking live playback or not
20724     */
20725    ;
20726
20727    _proto.isLive = function isLive() {
20728      return this.isTracking();
20729    }
20730    /**
20731     * Determines if currentTime is at the live edge and won't fall behind
20732     * on each seekableendchange
20733     */
20734    ;
20735
20736    _proto.atLiveEdge = function atLiveEdge() {
20737      return !this.behindLiveEdge();
20738    }
20739    /**
20740     * get what we expect the live current time to be
20741     */
20742    ;
20743
20744    _proto.liveCurrentTime = function liveCurrentTime() {
20745      return this.pastSeekEnd() + this.seekableEnd();
20746    }
20747    /**
20748     * Returns how far past seek end we expect current time to be
20749     */
20750    ;
20751
20752    _proto.pastSeekEnd = function pastSeekEnd() {
20753      return this.pastSeekEnd_;
20754    }
20755    /**
20756     * If we are currently behind the live edge, aka currentTime will be
20757     * behind on a seekableendchange
20758     */
20759    ;
20760
20761    _proto.behindLiveEdge = function behindLiveEdge() {
20762      return this.behindLiveEdge_;
20763    };
20764
20765    _proto.isTracking = function isTracking() {
20766      return typeof this.trackingInterval_ === 'number';
20767    }
20768    /**
20769     * Seek to the live edge if we are behind the live edge
20770     */
20771    ;
20772
20773    _proto.seekToLiveEdge = function seekToLiveEdge() {
20774      if (this.atLiveEdge()) {
20775        return;
20776      }
20777
20778      this.player_.currentTime(this.liveCurrentTime());
20779
20780      if (this.player_.paused()) {
20781        this.player_.play();
20782      }
20783    };
20784
20785    _proto.dispose = function dispose() {
20786      this.stopTracking();
20787
20788      _Component.prototype.dispose.call(this);
20789    };
20790
20791    return LiveTracker;
20792  }(Component);
20793
20794  Component.registerComponent('LiveTracker', LiveTracker);
20795
20796  /**
20797   * This function is used to fire a sourceset when there is something
20798   * similar to `mediaEl.load()` being called. It will try to find the source via
20799   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20800   * with the source that was found or empty string if we cannot know. If it cannot
20801   * find a source then `sourceset` will not be fired.
20802   *
20803   * @param {Html5} tech
20804   *        The tech object that sourceset was setup on
20805   *
20806   * @return {boolean}
20807   *         returns false if the sourceset was not fired and true otherwise.
20808   */
20809
20810  var sourcesetLoad = function sourcesetLoad(tech) {
20811    var el = tech.el(); // if `el.src` is set, that source will be loaded.
20812
20813    if (el.hasAttribute('src')) {
20814      tech.triggerSourceset(el.src);
20815      return true;
20816    }
20817    /**
20818     * Since there isn't a src property on the media element, source elements will be used for
20819     * implementing the source selection algorithm. This happens asynchronously and
20820     * for most cases were there is more than one source we cannot tell what source will
20821     * be loaded, without re-implementing the source selection algorithm. At this time we are not
20822     * going to do that. There are three special cases that we do handle here though:
20823     *
20824     * 1. If there are no sources, do not fire `sourceset`.
20825     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20826     * 3. If there is more than one `<source>` but all of them have the same `src` url.
20827     *    That will be our src.
20828     */
20829
20830
20831    var sources = tech.$$('source');
20832    var srcUrls = [];
20833    var src = ''; // if there are no sources, do not fire sourceset
20834
20835    if (!sources.length) {
20836      return false;
20837    }
vendor: 39,741 bytes, lines 20837-22031
20837 // only count valid/non-duplicate source elements
20838
20839
20840    for (var i = 0; i < sources.length; i++) {
20841      var url = sources[i].src;
20842
20843      if (url && srcUrls.indexOf(url) === -1) {
20844        srcUrls.push(url);
20845      }
20846    } // there were no valid sources
20847
20848
20849    if (!srcUrls.length) {
20850      return false;
20851    } // there is only one valid source element url
20852    // use that
20853
20854
20855    if (srcUrls.length === 1) {
20856      src = srcUrls[0];
20857    }
20858
20859    tech.triggerSourceset(src);
20860    return true;
20861  };
20862  /**
20863   * our implementation of an `innerHTML` descriptor for browsers
20864   * that do not have one.
20865   */
20866
20867
20868  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
20869    get: function get() {
20870      return this.cloneNode(true).innerHTML;
20871    },
20872    set: function set(v) {
20873      // make a dummy node to use innerHTML on
20874      var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
20875
20876      dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
20877
20878      var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
20879      // to the document fragment
20880
20881      while (dummy.childNodes.length) {
20882        docFrag.appendChild(dummy.childNodes[0]);
20883      } // remove content
20884
20885
20886      this.innerText = ''; // now we add all of that html in one by appending the
20887      // document fragment. This is how innerHTML does it.
20888
20889      window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
20890
20891      return this.innerHTML;
20892    }
20893  });
20894  /**
20895   * Get a property descriptor given a list of priorities and the
20896   * property to get.
20897   */
20898
20899  var getDescriptor = function getDescriptor(priority, prop) {
20900    var descriptor = {};
20901
20902    for (var i = 0; i < priority.length; i++) {
20903      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
20904
20905      if (descriptor && descriptor.set && descriptor.get) {
20906        break;
20907      }
20908    }
20909
20910    descriptor.enumerable = true;
20911    descriptor.configurable = true;
20912    return descriptor;
20913  };
20914
20915  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
20916    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
20917  };
20918  /**
20919   * Patches browser internal functions so that we can tell synchronously
20920   * if a `<source>` was appended to the media element. For some reason this
20921   * causes a `sourceset` if the the media element is ready and has no source.
20922   * This happens when:
20923   * - The page has just loaded and the media element does not have a source.
20924   * - The media element was emptied of all sources, then `load()` was called.
20925   *
20926   * It does this by patching the following functions/properties when they are supported:
20927   *
20928   * - `append()` - can be used to add a `<source>` element to the media element
20929   * - `appendChild()` - can be used to add a `<source>` element to the media element
20930   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
20931   * - `innerHTML` -  can be used to add a `<source>` element to the media element
20932   *
20933   * @param {Html5} tech
20934   *        The tech object that sourceset is being setup on.
20935   */
20936
20937
20938  var firstSourceWatch = function firstSourceWatch(tech) {
20939    var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
20940
20941    if (el.resetSourceWatch_) {
20942      return;
20943    }
20944
20945    var old = {};
20946    var innerDescriptor = getInnerHTMLDescriptor(tech);
20947
20948    var appendWrapper = function appendWrapper(appendFn) {
20949      return function () {
20950        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
20951          args[_key] = arguments[_key];
20952        }
20953
20954        var retval = appendFn.apply(el, args);
20955        sourcesetLoad(tech);
20956        return retval;
20957      };
20958    };
20959
20960    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
20961      if (!el[k]) {
20962        return;
20963      } // store the old function
20964
20965
20966      old[k] = el[k]; // call the old function with a sourceset if a source
20967      // was loaded
20968
20969      el[k] = appendWrapper(old[k]);
20970    });
20971    Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
20972      set: appendWrapper(innerDescriptor.set)
20973    }));
20974
20975    el.resetSourceWatch_ = function () {
20976      el.resetSourceWatch_ = null;
20977      Object.keys(old).forEach(function (k) {
20978        el[k] = old[k];
20979      });
20980      Object.defineProperty(el, 'innerHTML', innerDescriptor);
20981    }; // on the first sourceset, we need to revert our changes
20982
20983
20984    tech.one('sourceset', el.resetSourceWatch_);
20985  };
20986  /**
20987   * our implementation of a `src` descriptor for browsers
20988   * that do not have one.
20989   */
20990
20991
20992  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
20993    get: function get() {
20994      if (this.hasAttribute('src')) {
20995        return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
20996      }
20997
20998      return '';
20999    },
21000    set: function set(v) {
21001      window$1.Element.prototype.setAttribute.call(this, 'src', v);
21002      return v;
21003    }
21004  });
21005
21006  var getSrcDescriptor = function getSrcDescriptor(tech) {
21007    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
21008  };
21009  /**
21010   * setup `sourceset` handling on the `Html5` tech. This function
21011   * patches the following element properties/functions:
21012   *
21013   * - `src` - to determine when `src` is set
21014   * - `setAttribute()` - to determine when `src` is set
21015   * - `load()` - this re-triggers the source selection algorithm, and can
21016   *              cause a sourceset.
21017   *
21018   * If there is no source when we are adding `sourceset` support or during a `load()`
21019   * we also patch the functions listed in `firstSourceWatch`.
21020   *
21021   * @param {Html5} tech
21022   *        The tech to patch
21023   */
21024
21025
21026  var setupSourceset = function setupSourceset(tech) {
21027    if (!tech.featuresSourceset) {
21028      return;
21029    }
21030
21031    var el = tech.el(); // make sure sourceset isn't setup twice.
21032
21033    if (el.resetSourceset_) {
21034      return;
21035    }
21036
21037    var srcDescriptor = getSrcDescriptor(tech);
21038    var oldSetAttribute = el.setAttribute;
21039    var oldLoad = el.load;
21040    Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
21041      set: function set(v) {
21042        var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
21043
21044        tech.triggerSourceset(el.src);
21045        return retval;
21046      }
21047    }));
21048
21049    el.setAttribute = function (n, v) {
21050      var retval = oldSetAttribute.call(el, n, v);
21051
21052      if (/src/i.test(n)) {
21053        tech.triggerSourceset(el.src);
21054      }
21055
21056      return retval;
21057    };
21058
21059    el.load = function () {
21060      var retval = oldLoad.call(el); // if load was called, but there was no source to fire
21061      // sourceset on. We have to watch for a source append
21062      // as that can trigger a `sourceset` when the media element
21063      // has no source
21064
21065      if (!sourcesetLoad(tech)) {
21066        tech.triggerSourceset('');
21067        firstSourceWatch(tech);
21068      }
21069
21070      return retval;
21071    };
21072
21073    if (el.currentSrc) {
21074      tech.triggerSourceset(el.currentSrc);
21075    } else if (!sourcesetLoad(tech)) {
21076      firstSourceWatch(tech);
21077    }
21078
21079    el.resetSourceset_ = function () {
21080      el.resetSourceset_ = null;
21081      el.load = oldLoad;
21082      el.setAttribute = oldSetAttribute;
21083      Object.defineProperty(el, 'src', srcDescriptor);
21084
21085      if (el.resetSourceWatch_) {
21086        el.resetSourceWatch_();
21087      }
21088    };
21089  };
21090
21091  /**
21092   * Object.defineProperty but "lazy", which means that the value is only set after
21093   * it retrieved the first time, rather than being set right away.
21094   *
21095   * @param {Object} obj the object to set the property on
21096   * @param {string} key the key for the property to set
21097   * @param {Function} getValue the function used to get the value when it is needed.
21098   * @param {boolean} setter wether a setter shoould be allowed or not
21099   */
21100  var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) {
21101    if (setter === void 0) {
21102      setter = true;
21103    }
21104
21105    var set = function set(value) {
21106      return Object.defineProperty(obj, key, {
21107        value: value,
21108        enumerable: true,
21109        writable: true
21110      });
21111    };
21112
21113    var options = {
21114      configurable: true,
21115      enumerable: true,
21116      get: function get() {
21117        var value = getValue();
21118        set(value);
21119        return value;
21120      }
21121    };
21122
21123    if (setter) {
21124      options.set = set;
21125    }
21126
21127    return Object.defineProperty(obj, key, options);
21128  };
21129
21130  /**
21131   * HTML5 Media Controller - Wrapper for HTML5 Media API
21132   *
21133   * @mixes Tech~SourceHandlerAdditions
21134   * @extends Tech
21135   */
21136
21137  var Html5 =
21138  /*#__PURE__*/
21139  function (_Tech) {
21140    inheritsLoose(Html5, _Tech);
21141
21142    /**
21143    * Create an instance of this Tech.
21144    *
21145    * @param {Object} [options]
21146    *        The key/value store of player options.
21147    *
21148    * @param {Component~ReadyCallback} ready
21149    *        Callback function to call when the `HTML5` Tech is ready.
21150    */
21151    function Html5(options, ready) {
21152      var _this;
21153
21154      _this = _Tech.call(this, options, ready) || this;
21155      var source = options.source;
21156      var crossoriginTracks = false; // Set the source if one is provided
21157      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
21158      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
21159      // anyway so the error gets fired.
21160
21161      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
21162        _this.setSource(source);
21163      } else {
21164        _this.handleLateInit_(_this.el_);
21165      } // setup sourceset after late sourceset/init
21166
21167
21168      if (options.enableSourceset) {
21169        _this.setupSourcesetHandling_();
21170      }
21171
21172      if (_this.el_.hasChildNodes()) {
21173        var nodes = _this.el_.childNodes;
21174        var nodesLength = nodes.length;
21175        var removeNodes = [];
21176
21177        while (nodesLength--) {
21178          var node = nodes[nodesLength];
21179          var nodeName = node.nodeName.toLowerCase();
21180
21181          if (nodeName === 'track') {
21182            if (!_this.featuresNativeTextTracks) {
21183              // Empty video tag tracks so the built-in player doesn't use them also.
21184              // This may not be fast enough to stop HTML5 browsers from reading the tags
21185              // so we'll need to turn off any default tracks if we're manually doing
21186              // captions and subtitles. videoElement.textTracks
21187              removeNodes.push(node);
21188            } else {
21189              // store HTMLTrackElement and TextTrack to remote list
21190              _this.remoteTextTrackEls().addTrackElement_(node);
21191
21192              _this.remoteTextTracks().addTrack(node.track);
21193
21194              _this.textTracks().addTrack(node.track);
21195
21196              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
21197                crossoriginTracks = true;
21198              }
21199            }
21200          }
21201        }
21202
21203        for (var i = 0; i < removeNodes.length; i++) {
21204          _this.el_.removeChild(removeNodes[i]);
21205        }
21206      }
21207
21208      _this.proxyNativeTracks_();
21209
21210      if (_this.featuresNativeTextTracks && crossoriginTracks) {
21211        log.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
21212      } // prevent iOS Safari from disabling metadata text tracks during native playback
21213
21214
21215      _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
21216      // Our goal should be to get the custom controls on mobile solid everywhere
21217      // so we can remove this all together. Right now this will block custom
21218      // controls on touch enabled laptops like the Chrome Pixel
21219
21220
21221      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
21222        _this.setControls(true);
21223      } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
21224      // into a `fullscreenchange` event
21225
21226
21227      _this.proxyWebkitFullscreen_();
21228
21229      _this.triggerReady();
21230
21231      return _this;
21232    }
21233    /**
21234     * Dispose of `HTML5` media element and remove all tracks.
21235     */
21236
21237
21238    var _proto = Html5.prototype;
21239
21240    _proto.dispose = function dispose() {
21241      if (this.el_ && this.el_.resetSourceset_) {
21242        this.el_.resetSourceset_();
21243      }
21244
21245      Html5.disposeMediaElement(this.el_);
21246      this.options_ = null; // tech will handle clearing of the emulated track list
21247
21248      _Tech.prototype.dispose.call(this);
21249    }
21250    /**
21251     * Modify the media element so that we can detect when
21252     * the source is changed. Fires `sourceset` just after the source has changed
21253     */
21254    ;
21255
21256    _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
21257      setupSourceset(this);
21258    }
21259    /**
21260     * When a captions track is enabled in the iOS Safari native player, all other
21261     * tracks are disabled (including metadata tracks), which nulls all of their
21262     * associated cue points. This will restore metadata tracks to their pre-fullscreen
21263     * state in those cases so that cue points are not needlessly lost.
21264     *
21265     * @private
21266     */
21267    ;
21268
21269    _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
21270      var textTracks = this.textTracks();
21271      var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
21272
21273      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
21274        metadataTracksPreFullscreenState = [];
21275
21276        for (var i = 0; i < textTracks.length; i++) {
21277          var track = textTracks[i];
21278
21279          if (track.kind === 'metadata') {
21280            metadataTracksPreFullscreenState.push({
21281              track: track,
21282              storedMode: track.mode
21283            });
21284          }
21285        }
21286      }; // snapshot each metadata track's initial state, and update the snapshot
21287      // each time there is a track 'change' event
21288
21289
21290      takeMetadataTrackSnapshot();
21291      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
21292      this.on('dispose', function () {
21293        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
21294      });
21295
21296      var restoreTrackMode = function restoreTrackMode() {
21297        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
21298          var storedTrack = metadataTracksPreFullscreenState[i];
21299
21300          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
21301            storedTrack.track.mode = storedTrack.storedMode;
21302          }
21303        } // we only want this handler to be executed on the first 'change' event
21304
21305
21306        textTracks.removeEventListener('change', restoreTrackMode);
21307      }; // when we enter fullscreen playback, stop updating the snapshot and
21308      // restore all track modes to their pre-fullscreen state
21309
21310
21311      this.on('webkitbeginfullscreen', function () {
21312        textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
21313
21314        textTracks.removeEventListener('change', restoreTrackMode);
21315        textTracks.addEventListener('change', restoreTrackMode);
21316      }); // start updating the snapshot again after leaving fullscreen
21317
21318      this.on('webkitendfullscreen', function () {
21319        // remove the listener before adding it just in case it wasn't previously removed
21320        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
21321        textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
21322
21323        textTracks.removeEventListener('change', restoreTrackMode);
21324      });
21325    }
21326    /**
21327     * Attempt to force override of tracks for the given type
21328     *
21329     * @param {string} type - Track type to override, possible values include 'Audio',
21330     * 'Video', and 'Text'.
21331     * @param {boolean} override - If set to true native audio/video will be overridden,
21332     * otherwise native audio/video will potentially be used.
21333     * @private
21334     */
21335    ;
21336
21337    _proto.overrideNative_ = function overrideNative_(type, override) {
21338      var _this2 = this;
21339
21340      // If there is no behavioral change don't add/remove listeners
21341      if (override !== this["featuresNative" + type + "Tracks"]) {
21342        return;
21343      }
21344
21345      var lowerCaseType = type.toLowerCase();
21346
21347      if (this[lowerCaseType + "TracksListeners_"]) {
21348        Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
21349          var elTracks = _this2.el()[lowerCaseType + "Tracks"];
21350
21351          elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
21352        });
21353      }
21354
21355      this["featuresNative" + type + "Tracks"] = !override;
21356      this[lowerCaseType + "TracksListeners_"] = null;
21357      this.proxyNativeTracksForType_(lowerCaseType);
21358    }
21359    /**
21360     * Attempt to force override of native audio tracks.
21361     *
21362     * @param {boolean} override - If set to true native audio will be overridden,
21363     * otherwise native audio will potentially be used.
21364     */
21365    ;
21366
21367    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
21368      this.overrideNative_('Audio', override);
21369    }
21370    /**
21371     * Attempt to force override of native video tracks.
21372     *
21373     * @param {boolean} override - If set to true native video will be overridden,
21374     * otherwise native video will potentially be used.
21375     */
21376    ;
21377
21378    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
21379      this.overrideNative_('Video', override);
21380    }
21381    /**
21382     * Proxy native track list events for the given type to our track
21383     * lists if the browser we are playing in supports that type of track list.
21384     *
21385     * @param {string} name - Track type; values include 'audio', 'video', and 'text'
21386     * @private
21387     */
21388    ;
21389
21390    _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
21391      var _this3 = this;
21392
21393      var props = NORMAL[name];
21394      var elTracks = this.el()[props.getterName];
21395      var techTracks = this[props.getterName]();
21396
21397      if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
21398        return;
21399      }
21400
21401      var listeners = {
21402        change: function change(e) {
21403          techTracks.trigger({
21404            type: 'change',
21405            target: techTracks,
21406            currentTarget: techTracks,
21407            srcElement: techTracks
21408          });
21409        },
21410        addtrack: function addtrack(e) {
21411          techTracks.addTrack(e.track);
21412        },
21413        removetrack: function removetrack(e) {
21414          techTracks.removeTrack(e.track);
21415        }
21416      };
21417
21418      var removeOldTracks = function removeOldTracks() {
21419        var removeTracks = [];
21420
21421        for (var i = 0; i < techTracks.length; i++) {
21422          var found = false;
21423
21424          for (var j = 0; j < elTracks.length; j++) {
21425            if (elTracks[j] === techTracks[i]) {
21426              found = true;
21427              break;
21428            }
21429          }
21430
21431          if (!found) {
21432            removeTracks.push(techTracks[i]);
21433          }
21434        }
21435
21436        while (removeTracks.length) {
21437          techTracks.removeTrack(removeTracks.shift());
21438        }
21439      };
21440
21441      this[props.getterName + 'Listeners_'] = listeners;
21442      Object.keys(listeners).forEach(function (eventName) {
21443        var listener = listeners[eventName];
21444        elTracks.addEventListener(eventName, listener);
21445
21446        _this3.on('dispose', function (e) {
21447          return elTracks.removeEventListener(eventName, listener);
21448        });
21449      }); // Remove (native) tracks that are not used anymore
21450
21451      this.on('loadstart', removeOldTracks);
21452      this.on('dispose', function (e) {
21453        return _this3.off('loadstart', removeOldTracks);
21454      });
21455    }
21456    /**
21457     * Proxy all native track list events to our track lists if the browser we are playing
21458     * in supports that type of track list.
21459     *
21460     * @private
21461     */
21462    ;
21463
21464    _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
21465      var _this4 = this;
21466
21467      NORMAL.names.forEach(function (name) {
21468        _this4.proxyNativeTracksForType_(name);
21469      });
21470    }
21471    /**
21472     * Create the `Html5` Tech's DOM element.
21473     *
21474     * @return {Element}
21475     *         The element that gets created.
21476     */
21477    ;
21478
21479    _proto.createEl = function createEl() {
21480      var el = this.options_.tag; // Check if this browser supports moving the element into the box.
21481      // On the iPhone video will break if you move the element,
21482      // So we have to create a brand new element.
21483      // If we ingested the player div, we do not need to move the media element.
21484
21485      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
21486        // If the original tag is still there, clone and remove it.
21487        if (el) {
21488          var clone = el.cloneNode(true);
21489
21490          if (el.parentNode) {
21491            el.parentNode.insertBefore(clone, el);
21492          }
21493
21494          Html5.disposeMediaElement(el);
21495          el = clone;
21496        } else {
21497          el = document.createElement('video'); // determine if native controls should be used
21498
21499          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
21500          var attributes = mergeOptions({}, tagAttributes);
21501
21502          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
21503            delete attributes.controls;
21504          }
21505
21506          setAttributes(el, assign(attributes, {
21507            id: this.options_.techId,
21508            "class": 'vjs-tech'
21509          }));
21510        }
21511
21512        el.playerId = this.options_.playerId;
21513      }
21514
21515      if (typeof this.options_.preload !== 'undefined') {
21516        setAttribute(el, 'preload', this.options_.preload);
21517      } // Update specific tag settings, in case they were overridden
21518      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
21519      // when iOS/Safari or other browsers attempt to autoplay.
21520
21521
21522      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
21523
21524      for (var i = 0; i < settingsAttrs.length; i++) {
21525        var attr = settingsAttrs[i];
21526        var value = this.options_[attr];
21527
21528        if (typeof value !== 'undefined') {
21529          if (value) {
21530            setAttribute(el, attr, attr);
21531          } else {
21532            removeAttribute(el, attr);
21533          }
21534
21535          el[attr] = value;
21536        }
21537      }
21538
21539      return el;
21540    }
21541    /**
21542     * This will be triggered if the loadstart event has already fired, before videojs was
21543     * ready. Two known examples of when this can happen are:
21544     * 1. If we're loading the playback object after it has started loading
21545     * 2. The media is already playing the (often with autoplay on) then
21546     *
21547     * This function will fire another loadstart so that videojs can catchup.
21548     *
21549     * @fires Tech#loadstart
21550     *
21551     * @return {undefined}
21552     *         returns nothing.
21553     */
21554    ;
21555
21556    _proto.handleLateInit_ = function handleLateInit_(el) {
21557      if (el.networkState === 0 || el.networkState === 3) {
21558        // The video element hasn't started loading the source yet
21559        // or didn't find a source
21560        return;
21561      }
21562
21563      if (el.readyState === 0) {
21564        // NetworkState is set synchronously BUT loadstart is fired at the
21565        // end of the current stack, usually before setInterval(fn, 0).
21566        // So at this point we know loadstart may have already fired or is
21567        // about to fire, and either way the player hasn't seen it yet.
21568        // We don't want to fire loadstart prematurely here and cause a
21569        // double loadstart so we'll wait and see if it happens between now
21570        // and the next loop, and fire it if not.
21571        // HOWEVER, we also want to make sure it fires before loadedmetadata
21572        // which could also happen between now and the next loop, so we'll
21573        // watch for that also.
21574        var loadstartFired = false;
21575
21576        var setLoadstartFired = function setLoadstartFired() {
21577          loadstartFired = true;
21578        };
21579
21580        this.on('loadstart', setLoadstartFired);
21581
21582        var triggerLoadstart = function triggerLoadstart() {
21583          // We did miss the original loadstart. Make sure the player
21584          // sees loadstart before loadedmetadata
21585          if (!loadstartFired) {
21586            this.trigger('loadstart');
21587          }
21588        };
21589
21590        this.on('loadedmetadata', triggerLoadstart);
21591        this.ready(function () {
21592          this.off('loadstart', setLoadstartFired);
21593          this.off('loadedmetadata', triggerLoadstart);
21594
21595          if (!loadstartFired) {
21596            // We did miss the original native loadstart. Fire it now.
21597            this.trigger('loadstart');
21598          }
21599        });
21600        return;
21601      } // From here on we know that loadstart already fired and we missed it.
21602      // The other readyState events aren't as much of a problem if we double
21603      // them, so not going to go to as much trouble as loadstart to prevent
21604      // that unless we find reason to.
21605
21606
21607      var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
21608
21609      eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
21610
21611      if (el.readyState >= 2) {
21612        eventsToTrigger.push('loadeddata');
21613      } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
21614
21615
21616      if (el.readyState >= 3) {
21617        eventsToTrigger.push('canplay');
21618      } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
21619
21620
21621      if (el.readyState >= 4) {
21622        eventsToTrigger.push('canplaythrough');
21623      } // We still need to give the player time to add event listeners
21624
21625
21626      this.ready(function () {
21627        eventsToTrigger.forEach(function (type) {
21628          this.trigger(type);
21629        }, this);
21630      });
21631    }
21632    /**
21633     * Set current time for the `HTML5` tech.
21634     *
21635     * @param {number} seconds
21636     *        Set the current time of the media to this.
21637     */
21638    ;
21639
21640    _proto.setCurrentTime = function setCurrentTime(seconds) {
21641      try {
21642        this.el_.currentTime = seconds;
21643      } catch (e) {
21644        log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
21645      }
21646    }
21647    /**
21648     * Get the current duration of the HTML5 media element.
21649     *
21650     * @return {number}
21651     *         The duration of the media or 0 if there is no duration.
21652     */
21653    ;
21654
21655    _proto.duration = function duration() {
21656      var _this5 = this;
21657
21658      // Android Chrome will report duration as Infinity for VOD HLS until after
21659      // playback has started, which triggers the live display erroneously.
21660      // Return NaN if playback has not started and trigger a durationupdate once
21661      // the duration can be reliably known.
21662      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
21663        // Wait for the first `timeupdate` with currentTime > 0 - there may be
21664        // several with 0
21665        var checkProgress = function checkProgress() {
21666          if (_this5.el_.currentTime > 0) {
21667            // Trigger durationchange for genuinely live video
21668            if (_this5.el_.duration === Infinity) {
21669              _this5.trigger('durationchange');
21670            }
21671
21672            _this5.off('timeupdate', checkProgress);
21673          }
21674        };
21675
21676        this.on('timeupdate', checkProgress);
21677        return NaN;
21678      }
21679
21680      return this.el_.duration || NaN;
21681    }
21682    /**
21683     * Get the current width of the HTML5 media element.
21684     *
21685     * @return {number}
21686     *         The width of the HTML5 media element.
21687     */
21688    ;
21689
21690    _proto.width = function width() {
21691      return this.el_.offsetWidth;
21692    }
21693    /**
21694     * Get the current height of the HTML5 media element.
21695     *
21696     * @return {number}
21697     *         The height of the HTML5 media element.
21698     */
21699    ;
21700
21701    _proto.height = function height() {
21702      return this.el_.offsetHeight;
21703    }
21704    /**
21705     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
21706     * `fullscreenchange` event.
21707     *
21708     * @private
21709     * @fires fullscreenchange
21710     * @listens webkitendfullscreen
21711     * @listens webkitbeginfullscreen
21712     * @listens webkitbeginfullscreen
21713     */
21714    ;
21715
21716    _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
21717      var _this6 = this;
21718
21719      if (!('webkitDisplayingFullscreen' in this.el_)) {
21720        return;
21721      }
21722
21723      var endFn = function endFn() {
21724        this.trigger('fullscreenchange', {
21725          isFullscreen: false
21726        });
21727      };
21728
21729      var beginFn = function beginFn() {
21730        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
21731          this.one('webkitendfullscreen', endFn);
21732          this.trigger('fullscreenchange', {
21733            isFullscreen: true
21734          });
21735        }
21736      };
21737
21738      this.on('webkitbeginfullscreen', beginFn);
21739      this.on('dispose', function () {
21740        _this6.off('webkitbeginfullscreen', beginFn);
21741
21742        _this6.off('webkitendfullscreen', endFn);
21743      });
21744    }
21745    /**
21746     * Check if fullscreen is supported on the current playback device.
21747     *
21748     * @return {boolean}
21749     *         - True if fullscreen is supported.
21750     *         - False if fullscreen is not supported.
21751     */
21752    ;
21753
21754    _proto.supportsFullScreen = function supportsFullScreen() {
21755      if (typeof this.el_.webkitEnterFullScreen === 'function') {
21756        var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
21757
21758        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
21759          return true;
21760        }
21761      }
21762
21763      return false;
21764    }
21765    /**
21766     * Request that the `HTML5` Tech enter fullscreen.
21767     */
21768    ;
21769
21770    _proto.enterFullScreen = function enterFullScreen() {
21771      var video = this.el_;
21772
21773      if (video.paused && video.networkState <= video.HAVE_METADATA) {
21774        // attempt to prime the video element for programmatic access
21775        // this isn't necessary on the desktop but shouldn't hurt
21776        this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
21777        // can get iOS ~6.1 devices into a play/pause loop
21778
21779        this.setTimeout(function () {
21780          video.pause();
21781          video.webkitEnterFullScreen();
21782        }, 0);
21783      } else {
21784        video.webkitEnterFullScreen();
21785      }
21786    }
21787    /**
21788     * Request that the `HTML5` Tech exit fullscreen.
21789     */
21790    ;
21791
21792    _proto.exitFullScreen = function exitFullScreen() {
21793      this.el_.webkitExitFullScreen();
21794    }
21795    /**
21796     * Create a floating video window always on top of other windows so that users may
21797     * continue consuming media while they interact with other content sites, or
21798     * applications on their device.
21799     *
21800     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
21801     *
21802     * @return {Promise}
21803     *         A promise with a Picture-in-Picture window.
21804     */
21805    ;
21806
21807    _proto.requestPictureInPicture = function requestPictureInPicture() {
21808      return this.el_.requestPictureInPicture();
21809    }
21810    /**
21811     * A getter/setter for the `Html5` Tech's source object.
21812     * > Note: Please use {@link Html5#setSource}
21813     *
21814     * @param {Tech~SourceObject} [src]
21815     *        The source object you want to set on the `HTML5` techs element.
21816     *
21817     * @return {Tech~SourceObject|undefined}
21818     *         - The current source object when a source is not passed in.
21819     *         - undefined when setting
21820     *
21821     * @deprecated Since version 5.
21822     */
21823    ;
21824
21825    _proto.src = function src(_src) {
21826      if (_src === undefined) {
21827        return this.el_.src;
21828      } // Setting src through `src` instead of `setSrc` will be deprecated
21829
21830
21831      this.setSrc(_src);
21832    }
21833    /**
21834     * Reset the tech by removing all sources and then calling
21835     * {@link Html5.resetMediaElement}.
21836     */
21837    ;
21838
21839    _proto.reset = function reset() {
21840      Html5.resetMediaElement(this.el_);
21841    }
21842    /**
21843     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
21844     * the HTML5 media element.
21845     *
21846     * @return {Tech~SourceObject}
21847     *         The current source object from the HTML5 tech. With a fallback to the
21848     *         elements source.
21849     */
21850    ;
21851
21852    _proto.currentSrc = function currentSrc() {
21853      if (this.currentSource_) {
21854        return this.currentSource_.src;
21855      }
21856
21857      return this.el_.currentSrc;
21858    }
21859    /**
21860     * Set controls attribute for the HTML5 media Element.
21861     *
21862     * @param {string} val
21863     *        Value to set the controls attribute to
21864     */
21865    ;
21866
21867    _proto.setControls = function setControls(val) {
21868      this.el_.controls = !!val;
21869    }
21870    /**
21871     * Create and returns a remote {@link TextTrack} object.
21872     *
21873     * @param {string} kind
21874     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
21875     *
21876     * @param {string} [label]
21877     *        Label to identify the text track
21878     *
21879     * @param {string} [language]
21880     *        Two letter language abbreviation
21881     *
21882     * @return {TextTrack}
21883     *         The TextTrack that gets created.
21884     */
21885    ;
21886
21887    _proto.addTextTrack = function addTextTrack(kind, label, language) {
21888      if (!this.featuresNativeTextTracks) {
21889        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
21890      }
21891
21892      return this.el_.addTextTrack(kind, label, language);
21893    }
21894    /**
21895     * Creates either native TextTrack or an emulated TextTrack depending
21896     * on the value of `featuresNativeTextTracks`
21897     *
21898     * @param {Object} options
21899     *        The object should contain the options to initialize the TextTrack with.
21900     *
21901     * @param {string} [options.kind]
21902     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
21903     *
21904     * @param {string} [options.label]
21905     *        Label to identify the text track
21906     *
21907     * @param {string} [options.language]
21908     *        Two letter language abbreviation.
21909     *
21910     * @param {boolean} [options.default]
21911     *        Default this track to on.
21912     *
21913     * @param {string} [options.id]
21914     *        The internal id to assign this track.
21915     *
21916     * @param {string} [options.src]
21917     *        A source url for the track.
21918     *
21919     * @return {HTMLTrackElement}
21920     *         The track element that gets created.
21921     */
21922    ;
21923
21924    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
21925      if (!this.featuresNativeTextTracks) {
21926        return _Tech.prototype.createRemoteTextTrack.call(this, options);
21927      }
21928
21929      var htmlTrackElement = document.createElement('track');
21930
21931      if (options.kind) {
21932        htmlTrackElement.kind = options.kind;
21933      }
21934
21935      if (options.label) {
21936        htmlTrackElement.label = options.label;
21937      }
21938
21939      if (options.language || options.srclang) {
21940        htmlTrackElement.srclang = options.language || options.srclang;
21941      }
21942
21943      if (options["default"]) {
21944        htmlTrackElement["default"] = options["default"];
21945      }
21946
21947      if (options.id) {
21948        htmlTrackElement.id = options.id;
21949      }
21950
21951      if (options.src) {
21952        htmlTrackElement.src = options.src;
21953      }
21954
21955      return htmlTrackElement;
21956    }
21957    /**
21958     * Creates a remote text track object and returns an html track element.
21959     *
21960     * @param {Object} options The object should contain values for
21961     * kind, language, label, and src (location of the WebVTT file)
21962     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
21963     * automatically removed from the video element whenever the source changes
21964     * @return {HTMLTrackElement} An Html Track Element.
21965     * This can be an emulated {@link HTMLTrackElement} or a native one.
21966     * @deprecated The default value of the "manualCleanup" parameter will default
21967     * to "false" in upcoming versions of Video.js
21968     */
21969    ;
21970
21971    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
21972      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
21973
21974      if (this.featuresNativeTextTracks) {
21975        this.el().appendChild(htmlTrackElement);
21976      }
21977
21978      return htmlTrackElement;
21979    }
21980    /**
21981     * Remove remote `TextTrack` from `TextTrackList` object
21982     *
21983     * @param {TextTrack} track
21984     *        `TextTrack` object to remove
21985     */
21986    ;
21987
21988    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
21989      _Tech.prototype.removeRemoteTextTrack.call(this, track);
21990
21991      if (this.featuresNativeTextTracks) {
21992        var tracks = this.$$('track');
21993        var i = tracks.length;
21994
21995        while (i--) {
21996          if (track === tracks[i] || track === tracks[i].track) {
21997            this.el().removeChild(tracks[i]);
21998          }
21999        }
22000      }
22001    }
22002    /**
22003     * Gets available media playback quality metrics as specified by the W3C's Media
22004     * Playback Quality API.
22005     *
22006     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
22007     *
22008     * @return {Object}
22009     *         An object with supported media playback quality metrics
22010     */
22011    ;
22012
22013    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
22014      if (typeof this.el().getVideoPlaybackQuality === 'function') {
22015        return this.el().getVideoPlaybackQuality();
22016      }
22017
22018      var videoPlaybackQuality = {};
22019
22020      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
22021        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
22022        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
22023      }
22024
22025      if (window$1.performance && typeof window$1.performance.now === 'function') {
22026        videoPlaybackQuality.creationTime = window$1.performance.now();
22027      } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
22028        videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
22029      }
22030
22031      return videoPlaybackQuality;
vendor: 4,248 bytes, lines 22032-22189
22032    };
22033
22034    return Html5;
22035  }(Tech);
22036  /* HTML5 Support Testing ---------------------------------------------------- */
22037
22038  /**
22039   * Element for testing browser HTML5 media capabilities
22040   *
22041   * @type {Element}
22042   * @constant
22043   * @private
22044   */
22045
22046
22047  defineLazyProperty(Html5, 'TEST_VID', function () {
22048    if (!isReal()) {
22049      return;
22050    }
22051
22052    var video = document.createElement('video');
22053    var track = document.createElement('track');
22054    track.kind = 'captions';
22055    track.srclang = 'en';
22056    track.label = 'English';
22057    video.appendChild(track);
22058    return video;
22059  });
22060  /**
22061   * Check if HTML5 media is supported by this browser/device.
22062   *
22063   * @return {boolean}
22064   *         - True if HTML5 media is supported.
22065   *         - False if HTML5 media is not supported.
22066   */
22067
22068  Html5.isSupported = function () {
22069    // IE with no Media Player is a LIAR! (#984)
22070    try {
22071      Html5.TEST_VID.volume = 0.5;
22072    } catch (e) {
22073      return false;
22074    }
22075
22076    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
22077  };
22078  /**
22079   * Check if the tech can support the given type
22080   *
22081   * @param {string} type
22082   *        The mimetype to check
22083   * @return {string} 'probably', 'maybe', or '' (empty string)
22084   */
22085
22086
22087  Html5.canPlayType = function (type) {
22088    return Html5.TEST_VID.canPlayType(type);
22089  };
22090  /**
22091   * Check if the tech can support the given source
22092   *
22093   * @param {Object} srcObj
22094   *        The source object
22095   * @param {Object} options
22096   *        The options passed to the tech
22097   * @return {string} 'probably', 'maybe', or '' (empty string)
22098   */
22099
22100
22101  Html5.canPlaySource = function (srcObj, options) {
22102    return Html5.canPlayType(srcObj.type);
22103  };
22104  /**
22105   * Check if the volume can be changed in this browser/device.
22106   * Volume cannot be changed in a lot of mobile devices.
22107   * Specifically, it can't be changed from 1 on iOS.
22108   *
22109   * @return {boolean}
22110   *         - True if volume can be controlled
22111   *         - False otherwise
22112   */
22113
22114
22115  Html5.canControlVolume = function () {
22116    // IE will error if Windows Media Player not installed #3315
22117    try {
22118      var volume = Html5.TEST_VID.volume;
22119      Html5.TEST_VID.volume = volume / 2 + 0.1;
22120      return volume !== Html5.TEST_VID.volume;
22121    } catch (e) {
22122      return false;
22123    }
22124  };
22125  /**
22126   * Check if the volume can be muted in this browser/device.
22127   * Some devices, e.g. iOS, don't allow changing volume
22128   * but permits muting/unmuting.
22129   *
22130   * @return {bolean}
22131   *      - True if volume can be muted
22132   *      - False otherwise
22133   */
22134
22135
22136  Html5.canMuteVolume = function () {
22137    try {
22138      var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
22139      // work, so we want to set both property and attribute
22140
22141      Html5.TEST_VID.muted = !muted;
22142
22143      if (Html5.TEST_VID.muted) {
22144        setAttribute(Html5.TEST_VID, 'muted', 'muted');
22145      } else {
22146        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
22147      }
22148
22149      return muted !== Html5.TEST_VID.muted;
22150    } catch (e) {
22151      return false;
22152    }
22153  };
22154  /**
22155   * Check if the playback rate can be changed in this browser/device.
22156   *
22157   * @return {boolean}
22158   *         - True if playback rate can be controlled
22159   *         - False otherwise
22160   */
22161
22162
22163  Html5.canControlPlaybackRate = function () {
22164    // Playback rate API is implemented in Android Chrome, but doesn't do anything
22165    // https://github.com/videojs/video.js/issues/3180
22166    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
22167      return false;
22168    } // IE will error if Windows Media Player not installed #3315
22169
22170
22171    try {
22172      var playbackRate = Html5.TEST_VID.playbackRate;
22173      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
22174      return playbackRate !== Html5.TEST_VID.playbackRate;
22175    } catch (e) {
22176      return false;
22177    }
22178  };
22179  /**
22180   * Check if we can override a video/audio elements attributes, with
22181   * Object.defineProperty.
22182   *
22183   * @return {boolean}
22184   *         - True if builtin attributes can be overridden
22185   *         - False otherwise
22186   */
22187
22188
22189  Html5.canOverrideAttributes = function () {
22190    // if we cannot overwrite the src/innerHTML property, there is no support
22191    // iOS 7 safari for instance cannot do this.
22192    try {
22193      var noop = function noop() {};
22194
22195      Object.defineProperty(document.createElement('video'), 'src', {
22196        get: noop,
22197        set: noop
22198      });
22199      Object.defineProperty(document.createElement('audio'), 'src', {
22200        get: noop,
22201        set: noop
22202      });
22203      Object.defineProperty(document.createElement('video'), 'innerHTML', {
22204        get: noop,
22205        set: noop
22206      });
22207      Object.defineProperty(document.createElement('audio'), 'innerHTML', {
22208        get: noop,
22209        set: noop
22210      });
22211    } catch (e) {
22212      return false;
22213    }
22214
22215    return true;
22216  };
22217  /**
22218   * Check to see if native `TextTrack`s are supported by this browser/device.
22219   *
22220   * @return {boolean}
22221   *         - True if native `TextTrack`s are supported.
22222   *         - False otherwise
22223   */
22224
22225
22226  Html5.supportsNativeTextTracks = function () {
22227    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
22228  };
22229  /**
22230   * Check to see if native `VideoTrack`s are supported by this browser/device
22231   *
22232   * @return {boolean}
22233   *        - True if native `VideoTrack`s are supported.
22234   *        - False otherwise
22235   */
22236
22237
22238  Html5.supportsNativeVideoTracks = function () {
22239    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
22240  };
22241  /**
22242   * Check to see if native `AudioTrack`s are supported by this browser/device
22243   *
22244   * @return {boolean}
22245   *        - True if native `AudioTrack`s are supported.
22246   *        - False otherwise
22247   */
22248
22249
22250  Html5.supportsNativeAudioTracks = function () {
22251    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
22252  };
22253  /**
22254   * An array of events available on the Html5 tech.
22255   *
22256   * @private
22257   * @type {Array}
22258   */
22259
22260
22261  Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
22262  /**
22263   * Boolean indicating whether the `Tech` supports volume control.
22264   *
22265   * @type {boolean}
22266   * @default {@link Html5.canControlVolume}
22267   */
22268
22269  /**
22270   * Boolean indicating whether the `Tech` supports muting volume.
22271   *
22272   * @type {bolean}
22273   * @default {@link Html5.canMuteVolume}
22274   */
22275
22276  /**
22277   * Boolean indicating whether the `Tech` supports changing the speed at which the media
22278   * plays. Examples:
22279   *   - Set player to play 2x (twice) as fast
22280   *   - Set player to play 0.5x (half) as fast
22281   *
22282   * @type {boolean}
22283   * @default {@link Html5.canControlPlaybackRate}
22284   */
22285
22286  /**
22287   * Boolean indicating whether the `Tech` supports the `sourceset` event.
22288   *
22289   * @type {boolean}
22290   * @default
22291   */
22292
22293  /**
22294   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
22295   *
22296   * @type {boolean}
22297   * @default {@link Html5.supportsNativeTextTracks}
22298   */
22299
22300  /**
22301   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
22302   *
22303   * @type {boolean}
22304   * @default {@link Html5.supportsNativeVideoTracks}
22305   */
22306
22307  /**
22308   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
22309   *
22310   * @type {boolean}
22311   * @default {@link Html5.supportsNativeAudioTracks}
22312   */
22313
22314  [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
22315    var key = _ref[0],
22316        fn = _ref[1];
22317    defineLazyProperty(Html5.prototype, key, function () {
22318      return Html5[fn]();
22319    }, true);
22320  });
22321  /**
22322   * Boolean indicating whether the `HTML5` tech currently supports the media element
22323   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
22324   * false there. Everywhere else this should be true.
22325   *
22326   * @type {boolean}
22327   * @default
22328   */
22329
22330  Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
22331  //       Is this true?
22332
22333  /**
22334   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
22335   * when going into fullscreen.
22336   *
22337   * @type {boolean}
22338   * @default
22339   */
22340
22341  Html5.prototype.featuresFullscreenResize = true;
22342  /**
22343   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
22344   * If this is false, manual `progress` events will be triggered instead.
22345   *
22346   * @type {boolean}
22347   * @default
22348   */
22349
22350  Html5.prototype.featuresProgressEvents = true;
22351  /**
22352   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
22353   * If this is false, manual `timeupdate` events will be triggered instead.
22354   *
22355   * @default
22356   */
22357
22358  Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- //
22359
22360  var canPlayType;
22361
22362  Html5.patchCanPlayType = function () {
22363    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
22364    // Firefox and Chrome report correctly
22365    if (ANDROID_VERSION >
vendor: 14,811 bytes, lines 22365-22757
22365= 4.0 && !IS_FIREFOX && !IS_CHROME) {
22366      canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
22367
22368      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
22369        var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
22370
22371        if (type && mpegurlRE.test(type)) {
22372          return 'maybe';
22373        }
22374
22375        return canPlayType.call(this, type);
22376      };
22377    }
22378  };
22379
22380  Html5.unpatchCanPlayType = function () {
22381    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
22382
22383    if (canPlayType) {
22384      Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
22385    }
22386
22387    return r;
22388  }; // by default, patch the media element
22389
22390
22391  Html5.patchCanPlayType();
22392
22393  Html5.disposeMediaElement = function (el) {
22394    if (!el) {
22395      return;
22396    }
22397
22398    if (el.parentNode) {
22399      el.parentNode.removeChild(el);
22400    } // remove any child track or source nodes to prevent their loading
22401
22402
22403    while (el.hasChildNodes()) {
22404      el.removeChild(el.firstChild);
22405    } // remove any src reference. not setting `src=''` because that causes a warning
22406    // in firefox
22407
22408
22409    el.removeAttribute('src'); // force the media element to update its loading state by calling load()
22410    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
22411
22412    if (typeof el.load === 'function') {
22413      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
22414      (function () {
22415        try {
22416          el.load();
22417        } catch (e) {// not supported
22418        }
22419      })();
22420    }
22421  };
22422
22423  Html5.resetMediaElement = function (el) {
22424    if (!el) {
22425      return;
22426    }
22427
22428    var sources = el.querySelectorAll('source');
22429    var i = sources.length;
22430
22431    while (i--) {
22432      el.removeChild(sources[i]);
22433    } // remove any src reference.
22434    // not setting `src=''` because that throws an error
22435
22436
22437    el.removeAttribute('src');
22438
22439    if (typeof el.load === 'function') {
22440      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
22441      (function () {
22442        try {
22443          el.load();
22444        } catch (e) {// satisfy linter
22445        }
22446      })();
22447    }
22448  };
22449  /* Native HTML5 element property wrapping ----------------------------------- */
22450  // Wrap native boolean attributes with getters that check both property and attribute
22451  // The list is as followed:
22452  // muted, defaultMuted, autoplay, controls, loop, playsinline
22453
22454
22455  [
22456  /**
22457   * Get the value of `muted` from the media element. `muted` indicates
22458   * that the volume for the media should be set to silent. This does not actually change
22459   * the `volume` attribute.
22460   *
22461   * @method Html5#muted
22462   * @return {boolean}
22463   *         - True if the value of `volume` should be ignored and the audio set to silent.
22464   *         - False if the value of `volume` should be used.
22465   *
22466   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
22467   */
22468  'muted',
22469  /**
22470   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
22471   * whether the media should start muted or not. Only changes the default state of the
22472   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
22473   * current state.
22474   *
22475   * @method Html5#defaultMuted
22476   * @return {boolean}
22477   *         - The value of `defaultMuted` from the media element.
22478   *         - True indicates that the media should start muted.
22479   *         - False indicates that the media should not start muted
22480   *
22481   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
22482   */
22483  'defaultMuted',
22484  /**
22485   * Get the value of `autoplay` from the media element. `autoplay` indicates
22486   * that the media should start to play as soon as the page is ready.
22487   *
22488   * @method Html5#autoplay
22489   * @return {boolean}
22490   *         - The value of `autoplay` from the media element.
22491   *         - True indicates that the media should start as soon as the page loads.
22492   *         - False indicates that the media should not start as soon as the page loads.
22493   *
22494   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
22495   */
22496  'autoplay',
22497  /**
22498   * Get the value of `controls` from the media element. `controls` indicates
22499   * whether the native media controls should be shown or hidden.
22500   *
22501   * @method Html5#controls
22502   * @return {boolean}
22503   *         - The value of `controls` from the media element.
22504   *         - True indicates that native controls should be showing.
22505   *         - False indicates that native controls should be hidden.
22506   *
22507   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
22508   */
22509  'controls',
22510  /**
22511   * Get the value of `loop` from the media element. `loop` indicates
22512   * that the media should return to the start of the media and continue playing once
22513   * it reaches the end.
22514   *
22515   * @method Html5#loop
22516   * @return {boolean}
22517   *         - The value of `loop` from the media element.
22518   *         - True indicates that playback should seek back to start once
22519   *           the end of a media is reached.
22520   *         - False indicates that playback should not loop back to the start when the
22521   *           end of the media is reached.
22522   *
22523   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
22524   */
22525  'loop',
22526  /**
22527   * Get the value of `playsinline` from the media element. `playsinline` indicates
22528   * to the browser that non-fullscreen playback is preferred when fullscreen
22529   * playback is the native default, such as in iOS Safari.
22530   *
22531   * @method Html5#playsinline
22532   * @return {boolean}
22533   *         - The value of `playsinline` from the media element.
22534   *         - True indicates that the media should play inline.
22535   *         - False indicates that the media should not play inline.
22536   *
22537   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
22538   */
22539  'playsinline'].forEach(function (prop) {
22540    Html5.prototype[prop] = function () {
22541      return this.el_[prop] || this.el_.hasAttribute(prop);
22542    };
22543  }); // Wrap native boolean attributes with setters that set both property and attribute
22544  // The list is as followed:
22545  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
22546  // setControls is special-cased above
22547
22548  [
22549  /**
22550   * Set the value of `muted` on the media element. `muted` indicates that the current
22551   * audio level should be silent.
22552   *
22553   * @method Html5#setMuted
22554   * @param {boolean} muted
22555   *        - True if the audio should be set to silent
22556   *        - False otherwise
22557   *
22558   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
22559   */
22560  'muted',
22561  /**
22562   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
22563   * audio level should be silent, but will only effect the muted level on intial playback..
22564   *
22565   * @method Html5.prototype.setDefaultMuted
22566   * @param {boolean} defaultMuted
22567   *        - True if the audio should be set to silent
22568   *        - False otherwise
22569   *
22570   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
22571   */
22572  'defaultMuted',
22573  /**
22574   * Set the value of `autoplay` on the media element. `autoplay` indicates
22575   * that the media should start to play as soon as the page is ready.
22576   *
22577   * @method Html5#setAutoplay
22578   * @param {boolean} autoplay
22579   *         - True indicates that the media should start as soon as the page loads.
22580   *         - False indicates that the media should not start as soon as the page loads.
22581   *
22582   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
22583   */
22584  'autoplay',
22585  /**
22586   * Set the value of `loop` on the media element. `loop` indicates
22587   * that the media should return to the start of the media and continue playing once
22588   * it reaches the end.
22589   *
22590   * @method Html5#setLoop
22591   * @param {boolean} loop
22592   *         - True indicates that playback should seek back to start once
22593   *           the end of a media is reached.
22594   *         - False indicates that playback should not loop back to the start when the
22595   *           end of the media is reached.
22596   *
22597   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
22598   */
22599  'loop',
22600  /**
22601   * Set the value of `playsinline` from the media element. `playsinline` indicates
22602   * to the browser that non-fullscreen playback is preferred when fullscreen
22603   * playback is the native default, such as in iOS Safari.
22604   *
22605   * @method Html5#setPlaysinline
22606   * @param {boolean} playsinline
22607   *         - True indicates that the media should play inline.
22608   *         - False indicates that the media should not play inline.
22609   *
22610   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
22611   */
22612  'playsinline'].forEach(function (prop) {
22613    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
22614      this.el_[prop] = v;
22615
22616      if (v) {
22617        this.el_.setAttribute(prop, prop);
22618      } else {
22619        this.el_.removeAttribute(prop);
22620      }
22621    };
22622  }); // Wrap native properties with a getter
22623  // The list is as followed
22624  // paused, currentTime, buffered, volume, poster, preload, error, seeking
22625  // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
22626  // readyState, videoWidth, videoHeight
22627
22628  [
22629  /**
22630   * Get the value of `paused` from the media element. `paused` indicates whether the media element
22631   * is currently paused or not.
22632   *
22633   * @method Html5#paused
22634   * @return {boolean}
22635   *         The value of `paused` from the media element.
22636   *
22637   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
22638   */
22639  'paused',
22640  /**
22641   * Get the value of `currentTime` from the media element. `currentTime` indicates
22642   * the current second that the media is at in playback.
22643   *
22644   * @method Html5#currentTime
22645   * @return {number}
22646   *         The value of `currentTime` from the media element.
22647   *
22648   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
22649   */
22650  'currentTime',
22651  /**
22652   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
22653   * object that represents the parts of the media that are already downloaded and
22654   * available for playback.
22655   *
22656   * @method Html5#buffered
22657   * @return {TimeRange}
22658   *         The value of `buffered` from the media element.
22659   *
22660   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
22661   */
22662  'buffered',
22663  /**
22664   * Get the value of `volume` from the media element. `volume` indicates
22665   * the current playback volume of audio for a media. `volume` will be a value from 0
22666   * (silent) to 1 (loudest and default).
22667   *
22668   * @method Html5#volume
22669   * @return {number}
22670   *         The value of `volume` from the media element. Value will be between 0-1.
22671   *
22672   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22673   */
22674  'volume',
22675  /**
22676   * Get the value of `poster` from the media element. `poster` indicates
22677   * that the url of an image file that can/will be shown when no media data is available.
22678   *
22679   * @method Html5#poster
22680   * @return {string}
22681   *         The value of `poster` from the media element. Value will be a url to an
22682   *         image.
22683   *
22684   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
22685   */
22686  'poster',
22687  /**
22688   * Get the value of `preload` from the media element. `preload` indicates
22689   * what should download before the media is interacted with. It can have the following
22690   * values:
22691   * - none: nothing should be downloaded
22692   * - metadata: poster and the first few frames of the media may be downloaded to get
22693   *   media dimensions and other metadata
22694   * - auto: allow the media and metadata for the media to be downloaded before
22695   *    interaction
22696   *
22697   * @method Html5#preload
22698   * @return {string}
22699   *         The value of `preload` from the media element. Will be 'none', 'metadata',
22700   *         or 'auto'.
22701   *
22702   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22703   */
22704  'preload',
22705  /**
22706   * Get the value of the `error` from the media element. `error` indicates any
22707   * MediaError that may have occurred during playback. If error returns null there is no
22708   * current error.
22709   *
22710   * @method Html5#error
22711   * @return {MediaError|null}
22712   *         The value of `error` from the media element. Will be `MediaError` if there
22713   *         is a current error and null otherwise.
22714   *
22715   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
22716   */
22717  'error',
22718  /**
22719   * Get the value of `seeking` from the media element. `seeking` indicates whether the
22720   * media is currently seeking to a new position or not.
22721   *
22722   * @method Html5#seeking
22723   * @return {boolean}
22724   *         - The value of `seeking` from the media element.
22725   *         - True indicates that the media is currently seeking to a new position.
22726   *         - False indicates that the media is not seeking to a new position at this time.
22727   *
22728   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
22729   */
22730  'seeking',
22731  /**
22732   * Get the value of `seekable` from the media element. `seekable` returns a
22733   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
22734   *
22735   * @method Html5#seekable
22736   * @return {TimeRange}
22737   *         The value of `seekable` from the media element. A `TimeRange` object
22738   *         indicating the current ranges of time that can be seeked to.
22739   *
22740   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
22741   */
22742  'seekable',
22743  /**
22744   * Get the value of `ended` from the media element. `ended` indicates whether
22745   * the media has reached the end or not.
22746   *
22747   * @method Html5#ended
22748   * @return {boolean}
22749   *         - The value of `ended` from the media element.
22750   *         - True indicates that the media has ended.
22751   *         - False indicates that the media has not ended.
22752   *
22753   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
22754   */
22755  'ended',
22756  /**
22757   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22758   * the rate at which the media is currently playing back. Examples:
22759   *   - if playbackRate is set to 2, media will play twice as fast.
22760   *   - if playbackRate is set to 0.5, media will play half as fast.
22761   *
22762   * @method Html5#playbackRate
22763   * @return {number}
22764   *         The value of `playbackRate` from the media element. A number indicating
22765   *         the current playback speed of the media, where 1 is normal speed.
22766   *
22767   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22768   */
22769  'playbackRate',
22770  /**
22771   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
22772   * the rate at which the media is currently playing back. This value will not indicate the current
22773   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
22774   *
22775   * Examples:
22776   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
22777   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
22778   *
22779   * @method Html5.prototype.defaultPlaybackRate
22780   * @return {number}
22781   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22782   *         the current playback speed of the media, where 1 is normal speed.
22783   *
22784   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22785   */
22786  'defaultPlaybackRate',
22787  /**
22788   * Get the value of `played` from the media element. `played` returns a `TimeRange`
22789   * object representing points in the media timeline that have been played.
22790   *
22791   * @method Html5#played
22792   * @return {TimeRange}
22793   *         The value of `played` from the media element. A `TimeRange` object indicating
22794   *         the ranges of time that have been played.
22795   *
22796   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
22797   */
22798  'played',
22799  /**
22800   * Get the value of `networkState` from the media element. `networkState` indicates
22801   * the current network state. It returns an enumeration from the following list:
22802   * - 0: NETWORK_EMPTY
22803   * - 1: NETWORK_IDLE
22804   * - 2: NETWORK_LOADING
22805   * - 3: NETWORK_NO_SOURCE
22806   *
22807   * @method Html5#networkState
22808   * @return {number}
22809   *         The value of `networkState` from the media element. This will be a number
22810   *         from the list in the description.
22811   *
22812   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
22813   */
22814  'networkState',
22815  /**
22816   * Get the value of `readyState` from the media element. `readyState` indicates
22817   * the current state of the media element. It returns an enumeration from the
22818   * following list:
22819   * - 0: HAVE_NOTHING
22820   * - 1: HAVE_METADATA
22821   * - 2: HAVE_CURRENT_DATA
22822   * - 3: HAVE_FUTURE_DATA
22823   * - 4: HAVE_ENOUGH_DATA
22824   *
22825   * @method Html5#readyState
22826   * @return {number}
22827   *         The value of `readyState` from the media element. This will be a number
22828   *         from the list in the description.
22829   *
22830   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
22831   */
22832  'readyState',
22833  /**
22834   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
22835   * the current width of the video in css pixels.
22836   *
22837   * @method Html5#videoWidth
22838   * @return {number}
22839   *         The value of `videoWidth` from the video element. This will be a number
22840   *         in css pixels.
22841   *
22842   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22843   */
22844  'videoWidth',
22845  /**
22846   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
22847   * the current height of the video in css pixels.
22848   *
22849   * @method Html5#videoHeight
22850   * @return {number}
22851   *         The value of `videoHeight` from the video element. This will be a number
22852   *         in css pixels.
22853   *
22854   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22855   */
22856  'videoHeight'].forEach(function (prop) {
22857    Html5.prototype[prop] = function () {
22858      return this.el_[prop];
22859    };
22860  }); // Wrap native properties with a setter in this format:
22861  // set + toTitleCase(name)
22862  // The list is as follows:
22863  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
22864
22865  [
22866  /**
22867   * Set the value of `volume` on the media element. `volume` indicates the current
22868   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
22869   * so on.
22870   *
22871   * @method Html5#setVolume
22872   * @param {number} percentAsDecimal
22873   *        The volume percent as a decimal. Valid range is from 0-1.
22874   *
22875   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22876   */
22877  'volume',
22878  /**
22879   * Set the value of `src` on the media element. `src` indicates the current
22880   * {@link Tech~SourceObject} for the media.
22881   *
22882   * @method Html5#setSrc
22883   * @param {Tech~SourceObject} src
22884   *        The source object to set as the current source.
22885   *
22886   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
22887   */
22888  'src',
22889  /**
22890   * Set the value of `poster` on the media element. `poster` is the url to
22891   * an image file that can/will be shown when no media data is available.
22892   *
22893   * @method Html5#setPoster
22894   * @param {string} poster
22895   *        The url to an image that should be used as the `poster` for the media
22896   *        element.
22897   *
22898   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
22899   */
22900  'poster',
22901  /**
22902   * Set the value of `preload` on the media element. `preload` indicates
22903   * what should download before the media is interacted with. It can have the following
22904   * values:
22905   * - none: nothing should be downloaded
22906   * - metadata: poster and the first few frames of the media may be downloaded to get
22907   *   media dimensions and other metadata
22908   * - auto: allow the media and metadata for the media to be downloaded before
22909   *    interaction
22910   *
22911   * @method Html5#setPreload
22912   * @param {string} preload
22913   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
22914   *         or 'auto'.
22915   *
22916   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22917   */
22918  'preload',
22919  /**
22920   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
22921   * the rate at which the media should play back. Examples:
22922   *   - if playbackRate is set to 2, media will play twice as fast.
22923   *   - if playbackRate is set to 0.5, media will play half as fast.
22924   *
22925   * @method Html5#setPlaybackRate
22926   * @return {number}
22927   *         The value of `playbackRate` from the media element. A number indicating
22928   *         the current playback speed of the media, where 1 is normal speed.
22929   *
22930   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22931   */
22932  'playbackRate',
22933  /**
22934   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
22935   * the rate at which the media should play back upon initial startup. Changing this value
22936   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
22937   *
22938   * Example Values:
22939   *   - if playbackRate is set to 2, media will play twice as fast.
22940   *   - if playbackRate is set to 0.5, media will play half as fast.
22941   *
22942   * @method Html5.prototype.setDefaultPlaybackRate
22943   * @return {number}
22944   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22945   *         the current playback speed of the media, where 1 is normal speed.
22946   *
22947   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
22948   */
22949  'defaultPlaybackRate'].forEach(function (prop) {
22950    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
22951      this.el_[prop] = v;
22952    };
22953  }); // wrap native functions with a function
22954  // The list is as follows:
22955  // pause, load, play
22956
22957  [
22958  /**
22959   * A wrapper around the media elements `pause` function. This will call the `HTML5`
22960   * media elements `pause` function.
22961   *
22962   * @method Html5#pause
22963   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
22964   */
22965  'pause',
22966  /**
22967   * A wrapper around the media elements `load` function. This will call the `HTML5`s
22968   * media element `load` function.
22969   *
22970   * @method Html5#load
22971   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
22972   */
22973  'load',
22974  /**
22975   * A wrapper around the media elements `play` function. This will call the `HTML5`s
22976   * media element `play` function.
22977   *
22978   * @method Html5#play
22979   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
22980   */
22981  'play'].forEach(function (prop) {
22982    Html5.prototype[prop] = function () {
22983      return this.el_[prop]();
22984    };
22985  });
22986  Tech.withSourceHandlers(Html5);
22987  /**
22988   * Native source handler for Html5, simply passes the source to the media element.
22989   *
22990   * @property {Tech~SourceObject} source
22991   *        The source object
22992   *
22993   * @property {Html5} tech
22994   *        The instance of the HTML5 tech.
22995   */
22996
22997  Html5.nativeSourceHandler = {};
22998  /**
22999   * Check if the media element can play the given mime type.
23000   *
23001   * @param {string} type
23002   *        The mimetype to check
23003   *
23004   * @return {string}
23005   *         'probably', 'maybe', or '' (empty string)
23006   */
23007
23008  Html5.nativeSourceHandler.canPlayType = function (type) {
23009    // IE without MediaPlayer throws an error (#519)
23010    try {
23011      return Html5.TEST_VID.canPlayType(type);
23012    } catch (e) {
23013      return '';
23014    }
23015  };
23016  /**
23017   * Check if the media element can handle a source natively.
23018   *
23019   * @param {Tech~SourceObject} source
23020   *         The source object
23021   *
23022   * @param {Object} [options]
23023   *         Options to be passed to the tech.
23024   *
23025   * @return {string}
23026   *         'probably', 'maybe', or '' (empty string).
23027   */
23028
23029
23030  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
23031    // If a type was provided we should rely on that
23032    if (source.type) {
23033      return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
23034    } else if (source.src) {
23035      var ext = getFileExtension(source.src);
23036      return Html5.nativeSourceHandler.canPlayType("video/" + ext);
23037    }
23038
23039    return '';
23040  };
23041  /**
23042   * Pass the source to the native media element.
23043   *
23044   * @param {Tech~SourceObject} source
23045   *        The source object
23046   *
23047   * @param {Html5} tech
23048   *        The instance of the Html5 tech
23049   *
23050   * @param {Object} [options]
23051   *        The options to pass to the source
23052   */
23053
23054
23055  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
23056    tech.setSrc(source.src);
23057  };
23058  /**
23059   * A noop for the native dispose function, as cleanup is not needed.
23060   */
23061
23062
23063  Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
23064
23065
23066  Html5.registerSourceHandler(Html5.nativeSourceHandler);
23067  Tech.registerTech('Html5', Html5);
23068
23069  // on the player when they happen
23070
23071  var TECH_EVENTS_RETRIGGER = [
23072  /**
23073   * Fired while the user agent is downloading media data.
23074   *
23075   * @event Player#progress
23076   * @type {EventTarget~Event}
23077   */
23078
23079  /**
23080   * Retrigger the `progress` event that was triggered by the {@link Tech}.
23081   *
23082   * @private
23083   * @method Player#handleTechProgress_
23084   * @fires Player#progress
23085   * @listens Tech#progress
23086   */
23087  'progress',
23088  /**
23089   * Fires when the loading of an audio/video is aborted.
23090   *
23091   * @event Player#abort
23092   * @type {EventTarget~Event}
23093   */
23094
23095  /**
23096   * Retrigger the `abort` event that was triggered by the {@link Tech}.
23097   *
23098   * @private
23099   * @method Player#handleTechAbort_
23100   * @fires Player#abort
23101   * @listens Tech#abort
23102   */
23103  'abort',
23104  /**
23105   * Fires when the browser is intentionally not getting media data.
23106   *
23107   * @event Player#suspend
23108   * @type {EventTarget~Event}
23109   */
23110
23111  /**
23112   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
23113   *
23114   * @private
23115   * @method Player#handleTechSuspend_
23116   * @fires Player#suspend
23117   * @listens Tech#suspend
23118   */
23119  'suspend',
23120  /**
23121   * Fires when the current playlist is empty.
23122   *
23123   * @event Player#emptied
23124   * @type {EventTarget~Event}
23125   */
23126
23127  /**
23128   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
23129   *
23130   * @private
23131   * @method Player#handleTechEmptied_
23132   * @fires Player#emptied
23133   * @listens Tech#emptied
23134   */
23135  'emptied',
23136  /**
23137   * Fires when the browser is trying to get media data, but data is not available.
23138   *
23139   * @event Player#stalled
23140   * @type {EventTarget~Event}
23141   */
23142
23143  /**
23144   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
23145   *
23146   * @private
23147   * @method Player#handleTechStalled_
23148   * @fires Player#stalled
23149   * @listens Tech#stalled
23150   */
23151  'stalled',
23152  /**
23153   * Fires when the browser has loaded meta data for the audio/video.
23154   *
23155   * @event Player#loadedmetadata
23156   * @type {EventTarget~Event}
23157   */
23158
23159  /**
23160   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
23161   *
23162   * @private
23163   * @method Player#handleTechLoadedmetadata_
23164   * @fires Player#loadedmetadata
23165   * @listens Tech#loadedmetadata
23166   */
23167  'loadedmetadata',
23168  /**
23169   * Fires when the browser has loaded the current frame of the audio/video.
23170   *
23171   * @event Player#loadeddata
23172   * @type {event}
23173   */
23174
23175  /**
23176   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
23177   *
23178   * @private
23179   * @method Player#handleTechLoaddeddata_
23180   * @fires Player#loadeddata
23181   * @listens Tech#loadeddata
23182   */
23183  'loadeddata',
23184  /**
23185   * Fires when the current playback position has changed.
23186   *
23187   * @event Player#timeupdate
23188   * @type {event}
23189   */
23190
23191  /**
23192   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
23193   *
23194   * @private
23195   * @method Player#handleTechTimeUpdate_
23196   * @fires Player#timeupdate
23197   * @listens Tech#timeupdate
23198   */
23199  'timeupdate',
23200  /**
23201   * Fires when the video's intrinsic dimensions change
23202   *
23203   * @event Player#resize
23204   * @type {event}
23205   */
23206
23207  /**
23208   * Retrigger the `resize` event that was triggered by the {@link Tech}.
23209   *
23210   * @private
23211   * @method Player#handleTechResize_
23212   * @fires Player#resize
23213   * @listens Tech#resize
23214   */
23215  'resize',
23216  /**
23217   * Fires when the volume has been changed
23218   *
23219   * @event Player#volumechange
23220   * @type {event}
23221   */
23222
23223  /**
23224   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
23225   *
23226   * @private
23227   * @method Player#handleTechVolumechange_
23228   * @fires Player#volumechange
23229   * @listens Tech#volumechange
23230   */
23231  'volumechange',
23232  /**
23233   * Fires when the text track has been changed
23234   *
23235   * @event Player#texttrackchange
23236   * @type {event}
23237   */
23238
23239  /**
23240   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
23241   *
23242   * @private
23243   * @method Player#handleTechTexttrackchange_
23244   * @fires Player#texttrackchange
23245   * @listens Tech#texttrackchange
23246   */
23247  'texttrackchange']; // events to queue when playback rate is zero
23248  // this is a hash for the sole purpose of mapping non-camel-cased event names
23249  // to camel-cased function names
23250
23251  var TECH_EVENTS_QUEUE = {
23252    canplay: 'CanPlay',
23253    canplaythrough: 'CanPlayThrough',
23254    playing: 'Playing',
23255    seeked: 'Seeked'
23256  };
23257  var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
23258  var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
23259  // grep: vjs-layout-x-small
23260  // grep: vjs-layout-small
23261  // grep: vjs-layout-medium
23262  // grep: vjs-layout-large
23263  // grep: vjs-layout-x-large
23264  // grep: vjs-layout-huge
23265
23266  BREAKPOINT_ORDER.forEach(function (k) {
23267    var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
23268    BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
23269  });
23270  var DEFAULT_BREAKPOINTS = {
23271    tiny: 210,
23272    xsmall: 320,
23273    small: 425,
23274    medium: 768,
23275    large: 1440,
23276    xlarge: 2560,
23277    huge: Infinity
23278  };
23279  /**
23280   * An instance of the `Player` class is created when any of the Video.js setup methods
23281   * are used to initialize a video.
23282   *
23283   * After an instance has been created it can be accessed globally in two ways:
23284   * 1. By calling `videojs('example_video_1');`
23285   * 2. By using it directly via  `videojs.players.example_video_1;`
23286   *
23287   * @extends Component
23288   */
23289
23290  var Player =
23291  /*#__PURE__*/
23292  function (_Component) {
23293    inheritsLoose(Player, _Component);
23294
23295    /**
23296     * Create an instance of this class.
23297     *
23298     * @param {Element} tag
23299     *        The original video DOM element used for configuring options.
23300     *
23301     * @param {Object} [options]
23302     *        Object of option names and values.
23303     *
23304     * @param {Component~ReadyCallback} [ready]
23305     *        Ready callback function.
23306     */
23307    function Player(tag, options, ready) {
23308      var _this;
23309
23310      // Make sure tag ID exists
23311      tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
23312      // The options argument overrides options set in the video tag
23313      // which overrides globally set options.
23314      // This latter part coincides with the load order
23315      // (tag must exist before Player)
23316
23317      options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
23318      // player properties first, and can't use `this` before `super()`
23319
23320      options.initChildren = false; // Same with creating the element
23321
23322      options.createEl = false; // don't auto mixin the evented mixin
23323
23324      options.evented = false; // we don't want the player to report touch activity on itself
23325      // see enableTouchActivity in Component
23326
23327      options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
23328
23329      if (!options.language) {
23330        if (typeof tag.closest === 'function') {
23331          var closest = tag.closest('[lang]');
23332
23333          if (closest && closest.getAttribute) {
23334            options.language = closest.getAttribute('lang');
23335          }
23336        } else {
23337          var element = tag;
23338
23339          while (element && element.nodeType === 1) {
23340            if (getAttributes(element).hasOwnProperty('lang')) {
23341              options.language = element.getAttribute('lang');
23342              break;
23343            }
23344
23345            element = element.parentNode;
23346          }
23347        }
23348      } // Run base component initializing with new options
23349
23350
23351      _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
23352
23353      _this.boundDocumentFullscreenChange_ = bind(assertThisInitialized(_this), _this.documentFullscreenChange_);
23354      _this.boundFullWindowOnEscKey_ = bind(assertThisInitialized(_this), _this.fullWindowOnEscKey); // default isFullscreen_ to false
23355
23356      _this.isFullscreen_ = false; // create logger
23357
23358      _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests
23359
23360      _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster
23361
23362      _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
23363      // and a seek is happening
23364
23365      _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
23366
23367      _this.isReady_ = false; // Init state hasStarted_
23368
23369      _this.hasStarted_ = false; // Init state userActive_
23370
23371      _this.userActive_ = false; // if the global option object was accidentally blown away by
23372      // someone, bail early with an informative error
23373
23374      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
23375        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
23376      } // Store the original tag used to set options
23377
23378
23379      _this.tag = tag; // Store the tag attributes used to restore html5 element
23380
23381      _this.tagAttributes = tag && getAttributes(tag); // Update current language
23382
23383      _this.language(_this.options_.language); // Update Supported Languages
23384
23385
23386      if (options.languages) {
23387        // Normalise player option languages to lowercase
23388        var languagesToLower = {};
23389        Object.getOwnPropertyNames(options.languages).forEach(function (name) {
23390          languagesToLower[name.toLowerCase()] = options.languages[name];
23391        });
23392        _this.languages_ = languagesToLower;
23393      } else {
23394        _this.languages_ = Player.prototype.options_.languages;
23395      }
23396
23397      _this.resetCache_(); // Set poster
23398
23399
23400      _this.poster_ = options.poster || ''; // Set controls
23401
23402      _this.controls_ = !!options.controls; // Original tag settings stored in options
23403      // now remove immediately so native controls don't flash.
23404      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
23405
23406      tag.controls = false;
23407      tag.removeAttribute('controls');
23408      _this.changingSrc_ = false;
23409      _this.playCallbacks_ = [];
23410      _this.playTerminatedQueue_ = []; // the attribute overrides the option
23411
23412      if (tag.hasAttribute('autoplay')) {
23413        _this.autoplay(true);
23414      } else {
23415        // otherwise use the setter to validate and
23416        // set the correct value.
23417        _this.autoplay(_this.options_.autoplay);
23418      } // check plugins
23419
23420
23421      if (options.plugins) {
23422        Object.keys(options.plugins).forEach(function (name) {
23423          if (typeof _this[name] !== 'function') {
23424            throw new Error("plugin \"" + name + "\" does not exist");
23425          }
23426        });
23427      }
23428      /*
23429       * Store the internal state of scrubbing
23430       *
23431       * @private
23432       * @return {Boolean} True if the user is scrubbing
23433       */
23434
23435
23436      _this.scrubbing_ = false;
23437      _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
23438
23439      evented(assertThisInitialized(_this), {
23440        eventBusKey: 'el_'
23441      }); // listen to document and player fullscreenchange handlers so we receive those events
23442      // before a user can receive them so we can update isFullscreen appropriately.
23443      // make sure that we listen to fullscreenchange events before everything else to make sure that
23444      // our isFullscreen method is updated properly for internal components as well as external.
23445
23446      if (_this.fsApi_.requestFullscreen) {
23447        on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
23448
23449        _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
23450      }
23451
23452      if (_this.fluid_) {
23453        _this.on('playerreset', _this.updateStyleEl_);
23454      } // We also want to pass the original player options to each component and plugin
23455      // as well so they don't need to reach back into the player for options later.
23456      // We also need to do another copy of this.options_ so we don't end up with
23457      // an infinite loop.
23458
23459
23460      var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
23461
23462      if (options.plugins) {
vendor: 4,134 bytes, lines 23463-23600
23463        Object.keys(options.plugins).forEach(function (name) {
23464          _this[name](options.plugins[name]);
23465        });
23466      }
23467
23468      _this.options_.playerOptions = playerOptionsCopy;
23469      _this.middleware_ = [];
23470
23471      _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
23472
23473
23474      _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
23475      // set because the element doesn't exist yet.
23476
23477
23478      if (_this.controls()) {
23479        _this.addClass('vjs-controls-enabled');
23480      } else {
23481        _this.addClass('vjs-controls-disabled');
23482      } // Set ARIA label and region role depending on player type
23483
23484
23485      _this.el_.setAttribute('role', 'region');
23486
23487      if (_this.isAudio()) {
23488        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
23489      } else {
23490        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
23491      }
23492
23493      if (_this.isAudio()) {
23494        _this.addClass('vjs-audio');
23495      }
23496
23497      if (_this.flexNotSupported_()) {
23498        _this.addClass('vjs-no-flex');
23499      } // TODO: Make this smarter. Toggle user state between touching/mousing
23500      // using events, since devices can have both touch and mouse events.
23501      // TODO: Make this check be performed again when the window switches between monitors
23502      // (See https://github.com/videojs/video.js/issues/5683)
23503
23504
23505      if (TOUCH_ENABLED) {
23506        _this.addClass('vjs-touch-enabled');
23507      } // iOS Safari has broken hover handling
23508
23509
23510      if (!IS_IOS) {
23511        _this.addClass('vjs-workinghover');
23512      } // Make player easily findable by ID
23513
23514
23515      Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins
23516
23517      var majorVersion = version.split('.')[0];
23518
23519      _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
23520      // like the control bar show themselves if needed
23521
23522
23523      _this.userActive(true);
23524
23525      _this.reportUserActivity();
23526
23527      _this.one('play', _this.listenForUserActivity_);
23528
23529      _this.on('stageclick', _this.handleStageClick_);
23530
23531      _this.on('keydown', _this.handleKeyDown);
23532
23533      _this.breakpoints(_this.options_.breakpoints);
23534
23535      _this.responsive(_this.options_.responsive);
23536
23537      return _this;
23538    }
23539    /**
23540     * Destroys the video player and does any necessary cleanup.
23541     *
23542     * This is especially helpful if you are dynamically adding and removing videos
23543     * to/from the DOM.
23544     *
23545     * @fires Player#dispose
23546     */
23547
23548
23549    var _proto = Player.prototype;
23550
23551    _proto.dispose = function dispose() {
23552      var _this2 = this;
23553
23554      /**
23555       * Called when the player is being disposed of.
23556       *
23557       * @event Player#dispose
23558       * @type {EventTarget~Event}
23559       */
23560      this.trigger('dispose'); // prevent dispose from being called twice
23561
23562      this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
23563
23564      off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
23565      off(document, 'keydown', this.boundFullWindowOnEscKey_);
23566
23567      if (this.styleEl_ && this.styleEl_.parentNode) {
23568        this.styleEl_.parentNode.removeChild(this.styleEl_);
23569        this.styleEl_ = null;
23570      } // Kill reference to this player
23571
23572
23573      Player.players[this.id_] = null;
23574
23575      if (this.tag && this.tag.player) {
23576        this.tag.player = null;
23577      }
23578
23579      if (this.el_ && this.el_.player) {
23580        this.el_.player = null;
23581      }
23582
23583      if (this.tech_) {
23584        this.tech_.dispose();
23585        this.isPosterFromTech_ = false;
23586        this.poster_ = '';
23587      }
23588
23589      if (this.playerElIngest_) {
23590        this.playerElIngest_ = null;
23591      }
23592
23593      if (this.tag) {
23594        this.tag = null;
23595      }
23596
23597      clearCacheForPlayer(this); // remove all event handlers for track lists
23598      // all tracks and track listeners are removed on
23599      // tech dispose
23600
vendor: 6,243 bytes, lines 23601-23762
23601      ALL.names.forEach(function (name) {
23602        var props = ALL[name];
23603
23604        var list = _this2[props.getterName](); // if it is not a native list
23605        // we have to manually remove event listeners
23606
23607
23608        if (list && list.off) {
23609          list.off();
23610        }
23611      }); // the actual .el_ is removed here
23612
23613      _Component.prototype.dispose.call(this);
23614    }
23615    /**
23616     * Create the `Player`'s DOM element.
23617     *
23618     * @return {Element}
23619     *         The DOM element that gets created.
23620     */
23621    ;
23622
23623    _proto.createEl = function createEl() {
23624      var tag = this.tag;
23625      var el;
23626      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
23627      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
23628
23629      if (playerElIngest) {
23630        el = this.el_ = tag.parentNode;
23631      } else if (!divEmbed) {
23632        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
23633      } // Copy over all the attributes from the tag, including ID and class
23634      // ID will now reference player box, not the video tag
23635
23636
23637      var attrs = getAttributes(tag);
23638
23639      if (divEmbed) {
23640        el = this.el_ = tag;
23641        tag = this.tag = document.createElement('video');
23642
23643        while (el.children.length) {
23644          tag.appendChild(el.firstChild);
23645        }
23646
23647        if (!hasClass(el, 'video-js')) {
23648          addClass(el, 'video-js');
23649        }
23650
23651        el.appendChild(tag);
23652        playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
23653        // to our new `video` element. This will move things like
23654        // `src` or `controls` that were set via js before the player
23655        // was initialized.
23656
23657        Object.keys(el).forEach(function (k) {
23658          try {
23659            tag[k] = el[k];
23660          } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it
23661          }
23662        });
23663      } // set tabindex to -1 to remove the video element from the focus order
23664
23665
23666      tag.setAttribute('tabindex', '-1');
23667      attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
23668      // for the same issue with Chrome (on Windows) with JAWS.
23669      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
23670      // Note that we can't detect if JAWS is being used, but this ARIA attribute
23671      //  doesn't change behavior of IE11 or Chrome if JAWS is not being used
23672
23673      if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
23674        tag.setAttribute('role', 'application');
23675        attrs.role = 'application';
23676      } // Remove width/height attrs from tag so CSS can make it 100% width/height
23677
23678
23679      tag.removeAttribute('width');
23680      tag.removeAttribute('height');
23681
23682      if ('width' in attrs) {
23683        delete attrs.width;
23684      }
23685
23686      if ('height' in attrs) {
23687        delete attrs.height;
23688      }
23689
23690      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
23691        // don't copy over the class attribute to the player element when we're in a div embed
23692        // the class is already set up properly in the divEmbed case
23693        // and we want to make sure that the `video-js` class doesn't get lost
23694        if (!(divEmbed && attr === 'class')) {
23695          el.setAttribute(attr, attrs[attr]);
23696        }
23697
23698        if (divEmbed) {
23699          tag.setAttribute(attr, attrs[attr]);
23700        }
23701      }); // Update tag id/class for use as HTML5 playback tech
23702      // Might think we should do this after embedding in container so .vjs-tech class
23703      // doesn't flash 100% width/height, but class only applies with .video-js parent
23704
23705      tag.playerId = tag.id;
23706      tag.id += '_html5_api';
23707      tag.className = 'vjs-tech'; // Make player findable on elements
23708
23709      tag.player = el.player = this; // Default state of video is paused
23710
23711      this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
23712      // of the player in a way that's still overrideable by CSS, just like the
23713      // video element
23714
23715      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
23716        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
23717        var defaultsStyleEl = $('.vjs-styles-defaults');
23718        var head = $('head');
23719        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
23720      }
23721
23722      this.fill_ = false;
23723      this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
23724
23725      this.width(this.options_.width);
23726      this.height(this.options_.height);
23727      this.fill(this.options_.fill);
23728      this.fluid(this.options_.fluid);
23729      this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
23730      // because IE doesn't hide them completely from screen readers.
23731
23732      var links = tag.getElementsByTagName('a');
23733
23734      for (var i = 0; i < links.length; i++) {
23735        var linkEl = links.item(i);
23736        addClass(linkEl, 'vjs-hidden');
23737        linkEl.setAttribute('hidden', 'hidden');
23738      } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
23739      // keep track of the original for later so we can know if the source originally failed
23740
23741
23742      tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
23743
23744      if (tag.parentNode && !playerElIngest) {
23745        tag.parentNode.insertBefore(el, tag);
23746      } // insert the tag as the first child of the player element
23747      // then manually add it to the children array so that this.addChild
23748      // will work properly for other components
23749      //
23750      // Breaks iPhone, fixed in HTML5 setup.
23751
23752
23753      prependTo(tag, el);
23754      this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
23755      // if it's been set to something different to the doc
23756
23757      this.el_.setAttribute('lang', this.language_);
23758      this.el_ = el;
23759      return el;
23760    }
23761    /**
23762     * A getter/setter for the `Player`'s width. Returns the player'
23762s configured value.
23763     * To get the current width use `currentWidth()`.
23764     *
23765     * @param {number} [value]
23766     *        The value to set the `Player`'s width to.
23767     *
23768     * @return {number}
23769     *         The current width of the `Player` when getting.
23770     */
23771    ;
23772
23773    _proto.width = function width(value) {
23774      return this.dimension('width', value);
23775    }
23776    /**
23777     * A getter/setter for the `Player`'s height. Returns the player's configured value.
23778     * To get the current height use `currentheight()`.
23779     *
23780     * @param {number} [value]
23781     *        The value to set the `Player`'s heigth to.
23782     *
23783     * @return {number}
23784     *         The current height of the `Player` when getting.
23785     */
23786    ;
23787
23788    _proto.height = function height(value) {
23789      return this.dimension('height', value);
23790    }
23791    /**
23792     * A getter/setter for the `Player`'s width & height.
23793     *
23794     * @param {string} dimension
23795     *        This string can be:
23796     *        - 'width'
23797     *        - 'height'
23798     *
23799     * @param {number} [value]
23800     *        Value for dimension specified in the first argument.
23801     *
23802     * @return {number}
23803     *         The dimension arguments value when getting (width/height).
23804     */
23805    ;
23806
23807    _proto.dimension = function dimension(_dimension, value) {
23808      var privDimension = _dimension + '_';
23809
23810      if (value === undefined) {
23811        return this[privDimension] || 0;
23812      }
23813
23814      if (value === '' || value === 'auto') {
23815        // If an empty string is given, reset the dimension to be automatic
23816        this[privDimension] = undefined;
23817        this.updateStyleEl_();
23818        return;
23819      }
23820
23821      var parsedVal = parseFloat(value);
23822
23823      if (isNaN(parsedVal)) {
23824        log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
23825        return;
23826      }
23827
23828      this[privDimension] = parsedVal;
23829      this.updateStyleEl_();
23830    }
23831    /**
23832     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
23833     *
23834     * Turning this on will turn off fill mode.
23835     *
23836     * @param {boolean} [bool]
23837     *        - A value of true adds the class.
23838     *        - A value of false removes the class.
23839     *        - No value will be a getter.
23840     *
23841     * @return {boolean|undefined}
23842     *         - The value of fluid when getting.
23843     *         - `undefined` when setting.
23844     */
23845    ;
23846
23847    _proto.fluid = function fluid(bool) {
23848      if (bool === undefined) {
23849        return !!this.fluid_;
23850      }
23851
23852      this.fluid_ = !!bool;
23853
23854      if (isEvented(this)) {
23855        this.off('playerreset', this.updateStyleEl_);
23856      }
23857
23858      if (bool) {
23859        this.addClass('vjs-fluid');
23860        this.fill(false);
23861        addEventedCallback(function () {
23862          this.on('playerreset', this.updateStyleEl_);
23863        });
23864      } else {
23865        this.removeClass('vjs-fluid');
23866      }
23867
23868      this.updateStyleEl_();
23869    }
23870    /**
23871     * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
23872     *
23873     * Turning this on will turn off fluid mode.
23874     *
23875     * @param {boolean} [bool]
23876     *        - A value of true adds the class.
23877     *        - A value of false removes the class.
23878     *        - No value will be a getter.
23879     *
23880     * @return {boolean|undefined}
23881     *         - The value of fluid when getting.
23882     *         - `undefined` when setting.
23883     */
23884    ;
23885
23886    _proto.fill = function fill(bool) {
23887      if (bool === undefined) {
23888        return !!this.fill_;
23889      }
23890
23891      this.fill_ = !!bool;
23892
23893      if (bool) {
23894        this.addClass('vjs-fill');
23895        this.fluid(false);
23896      } else {
23897        this.removeClass('vjs-fill');
23898      }
23899    }
23900    /**
23901     * Get/Set the aspect ratio
23902     *
23903     * @param {string} [ratio]
23904     *        Aspect ratio for player
23905     *
23906     * @return {string|undefined}
23907     *         returns the current aspect ratio when getting
23908     */
23909
23910    /**
23911     * A getter/setter for the `Player`'s aspect ratio.
23912     *
23913     * @param {string} [ratio]
23914     *        The value to set the `Player's aspect ratio to.
23915     *
23916     * @return {string|undefined}
23917     *         - The current aspect ratio of the `Player` when getting.
23918     *         - undefined when setting
23919     */
23920    ;
23921
23922    _proto.aspectRatio = function aspectRatio(ratio) {
23923      if (ratio === undefined) {
23924        return this.aspectRatio_;
23925      } // Check for width:height format
23926
23927
23928      if (!/^\d+\:\d+$/.test(ratio)) {
23929        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
23930      }
23931
23932      this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
23933      // because in fixed mode you could calculate width and height yourself.
23934
23935      this.fluid(true);
23936      this.updateStyleEl_();
23937    }
23938    /**
23939     * Update styles of the `Player` element (height, width and aspect ratio).
23940     *
23941     * @private
23942     * @listens Tech#loadedmetadata
23943     */
23944    ;
23945
23946    _proto.updateStyleEl_ = function updateStyleEl_() {
23947      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
23948        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
23949
23950        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
23951
23952        var techEl = this.tech_ && this.tech_.el();
23953
23954        if (techEl) {
23955          if (_width >= 0) {
23956            techEl.width = _width;
23957          }
23958
23959          if (_height >= 0) {
vendor: 14,623 bytes, lines 23960-24315
23960            techEl.height = _height;
23961          }
23962        }
23963
23964        return;
23965      }
23966
23967      var width;
23968      var height;
23969      var aspectRatio;
23970      var idClass; // The aspect ratio is either used directly or to calculate width and height.
23971
23972      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
23973        // Use any aspectRatio that's been specifically set
23974        aspectRatio = this.aspectRatio_;
23975      } else if (this.videoWidth() > 0) {
23976        // Otherwise try to get the aspect ratio from the video metadata
23977        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
23978      } else {
23979        // Or use a default. The video element's is 2:1, but 16:9 is more common.
23980        aspectRatio = '16:9';
23981      } // Get the ratio as a decimal we can use to calculate dimensions
23982
23983
23984      var ratioParts = aspectRatio.split(':');
23985      var ratioMultiplier = ratioParts[1] / ratioParts[0];
23986
23987      if (this.width_ !== undefined) {
23988        // Use any width that's been specifically set
23989        width = this.width_;
23990      } else if (this.height_ !== undefined) {
23991        // Or calulate the width from the aspect ratio if a height has been set
23992        width = this.height_ / ratioMultiplier;
23993      } else {
23994        // Or use the video's metadata, or use the video el's default of 300
23995        width = this.videoWidth() || 300;
23996      }
23997
23998      if (this.height_ !== undefined) {
23999        // Use any height that's been specifically set
24000        height = this.height_;
24001      } else {
24002        // Otherwise calculate the height from the ratio and the width
24003        height = width * ratioMultiplier;
24004      } // Ensure the CSS class is valid by starting with an alpha character
24005
24006
24007      if (/^[^a-zA-Z]/.test(this.id())) {
24008        idClass = 'dimensions-' + this.id();
24009      } else {
24010        idClass = this.id() + '-dimensions';
24011      } // Ensure the right class is still on the player for the style element
24012
24013
24014      this.addClass(idClass);
24015      setTextContent(this.styleEl_, "\n      ." + idClass + " {\n        width: " + width + "px;\n        height: " + height + "px;\n      }\n\n      ." + idClass + ".vjs-fluid {\n        padding-top: " + ratioMultiplier * 100 + "%;\n      }\n    ");
24016    }
24017    /**
24018     * Load/Create an instance of playback {@link Tech} including element
24019     * and API methods. Then append the `Tech` element in `Player` as a child.
24020     *
24021     * @param {string} techName
24022     *        name of the playback technology
24023     *
24024     * @param {string} source
24025     *        video source
24026     *
24027     * @private
24028     */
24029    ;
24030
24031    _proto.loadTech_ = function loadTech_(techName, source) {
24032      var _this3 = this;
24033
24034      // Pause and remove current playback technology
24035      if (this.tech_) {
24036        this.unloadTech_();
24037      }
24038
24039      var titleTechName = toTitleCase(techName);
24040      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
24041
24042      if (titleTechName !== 'Html5' && this.tag) {
24043        Tech.getTech('Html5').disposeMediaElement(this.tag);
24044        this.tag.player = null;
24045        this.tag = null;
24046      }
24047
24048      this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
24049
24050      this.isReady_ = false; // if autoplay is a string we pass false to the tech
24051      // because the player is going to handle autoplay on `loadstart`
24052
24053      var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
24054
24055      var techOptions = {
24056        source: source,
24057        autoplay: autoplay,
24058        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
24059        'playerId': this.id(),
24060        'techId': this.id() + "_" + camelTechName + "_api",
24061        'playsinline': this.options_.playsinline,
24062        'preload': this.options_.preload,
24063        'loop': this.options_.loop,
24064        'muted': this.options_.muted,
24065        'poster': this.poster(),
24066        'language': this.language(),
24067        'playerElIngest': this.playerElIngest_ || false,
24068        'vtt.js': this.options_['vtt.js'],
24069        'canOverridePoster': !!this.options_.techCanOverridePoster,
24070        'enableSourceset': this.options_.enableSourceset,
24071        'Promise': this.options_.Promise
24072      };
24073      ALL.names.forEach(function (name) {
24074        var props = ALL[name];
24075        techOptions[props.getterName] = _this3[props.privateName];
24076      });
24077      assign(techOptions, this.options_[titleTechName]);
24078      assign(techOptions, this.options_[camelTechName]);
24079      assign(techOptions, this.options_[techName.toLowerCase()]);
24080
24081      if (this.tag) {
24082        techOptions.tag = this.tag;
24083      }
24084
24085      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
24086        techOptions.startTime = this.cache_.currentTime;
24087      } // Initialize tech instance
24088
24089
24090      var TechClass = Tech.getTech(techName);
24091
24092      if (!TechClass) {
24093        throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
24094      }
24095
24096      this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
24097
24098      this.tech_.ready(bind(this, this.handleTechReady_), true);
24099      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
24100
24101      TECH_EVENTS_RETRIGGER.forEach(function (event) {
24102        _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
24103      });
24104      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
24105        _this3.on(_this3.tech_, event, function (eventObj) {
24106          if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
24107            _this3.queuedCallbacks_.push({
24108              callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
24109              event: eventObj
24110            });
24111
24112            return;
24113          }
24114
24115          _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
24116        });
24117      });
24118      this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
24119      this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
24120      this.on(this.tech_, 'waiting', this.handleTechWaiting_);
24121      this.on(this.tech_, 'ended', this.handleTechEnded_);
24122      this.on(this.tech_, 'seeking', this.handleTechSeeking_);
24123      this.on(this.tech_, 'play', this.handleTechPlay_);
24124      this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
24125      this.on(this.tech_, 'pause', this.handleTechPause_);
24126      this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
24127      this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
24128      this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_);
24129      this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_);
24130      this.on(this.tech_, 'error', this.handleTechError_);
24131      this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
24132      this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
24133      this.on(this.tech_, 'textdata', this.handleTechTextData_);
24134      this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
24135      this.usingNativeControls(this.techGet_('controls'));
24136
24137      if (this.controls() && !this.usingNativeControls()) {
24138        this.addTechControlsListeners_();
24139      } // Add the tech element in the DOM if it was not already there
24140      // Make sure to not insert the original video element if using Html5
24141
24142
24143      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
24144        prependTo(this.tech_.el(), this.el());
24145      } // Get rid of the original video tag reference after the first tech is loaded
24146
24147
24148      if (this.tag) {
24149        this.tag.player = null;
24150        this.tag = null;
24151      }
24152    }
24153    /**
24154     * Unload and dispose of the current playback {@link Tech}.
24155     *
24156     * @private
24157     */
24158    ;
24159
24160    _proto.unloadTech_ = function unloadTech_() {
24161      var _this4 = this;
24162
24163      // Save the current text tracks so that we can reuse the same text tracks with the next tech
24164      ALL.names.forEach(function (name) {
24165        var props = ALL[name];
24166        _this4[props.privateName] = _this4[props.getterName]();
24167      });
24168      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
24169      this.isReady_ = false;
24170      this.tech_.dispose();
24171      this.tech_ = false;
24172
24173      if (this.isPosterFromTech_) {
24174        this.poster_ = '';
24175        this.trigger('posterchange');
24176      }
24177
24178      this.isPosterFromTech_ = false;
24179    }
24180    /**
24181     * Return a reference to the current {@link Tech}.
24182     * It will print a warning by default about the danger of using the tech directly
24183     * but any argument that is passed in will silence the warning.
24184     *
24185     * @param {*} [safety]
24186     *        Anything passed in to silence the warning
24187     *
24188     * @return {Tech}
24189     *         The Tech
24190     */
24191    ;
24192
24193    _proto.tech = function tech(safety) {
24194      if (safety === undefined) {
24195        log.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n');
24196      }
24197
24198      return this.tech_;
24199    }
24200    /**
24201     * Set up click and touch listeners for the playback element
24202     *
24203     * - On desktops: a click on the video itself will toggle playback
24204     * - On mobile devices: a click on the video toggles controls
24205     *   which is done by toggling the user state between active and
24206     *   inactive
24207     * - A tap can signal that a user has become active or has become inactive
24208     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
24209     *   quick tap should hide them again (signaling the user is in an inactive
24210     *   viewing state)
24211     * - In addition to this, we still want the user to be considered inactive after
24212     *   a few seconds of inactivity.
24213     *
24214     * > Note: the only part of iOS interaction we can't mimic with this setup
24215     * is a touch and hold on the video element counting as activity in order to
24216     * keep the controls showing, but that shouldn't be an issue. A touch and hold
24217     * on any controls will still keep the user active
24218     *
24219     * @private
24220     */
24221    ;
24222
24223    _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
24224      // Make sure to remove all the previous listeners in case we are called multiple times.
24225      this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
24226      // trigger mousedown/up.
24227      // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
24228      // Any touch events are set to block the mousedown event from happening
24229
24230      this.on(this.tech_, 'mouseup', this.handleTechClick_);
24231      this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
24232      // so we'll check if the controls were already showing before reporting user
24233      // activity
24234
24235      this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
24236      this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
24237      this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
24238      // listener cancels out any reportedUserActivity when setting userActive(false)
24239
24240      this.on(this.tech_, 'tap', this.handleTechTap_);
24241    }
24242    /**
24243     * Remove the listeners used for click and tap controls. This is needed for
24244     * toggling to controls disabled, where a tap/touch should do nothing.
24245     *
24246     * @private
24247     */
24248    ;
24249
24250    _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
24251      // We don't want to just use `this.off()` because there might be other needed
24252      // listeners added by techs that extend this.
24253      this.off(this.tech_, 'tap', this.handleTechTap_);
24254      this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
24255      this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
24256      this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
24257      this.off(this.tech_, 'mouseup', this.handleTechClick_);
24258      this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
24259    }
24260    /**
24261     * Player waits for the tech to be ready
24262     *
24263     * @private
24264     */
24265    ;
24266
24267    _proto.handleTechReady_ = function handleTechReady_() {
24268      this.triggerReady(); // Keep the same volume as before
24269
24270      if (this.cache_.volume) {
24271        this.techCall_('setVolume', this.cache_.volume);
24272      } // Look if the tech found a higher resolution poster while loading
24273
24274
24275      this.handleTechPosterChange_(); // Update the duration if available
24276
24277      this.handleTechDurationChange_();
24278    }
24279    /**
24280     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
24281     * function will also trigger {@link Player#firstplay} if it is the first loadstart
24282     * for a video.
24283     *
24284     * @fires Player#loadstart
24285     * @fires Player#firstplay
24286     * @listens Tech#loadstart
24287     * @private
24288     */
24289    ;
24290
24291    _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
24292      // TODO: Update to use `emptied` event instead. See #1277.
24293      this.removeClass('vjs-ended');
24294      this.removeClass('vjs-seeking'); // reset the error state
24295
24296      this.error(null); // Update the duration
24297
24298      this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
24299      // The firstplay event relies on both the play and loadstart events
24300      // which can happen in any order for a new source
24301
24302      if (!this.paused()) {
24303        /**
24304         * Fired when the user agent begins looking for media data
24305         *
24306         * @event Player#loadstart
24307         * @type {EventTarget~Event}
24308         */
24309        this.trigger('loadstart');
24310        this.trigger('firstplay');
24311      } else {
24312        // reset the hasStarted state
24313        this.hasStarted(false);
24314        this.trigger('loadstart');
24315      } // autoplay happens after loadstart for the browser,
24316      // so we mimic that behavior
24317
24318
24319      this.manualAutoplay_(this.autoplay());
24320    }
24321    /**
24322     * Handle autoplay string values, rather than the typical boolean
24323     * values that should be handled by the tech. Note that this is not
24324     * part of any specification. Valid values and what they do can be
24325     * found on the autoplay getter at Player#autoplay()
24326     */
24327    ;
24328
24329    _proto.manualAutoplay_ = function manualAutoplay_(type) {
24330      var _this5 = this;
24331
24332      if (!this.tech_ || typeof type !== 'string') {
24333        return;
24334      }
24335
24336      var muted = function muted() {
24337        var previouslyMuted = _this5.muted();
24338
24339        _this5.muted(true);
24340
24341        var restoreMuted = function restoreMuted() {
24342          _this5.muted(previouslyMuted);
24343        }; // restore muted on play terminatation
24344
24345
24346        _this5.playTerminatedQueue_.push(restoreMuted);
24347
24348        var mutedPromise = _this5.play();
24349
24350        if (!isPromise(mutedPromise)) {
24351          return;
24352        }
24353
24354        return mutedPromise["catch"](restoreMuted);
24355      };
24356
24357      var promise; // if muted defaults to true
24358      // the only thing we can do is call play
24359
24360      if (type === 'any' && this.muted() !== true) {
24361        promise = this.play();
24362
24363        if (isPromise(promise)) {
24364          promise = promise["catch"](muted);
24365        }
24366      } else if (type === 'muted' && this.muted() !== true) {
24367        promise = muted();
24368      } else {
24369        promise = this.play();
24370      }
24371
24372      if (!isPromise(promise)) {
24373        return;
24374      }
24375
24376      return promise.then(function () {
24377        _this5.trigger({
24378          type: 'autoplay-success',
24379          autoplay: type
24380        });
24381      })["catch"](function (e) {
24382        _this5.trigger({
24383          type: 'autoplay-failure',
24384          autoplay: type
24385        });
24386      });
24387    }
24388    /**
24389     * Update the internal source caches so that we return the correct source from
24390     * `src()`, `currentSource()`, and `currentSources()`.
24391     *
24392     * > Note: `currentSources` will not be updated if the source that is passed in exists
24393     *         in the current `currentSources` cache.
24394     *
24395     *
24396     * @param {Tech~SourceObject} srcObj
24397     *        A string or object source to update our caches to.
24398     */
24399    ;
24400
24401    _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
24402      if (srcObj === void 0) {
24403        srcObj = '';
24404      }
24405
24406      var src = srcObj;
24407      var type = '';
24408
24409      if (typeof src !== 'string') {
24410        src = srcObj.src;
24411        type = srcObj.type;
24412      } // make sure all the caches are set to default values
24413      // to prevent null checking
24414
24415
24416      this.cache_.source = this.cache_.source || {};
24417      this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
24418
24419      if (src && !type) {
24420        type = findMimetype(this, src);
24421      } // update `currentSource` cache always
24422
24423
24424      this.cache_.source = mergeOptions({}, srcObj, {
24425        src: src,
24426        type: type
24427      });
24428      var matchingSources = this.cache_.sources.filter(function (s) {
24429        return s.src && s.src === src;
24430      });
24431      var sourceElSources = [];
24432      var sourceEls = this.$$('source');
24433      var matchingSourceEls = [];
24434
24435      for (var i = 0; i < sourceEls.length; i++) {
24436        var sourceObj = getAttributes(sourceEls[i]);
24437        sourceElSources.push(sourceObj);
24438
24439        if (sourceObj.src && sourceObj.src === src) {
24440          matchingSourceEls.push(sourceObj.src);
24441        }
24442      } // if we have matching source els but not matching sources
24443      // the current source cache is not up to date
24444
24445
24446      if (matchingSourceEls.length && !matchingSources.length) {
24447        this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
24448        // sources cache to the `currentSource` cache
24449      } else if (!matchingSources.length) {
24450        this.cache_.sources = [this.cache_.source];
24451      } // update the tech `src` cache
24452
24453
24454      this.cache_.src = src;
24455    }
24456    /**
24457     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
24458     * causing the media element to reload.
24459     *
24460     * It will fire for the initial source and each subsequent source.
24461     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
24462     *
24463     * The event object for this event contains a `src` property that will contain the source
24464     * that was available when the event was triggered. This is generally only necessary if Video.js
24465     * is switching techs while the source was being changed.
24466     *
24467     * It is also fired when `load` is called on the player (or media element)
24468     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
24469     * says that the resource selection algorithm needs to be aborted and restarted.
24470     * In this case, it is very likely that the `src` property will be set to the
24471     * empty string `""` to indicate we do not know what the source will be but
24472     * that it is changing.
24473     *
24474     * *This event is currently still experimental and may change in minor releases.*
24475     * __To use this, pass `enableSourceset` option to the player.__
24476     *
24477     * @event Player#sourceset
24478     * @type {EventTarget~Event}
24479     * @prop {string} src
24480     *                The source url available when the `sourceset` was triggered.
24481     *                It will be an empty string if we cannot know what the source is
24482     *                but know that the source will change.
24483     */
24484
24485    /**
24486     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
24487     *
24488     * @fires Player#sourceset
24489     * @listens Tech#sourceset
24490     * @private
24491     */
24492    ;
24493
24494    _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
24495      var _this6 = this;
24496
24497      // only update the source cache when the source
24498      // was not updated using the player api
24499      if (!this.changingSrc_) {
24500        var updateSourceCaches = function updateSourceCaches(src) {
24501          return _this6.updateSourceCaches_(src);
24502        };
24503
24504        var playerSrc = this.currentSource().src;
24505        var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
24506
24507        if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
24508          // if both the tech source and the player source were updated we assume
24509          // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
24510          if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
24511            updateSourceCaches = function updateSourceCaches() {};
24512          }
24513        } // update the source to the intial source right away
24514        // in some cases this will be empty string
24515
24516
24517        updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
24518        // wait for a `loadstart` to update the cache to `currentSrc`.
24519        // If a sourceset happens before a `loadstart`, we reset the state
24520
24521        if (!event.src) {
24522          this.tech_.any(['sourceset', 'loadstart'], function (e) {
24523            // if a sourceset happens before a `loadstart` there
24524            // is nothing to do as this `handleTechSourceset_`
24525            // will be called again and this will be handled there.
24526            if (e.type === 'sourceset') {
24527              return;
24528            }
24529
24530            var techSrc = _this6.techGet('currentSrc');
24531
24532            _this6.lastSource_.tech = techSrc;
24533
24534            _this6.updateSourceCaches_(techSrc);
24535          });
24536        }
24537      }
24538
24539      this.lastSource_ = {
24540        player: this.currentSource().src,
24541        tech: event.src
24542      };
24543      this.trigger({
24544        src: event.src,
24545        type: 'sourceset'
24546      });
24547    }
24548    /**
24549     * Add/remove the vjs-has-started class
24550     *
24551     * @fires Player#firstplay
24552     *
24553     * @param {boolean} request
24554     *        - true: adds the class
24555     *        - false: remove the class
24556     *
24557     * @return {boolean}
24558     *         the boolean value of hasStarted_
24559     */
24560    ;
24561
24562    _proto.hasStarted = function hasStarted(request) {
24563      if (request === undefined) {
24564        // act as getter, if we have no request to change
24565        return this.hasStarted_;
24566      }
24567
24568      if (request === this.hasStarted_) {
24569        return;
24570      }
24571
24572      this.hasStarted_ = request;
24573
24574      if (this.hasStarted_) {
24575        this.addClass('vjs-has-started');
24576        this.trigger('firstplay');
24577      } else {
24578        this.removeClass('vjs-has-started');
24579      }
24580    }
24581    /**
24582     * Fired whenever the media begins or resumes playback
24583     *
24584     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
24585     * @fires Player#play
24586     * @listens Tech#play
24587     * @private
24588     */
24589    ;
24590
24591    _proto.handleTechPlay_ = function handleTechPlay_() {
24592      this.removeClass('vjs-ended');
24593      this.removeClass('vjs-paused');
24594      this.addClass('vjs-playing'); // hide the poster when the user hits play
24595
24596      this.hasStarted(true);
24597      /**
24598       * Triggered whenever an {@link Tech#play} event happens. Indicates that
24599       * playback has started or resumed.
24600       *
24601       * @event Player#play
24602       * @type {EventTarget~Event}
24603       */
24604
24605      this.trigger('play');
24606    }
24607    /**
24608     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
24609     *
24610     * If there were any events queued while the playback rate was zero, fire
24611     * those events now.
24612     *
24613     * @private
24614     * @method Player#handleTechRateChange_
24615     * @fires Player#ratechange
24616     * @listens Tech#ratechange
24617     */
24618    ;
24619
24620    _proto.handleTechRateChange_ = function handleTechRateChange_() {
24621      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
24622        this.queuedCallbacks_.forEach(function (queued) {
24623          return queued.callback(queued.event);
24624        });
24625        this.queuedCallbacks_ = [];
24626      }
24627
24628      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
24629      /**
24630       * Fires when the playing speed of the audio/video is changed
24631       *
24632       * @event Player#ratechange
24633       * @type {event}
24634       */
24635
24636      this.trigger('ratechange');
24637    }
24638    /**
24639     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
24640     *
24641     * @fires Player#waiting
24642     * @listens Tech#waiting
24643     * @private
24644     */
24645    ;
24646
24647    _proto.handleTechWaiting_ = function handleTechWaiting_() {
24648      var _this7 = this;
24649
24650      this.addClass('vjs-waiting');
24651      /**
24652       * A readyState change on the DOM element has caused playback to stop.
24653       *
24654       * @event Player#waiting
24655       * @type {EventTarget~Event}
24656       */
24657
24658      this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
24659      // premature removal of the waiting class, wait for the time to change.
24660
24661      var timeWhenWaiting = this.currentTime();
24662
24663      var timeUpdateListener = function timeUpdateListener() {
24664        if (timeWhenWaiting !== _this7.currentTime()) {
24665          _this7.removeClass('vjs-waiting');
24666
24667          _this7.off('timeupdate', timeUpdateListener);
24668        }
24669      };
24670
24671      this.on('timeupdate', timeUpdateListener);
24672    }
24673    /**
24674     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
24675     * > Note: This is not consistent between browsers. See #1351
24676     *
24677     * @fires Player#canplay
24678     * @listens Tech#canplay
24679     * @private
24680     */
24681    ;
24682
24683    _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
24684      this.removeClass('vjs-waiting');
24685      /**
24686       * The media has a readyState of HAVE_FUTURE_DATA or greater.
24687       *
24688       * @event Player#canplay
24689       * @type {EventTarget~Event}
24690       */
24691
24692      this.trigger('canplay');
24693    }
24694    /**
24695     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
24696     *
24697     * @fires Player#canplaythrough
24698     * @listens Tech#canplaythrough
24699     * @private
24700     */
24701    ;
24702
24703    _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
24704      this.removeClass('vjs-waiting');
24705      /**
24706       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
24707       * entire media file can be played without buffering.
24708       *
24709       * @event Player#canplaythrough
24710       * @type {EventTarget~Event}
24711       */
24712
24713      this.trigger('canplaythrough');
24714    }
24715    /**
24716     * Retrigger the `playing` event that was triggered by the {@link Tech}.
24717     *
24718     * @fires Player#playing
24719     * @listens Tech#playing
24720     * @private
24721     */
24722    ;
24723
24724    _proto.handleTechPlaying_ = function handleTechPlaying_() {
24725      this.removeClass('vjs-waiting');
24726      /**
24727       * The media is no longer blocked from playback, and has started playing.
24728       *
24729       * @event Player#playing
24730       * @type {EventTarget~Event}
24731       */
24732
24733      this.trigger('playing');
24734    }
24735    /**
24736     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
24737     *
24738     * @fires Player#seeking
24739     * @listens Tech#seeking
24740     * @private
24741     */
24742    ;
24743
24744    _proto.handleTechSeeking_ = function handleTechSeeking_() {
24745      this.addClass('vjs-seeking');
24746      /**
24747       * Fired whenever the player is jumping to a new time
24748       *
24749       * @event Player#seeking
24750       * @type {EventTarget~Event}
24751       */
24752
24753      this.trigger('seeking');
24754    }
24755    /**
24756     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
24757     *
24758     * @fires Player#seeked
24759     * @listens Tech#seeked
24760     * @private
24761     */
24762    ;
24763
24764    _proto.handleTechSeeked_ = function handleTechSeeked_() {
24765      this.removeClass('vjs-seeking');
24766      this.removeClass('vjs-ended');
24767      /**
24768       * Fired when the player has finished jumping to a new time
24769       *
24770       * @event Player#seeked
24771       * @type {EventTarget~Event}
24772       */
24773
24774      this.trigger('seeked');
24775    }
24776    /**
24777     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
24778     *
24779     * @fires Player#firstplay
24780     * @listens Tech#firstplay
24781     * @deprecated As of 6.0 firstplay event is deprecated.
24782     *             As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
24783     * @private
24784     */
24785    ;
24786
24787    _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
24788      // If the first starttime attribute is specified
24789      // then we will start at the given offset in seconds
24790      if (this.options_.starttime) {
24791        log.warn('Passing the `starttime` option to the player will be deprecated 
24791in 6.0');
24792        this.currentTime(this.options_.starttime);
24793      }
24794
24795      this.addClass('vjs-has-started');
24796      /**
24797       * Fired the first time a video is played. Not part of the HLS spec, and this is
24798       * probably not the best implementation yet, so use sparingly. If you don't have a
24799       * reason to prevent playback, use `myPlayer.one('play');` instead.
24800       *
24801       * @event Player#firstplay
24802       * @deprecated As of 6.0 firstplay event is deprecated.
24803       * @type {EventTarget~Event}
24804       */
24805
24806      this.trigger('firstplay');
24807    }
24808    /**
24809     * Retrigger the `pause` event that was triggered by the {@link Tech}.
24810     *
24811     * @fires Player#pause
24812     * @listens Tech#pause
24813     * @private
24814     */
24815    ;
24816
24817    _proto.handleTechPause_ = function handleTechPause_() {
24818      this.removeClass('vjs-playing');
24819      this.addClass('vjs-paused');
24820      /**
24821       * Fired whenever the media has been paused
24822       *
24823       * @event Player#pause
24824       * @type {EventTarget~Event}
24825       */
24826
24827      this.trigger('pause');
24828    }
24829    /**
24830     * Retrigger the `ended` event that was triggered by the {@link Tech}.
24831     *
24832     * @fires Player#ended
24833     * @listens Tech#ended
24834     * @private
24835     */
24836    ;
24837
24838    _proto.handleTechEnded_ = function handleTechEnded_() {
24839      this.addClass('vjs-ended');
24840
24841      if (this.options_.loop) {
24842        this.currentTime(0);
24843        this.play();
24844      } else if (!this.paused()) {
24845        this.pause();
24846      }
24847      /**
24848       * Fired when the end of the media resource is reached (currentTime == duration)
24849       *
24850       * @event Player#ended
24851       * @type {EventTarget~Event}
24852       */
24853
24854
24855      this.trigger('ended');
24856    }
24857    /**
24858     * Fired when the duration of the media resource is first known or changed
24859     *
24860     * @listens Tech#durationchange
24861     * @private
24862     */
24863    ;
24864
24865    _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
24866      this.duration(this.techGet_('duration'));
24867    }
24868    /**
24869     * Handle a click on the media element to play/pause
24870     *
24871     * @param {EventTarget~Event} event
24872     *        the event that caused this function to trigger
24873     *
24874     * @listens Tech#mouseup
24875     * @private
24876     */
24877    ;
24878
24879    _proto.handleTechClick_ = function handleTechClick_(event) {
24880      if (!isSingleLeftClick(event)) {
24881        return;
24882      } // When controls are disabled a click should not toggle playback because
24883      // the click is considered a control
24884
24885
24886      if (!this.controls_) {
24887        return;
24888      }
24889
24890      if (this.paused()) {
24891        silencePromise(this.play());
24892      } else {
24893        this.pause();
24894      }
24895    }
24896    /**
24897     * Handle a double-click on the media element to enter/exit fullscreen
24898     *
24899     * @param {EventTarget~Event} event
24900     *        the event that caused this function to trigger
24901     *
24902     * @listens Tech#dblclick
24903     * @private
24904     */
24905    ;
24906
24907    _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
24908      if (!this.controls_) {
24909        return;
24910      } // we do not want to toggle fullscreen state
24911      // when double-clicking inside a control bar or a modal
24912
24913
24914      var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
24915        return el.contains(event.target);
24916      });
24917
24918      if (!inAllowedEls) {
24919        /*
24920         * options.userActions.doubleClick
24921         *
24922         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
24923         * Set to `false` to disable double-click handling
24924         * Set to a function to substitute an external double-click handler
24925         */
24926        if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
24927          if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
24928            this.options_.userActions.doubleClick.call(this, event);
24929          } else if (this.isFullscreen()) {
24930            this.exitFullscreen();
24931          } else {
24932            this.requestFullscreen();
24933          }
24934        }
24935      }
24936    }
24937    /**
24938     * Handle a tap on the media element. It will toggle the user
24939     * activity state, which hides and shows the controls.
24940     *
24941     * @listens Tech#tap
24942     * @private
24943     */
24944    ;
24945
24946    _proto.handleTechTap_ = function handleTechTap_() {
24947      this.userActive(!this.userActive());
24948    }
24949    /**
24950     * Handle touch to start
24951     *
24952     * @listens Tech#touchstart
24953     * @private
24954     */
24955    ;
24956
24957    _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
24958      this.userWasActive = this.userActive();
24959    }
24960    /**
24961     * Handle touch to move
24962     *
24963     * @listens Tech#touchmove
24964     * @private
24965     */
24966    ;
24967
24968    _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
24969      if (this.userWasActive) {
24970        this.reportUserActivity();
24971      }
24972    }
24973    /**
24974     * Handle touch to end
24975     *
24976     * @param {EventTarget~Event} event
24977     *        the touchend event that triggered
24978     *        this function
24979     *
24980     * @listens Tech#touchend
24981     * @private
24982     */
24983    ;
24984
24985    _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
24986      // Stop the mouse events from also happening
24987      event.preventDefault();
24988    }
24989    /**
24990     * native click events on the SWF aren't triggered on IE11, Win8.1RT
24991     * use stageclick events triggered from inside the SWF instead
24992     *
24993     * @private
24994     * @listens stageclick
24995     */
24996    ;
24997
24998    _proto.handleStageClick_ = function handleStageClick_() {
24999      this.reportUserActivity();
25000    }
25001    /**
25002     * @private
25003     */
25004    ;
25005
25006    _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
25007      if (this.isFullscreen()) {
25008        this.addClass('vjs-fullscreen');
25009      } else {
25010        this.removeClass('vjs-fullscreen');
25011      }
25012    }
25013    /**
25014     * when the document fschange event triggers it calls this
25015     */
25016    ;
25017
25018    _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
25019      var targetPlayer = e.target.player; // if another player was fullscreen
25020      // do a null check for targetPlayer because older firefox's would put document as e.target
25021
25022      if (targetPlayer && targetPlayer !== this) {
25023        return;
25024      }
25025
25026      var el = this.el();
25027      var isFs = document[this.fsApi_.fullscreenElement] === el;
25028
25029      if (!isFs && el.matches) {
25030        isFs = el.matches(':' + this.fsApi_.fullscreen);
25031      } else if (!isFs && el.msMatchesSelector) {
25032        isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
25033      }
25034
25035      this.isFullscreen(isFs);
25036    }
25037    /**
25038     * Handle Tech Fullscreen Change
25039     *
25040     * @param {EventTarget~Event} event
25041     *        the fullscreenchange event that triggered this function
25042     *
25043     * @param {Object} data
25044     *        the data that was sent with the event
25045     *
25046     * @private
25047     * @listens Tech#fullscreenchange
25048     * @fires Player#fullscreenchange
25049     */
25050    ;
25051
25052    _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
25053      if (data) {
25054        this.isFullscreen(data.isFullscreen);
25055      }
25056    }
25057    /**
25058     * @private
25059     */
25060    ;
25061
25062    _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() {
25063      if (this.isInPictureInPicture()) {
25064        this.addClass('vjs-picture-in-picture');
25065      } else {
25066        this.removeClass('vjs-picture-in-picture');
25067      }
25068    }
25069    /**
25070     * Handle Tech Enter Picture-in-Picture.
25071     *
25072     * @param {EventTarget~Event} event
25073     *        the enterpictureinpicture event that triggered this function
25074     *
25075     * @private
25076     * @listens Tech#enterpictureinpicture
25077     */
25078    ;
25079
25080    _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) {
25081      this.isInPictureInPicture(true);
25082    }
25083    /**
25084     * Handle Tech Leave Picture-in-Picture.
25085     *
25086     * @param {EventTarget~Event} event
25087     *        the leavepictureinpicture event that triggered this function
25088     *
25089     * @private
25090     * @listens Tech#leavepictureinpicture
25091     */
25092    ;
25093
25094    _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) {
25095      this.isInPictureInPicture(false);
25096    }
25097    /**
25098     * Fires when an error occurred during the loading of an audio/video.
25099     *
25100     * @private
25101     * @listens Tech#error
25102     */
25103    ;
25104
25105    _proto.handleTechError_ = function handleTechError_() {
25106      var error = this.tech_.error();
25107      this.error(error);
25108    }
25109    /**
25110     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
25111     *
25112     * @fires Player#textdata
25113     * @listens Tech#textdata
25114     * @private
25115     */
25116    ;
25117
25118    _proto.handleTechTextData_ = function handleTechTextData_() {
25119      var data = null;
25120
25121      if (arguments.length > 1) {
25122        data = arguments[1];
25123      }
25124      /**
25125       * Fires when we get a textdata event from tech
25126       *
25127       * @event Player#textdata
25128       * @type {EventTarget~Event}
25129       */
25130
25131
25132      this.trigger('textdata', data);
25133    }
25134    /**
25135     * Get object for cached values.
25136     *
25137     * @return {Object}
25138     *         get the current object cache
25139     */
25140    ;
25141
25142    _proto.getCache = function getCache() {
25143      return this.cache_;
25144    }
25145    /**
25146     * Resets the internal cache object.
25147     *
25148     * Using this function outside the player constructor or reset method may
25149     * have unintended side-effects.
25150     *
25151     * @private
25152     */
25153    ;
25154
25155    _proto.resetCache_ = function resetCache_() {
25156      this.cache_ = {
25157        // Right now, the currentTime is not _really_ cached because it is always
25158        // retrieved from the tech (see: currentTime). However, for completeness,
25159        // we set it to zero here to ensure that if we do start actually caching
25160        // it, we reset it along with everything else.
25161        currentTime: 0,
25162        inactivityTimeout: this.options_.inactivityTimeout,
25163        duration: NaN,
25164        lastVolume: 1,
25165        lastPlaybackRate: this.defaultPlaybackRate(),
25166        media: null,
25167        src: '',
25168        source: {},
25169        sources: [],
25170        volume: 1
25171      };
25172    }
25173    /**
25174     * Pass values to the playback tech
25175     *
25176     * @param {string} [method]
25177     *        the method to call
25178     *
25179     * @param {Object} arg
25180     *        the argument to pass
25181     *
25182     * @private
25183     */
25184    ;
25185
25186    _proto.techCall_ = function techCall_(method, arg) {
25187      // If it's not ready yet, call method when it is
25188      this.ready(function () {
25189        if (method in allowedSetters) {
25190          return set(this.middleware_, this.tech_, method, arg);
25191        } else if (method in allowedMediators) {
25192          return mediate(this.middleware_, this.tech_, method, arg);
25193        }
25194
25195        try {
25196          if (this.tech_) {
25197            this.tech_[method](arg);
25198          }
25199        } catch (e) {
25200          log(e);
25201          throw e;
25202        }
25203      }, true);
25204    }
25205    /**
25206     * Get calls can't wait for the tech, and sometimes don't need to.
25207     *
25208     * @param {string} method
25209     *        Tech method
25210     *
25211     * @return {Function|undefined}
25212     *         the method or undefined
25213     *
25214     * @private
25215     */
25216    ;
25217
25218    _proto.techGet_ = function techGet_(method) {
25219      if (!this.tech_ || !this.tech_.isReady_) {
25220        return;
25221      }
25222
25223      if (method in allowedGetters) {
25224        return get(this.middleware_, this.tech_, method);
25225      } else if (method in allowedMediators) {
25226        return mediate(this.middleware_, this.tech_, method);
25227      } // Flash likes to die and reload when you hide or reposition it.
25228      // In these cases the object methods go away and we get errors.
25229      // When that happens we'll catch the errors and inform tech that it's not ready any more.
25230
25231
25232      try {
25233        return this.tech_[method]();
25234      } catch (e) {
25235        // When building additional tech libs, an expected method may not be defined yet
25236        if (this.tech_[method] === undefined) {
25237          log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
25238          throw e;
25239        } // When a method isn't available on the object it throws a TypeError
25240
25241
25242        if (e.name === 'TypeError') {
25243          log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
25244          this.tech_.isReady_ = false;
25245          throw e;
25246        } // If error unknown, just log and throw
25247
25248
25249        log(e);
25250        throw e;
25251      }
25252    }
25253    /**
25254     * Attempt to begin playback at the first opportunity.
25255     *
25256     * @return {Promise|undefined}
25257     *         Returns a promise if the browser supports Promises (or one
25258     *         was passed in as an option). This promise will be resolved on
25259     *         the return value of play. If this is undefined it will fulfill the
25260     *         promise chain otherwise the promise chain will be fulfilled when
25261     *         the promise from play is fulfilled.
25262     */
25263    ;
25264
25265    _proto.play = function play() {
25266      var _this8 = this;
25267
25268      var PromiseClass = this.options_.Promise || window$1.Promise;
25269
25270      if (PromiseClass) {
25271        return new PromiseClass(function (resolve) {
25272          _this8.play_(resolve);
25273        });
25274      }
25275
25276      return this.play_();
25277    }
25278    /**
25279     * The actual logic for play, takes a callback that will be resolved on the
25280     * return value of play. This allows us to resolve to the play promise if there
25281     * is one on modern browsers.
25282     *
25283     * @private
25284     * @param {Function} [callback]
25285     *        The callback that should be called when the techs play is actually called
25286     */
25287    ;
25288
25289    _proto.play_ = function play_(callback) {
25290      var _this9 = this;
25291
25292      if (callback === void 0) {
25293        callback = silencePromise;
25294      }
25295
25296      this.playCallbacks_.push(callback);
25297      var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
25298
25299      if (this.waitToPlay_) {
25300        this.off(['ready', 'loadstart'], this.waitToPlay_);
25301        this.waitToPlay_ = null;
25302      } // if the player/tech is not ready or the src itself is not ready
25303      // queue up a call to play on `ready` or `loadstart`
25304
25305
25306      if (!this.isReady_ || !isSrcReady) {
25307        this.waitToPlay_ = function (e) {
25308          _this9.play_();
25309        };
25310
25311        this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
25312        // in that case, we need to prime the video element by calling load so it'll be ready in time
25313
25314        if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
25315          this.load();
25316        }
25317
25318        return;
25319      } // If the player/tech is ready and we have a source, we can attempt playback.
25320
25321
25322      var val = this.techGet_('play'); // play was terminated if the returned value is null
25323
25324      if (val === null) {
25325        this.runPlayTerminatedQueue_();
25326      } else {
25327        this.runPlayCallbacks_(val);
25328      }
25329    }
25330    /**
25331     * These functions will be run when if play is terminated. If play
25332     * runPlayCallbacks_ is run these function will not be run. This allows us
25333     * to differenciate between a terminated play and an actual call to play.
25334     */
25335    ;
25336
25337    _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
25338      var queue = this.playTerminatedQueue_.slice(0);
25339      this.playTerminatedQueue_ = [];
25340      queue.forEach(function (q) {
25341        q();
25342      });
25343    }
25344    /**
25345     * When a callback to play is delayed we have to run these
25346     * callbacks when play is actually called on the tech. This function
25347     * runs the callbacks that were delayed and accepts the return value
25348     * from the tech.
25349     *
25350     * @param {undefined|Promise} val
25351     *        The return value from the tech.
25352     */
25353    ;
25354
25355    _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
25356      var callbacks = this.playCallbacks_.slice(0);
25357      this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
25358
25359      this.playTerminatedQueue_ = [];
25360      callbacks.forEach(function (cb) {
25361        cb(val);
25362      });
25363    }
25364    /**
25365     * Pause the video playback
25366     *
25367     * @return {Player}
25368     *         A reference to the player object this function was called on
25369     */
25370    ;
25371
25372    _proto.pause = function pause() {
25373      this.techCall_('pause');
25374    }
25375    /**
25376     * Check if the player is paused or has yet to play
25377     *
25378     * @return {boolean}
25379     *         - false: if the media is currently playing
25380     *         - true: if media is not currently playing
25381     */
25382    ;
25383
25384    _proto.paused = function paused() {
25385      // The initial state of paused should be true (in Safari it's actually false)
25386      return this.techGet_('paused') === false ? false : true;
25387    }
25388    /**
25389     * Get a TimeRange object representing the current ranges of time that the user
25390     * has played.
25391     *
25392     * @return {TimeRange}
25393     *         A time range object that represents all the increments of time that have
25394     *         been played.
25395     */
25396    ;
25397
25398    _proto.played = function played() {
25399      return this.techGet_('played') || createTimeRanges(0, 0);
25400    }
25401    /**
25402     * Returns whether or not the user is "scrubbing". Scrubbing is
25403     * when the user has clicked the progress bar handle and is
25404     * dragging it along the progress bar.
25405     *
25406     * @param {boolean} [isScrubbing]
25407     *        whether the user is or is not scrubbing
25408     *
25409     * @return {boolean}
25410     *         The value of scrubbing when getting
25411     */
25412    ;
25413
25414    _proto.scrubbing = function scrubbing(isScrubbing) {
25415      if (typeof isScrubbing === 'undefined') {
25416        return this.scrubbing_;
25417      }
25418
25419      this.scrubbing_ = !!isScrubbing;
25420
25421      if (isScrubbing) {
25422        this.addClass('vjs-scrubbing');
25423      } else {
25424        this.removeClass('vjs-scrubbing');
25425      }
25426    }
25427    /**
25428     * Get or set the current time (in seconds)
25429     *
25430     * @param {number|string} [seconds]
25431     *        The time to seek to in seconds
25432     *
25433     * @return {number}
25434     *         - the current time in seconds when getting
25435     */
25436    ;
25437
25438    _proto.currentTime = function currentTime(seconds) {
25439      if (typeof seconds !== 'undefined') {
25440        if (seconds < 0) {
25441          seconds = 0;
25442        }
25443
25444        this.techCall_('setCurrentTime', seconds);
25445        return;
25446      } // cache last currentTime and return. default to 0 seconds
25447      //
25448      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
vendor: 5,572 bytes, lines 25449-25625
25449      // currentTime when scrubbing, but may not provide much performance benefit afterall.
25450      // Should be tested. Also something has to read the actual current time or the cache will
25451      // never get updated.
25452
25453
25454      this.cache_.currentTime = this.techGet_('currentTime') || 0;
25455      return this.cache_.currentTime;
25456    }
25457    /**
25458     * Normally gets the length in time of the video in seconds;
25459     * in all but the rarest use cases an argument will NOT be passed to the method
25460     *
25461     * > **NOTE**: The video must have started loading before the duration can be
25462     * known, and in the case of Flash, may not be known until the video starts
25463     * playing.
25464     *
25465     * @fires Player#durationchange
25466     *
25467     * @param {number} [seconds]
25468     *        The duration of the video to set in seconds
25469     *
25470     * @return {number}
25471     *         - The duration of the video in seconds when getting
25472     */
25473    ;
25474
25475    _proto.duration = function duration(seconds) {
25476      if (seconds === undefined) {
25477        // return NaN if the duration is not known
25478        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
25479      }
25480
25481      seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
25482
25483      if (seconds < 0) {
25484        seconds = Infinity;
25485      }
25486
25487      if (seconds !== this.cache_.duration) {
25488        // Cache the last set value for optimized scrubbing (esp. Flash)
25489        this.cache_.duration = seconds;
25490
25491        if (seconds === Infinity) {
25492          this.addClass('vjs-live');
25493
25494          if (this.options_.liveui && this.player_.liveTracker) {
25495            this.addClass('vjs-liveui');
25496          }
25497        } else {
25498          this.removeClass('vjs-live');
25499          this.removeClass('vjs-liveui');
25500        }
25501
25502        if (!isNaN(seconds)) {
25503          // Do not fire durationchange unless the duration value is known.
25504          // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
25505
25506          /**
25507           * @event Player#durationchange
25508           * @type {EventTarget~Event}
25509           */
25510          this.trigger('durationchange');
25511        }
25512      }
25513    }
25514    /**
25515     * Calculates how much time is left in the video. Not part
25516     * of the native video API.
25517     *
25518     * @return {number}
25519     *         The time remaining in seconds
25520     */
25521    ;
25522
25523    _proto.remainingTime = function remainingTime() {
25524      return this.duration() - this.currentTime();
25525    }
25526    /**
25527     * A remaining time function that is intented to be used when
25528     * the time is to be displayed directly to the user.
25529     *
25530     * @return {number}
25531     *         The rounded time remaining in seconds
25532     */
25533    ;
25534
25535    _proto.remainingTimeDisplay = function remainingTimeDisplay() {
25536      return Math.floor(this.duration()) - Math.floor(this.currentTime());
25537    } //
25538    // Kind of like an array of portions of the video that have been downloaded.
25539
25540    /**
25541     * Get a TimeRange object with an array of the times of the video
25542     * that have been downloaded. If you just want the percent of the
25543     * video that's been downloaded, use bufferedPercent.
25544     *
25545     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
25546     *
25547     * @return {TimeRange}
25548     *         A mock TimeRange object (following HTML spec)
25549     */
25550    ;
25551
25552    _proto.buffered = function buffered() {
25553      var buffered = this.techGet_('buffered');
25554
25555      if (!buffered || !buffered.length) {
25556        buffered = createTimeRanges(0, 0);
25557      }
25558
25559      return buffered;
25560    }
25561    /**
25562     * Get the percent (as a decimal) of the video that's been downloaded.
25563     * This method is not a part of the native HTML video API.
25564     *
25565     * @return {number}
25566     *         A decimal between 0 and 1 representing the percent
25567     *         that is buffered 0 being 0% and 1 being 100%
25568     */
25569    ;
25570
25571    _proto.bufferedPercent = function bufferedPercent$1() {
25572      return bufferedPercent(this.buffered(), this.duration());
25573    }
25574    /**
25575     * Get the ending time of the last buffered time range
25576     * This is used in the progress bar to encapsulate all time ranges.
25577     *
25578     * @return {number}
25579     *         The end of the last buffered time range
25580     */
25581    ;
25582
25583    _proto.bufferedEnd = function bufferedEnd() {
25584      var buffered = this.buffered();
25585      var duration = this.duration();
25586      var end = buffered.end(buffered.length - 1);
25587
25588      if (end > duration) {
25589        end = duration;
25590      }
25591
25592      return end;
25593    }
25594    /**
25595     * Get or set the current volume of the media
25596     *
25597     * @param  {number} [percentAsDecimal]
25598     *         The new volume as a decimal percent:
25599     *         - 0 is muted/0%/off
25600     *         - 1.0 is 100%/full
25601     *         - 0.5 is half volume or 50%
25602     *
25603     * @return {number}
25604     *         The current volume as a percent when getting
25605     */
25606    ;
25607
25608    _proto.volume = function volume(percentAsDecimal) {
25609      var vol;
25610
25611      if (percentAsDecimal !== undefined) {
25612        // Force value to between 0 and 1
25613        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
25614        this.cache_.volume = vol;
25615        this.techCall_('setVolume', vol);
25616
25617        if (vol > 0) {
25618          this.lastVolume_(vol);
25619        }
25620
25621        return;
25622      } // Default to 1 when returning current volume.
25623
25624
25625      vol = parseFloat(this.techGet_('volume'));
25626      return isNaN(vol) ? 1 : vol;
25627    }
25628    /**
25629     * Get the current muted state, or turn mute on or off
25630     *
25631     * @param {boolean} [muted]
25632     *        - true to mute
25633     *        - false to unmute
25634     *
25635     * @return {boolean}
25636     *         - true if mute is on and getting
25637     *         - false if mute is off and getting
25638     */
25639    ;
25640
25641    _proto.muted = function muted(_muted) {
25642      if (_muted !== undefined) {
25643        this.techCall_('setMuted', _muted);
25644        return;
25645      }
25646
25647      return this.techGet_('muted') || false;
25648    }
25649    /**
25650     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
25651     * indicates the state of muted on initial playback.
25652     *
25653     * ```js
25654     *   var myPlayer = videojs('some-player-id');
25655     *
25656     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
25657     *
25658     *   // get, should be false
25659     *   console.log(myPlayer.defaultMuted());
25660     *   // set to true
25661     *   myPlayer.defaultMuted(true);
25662     *   // get should be true
25663     *   console.log(myPlayer.defaultMuted());
25664     * ```
25665     *
25666     * @param {boolean} [defaultMuted]
25667     *        - true to mute
25668     *        - false to unmute
25669     *
25670     * @return {boolean|Player}
25671     *         - true if defaultMuted is on and getting
25672     *         - false if defaultMuted is off and getting
25673     *         - A reference to the current player when setting
25674     */
25675    ;
25676
25677    _proto.defaultMuted = function defaultMuted(_defaultMuted) {
25678      if (_defaultMuted !== undefined) {
25679        return this.techCall_('setDefaultMuted', _defaultMuted);
25680      }
25681
25682      return this.techGet_('defaultMuted') || false;
25683    }
25684    /**
25685     * Get the last volume, or set it
25686     *
25687     * @param  {number} [percentAsDecimal]
25688     *         The new last volume as a decimal percent:
25689     *         - 0 is muted/0%/off
25690     *         - 1.0 is 100%/full
25691     *         - 0.5 is half volume or 50%
25692     *
25693     * @return {number}
25694     *         the current value of lastVolume as a percent when getting
25695     *
25696     * @private
25697     */
25698    ;
25699
25700    _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
25701      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
25702        this.cache_.lastVolume = percentAsDecimal;
25703        return;
25704      }
25705
25706      return this.cache_.lastVolume;
25707    }
25708    /**
25709     * Check if current tech can support native fullscreen
25710     * (e.g. with built in controls like iOS, so not our flash swf)
25711     *
25712     * @return {boolean}
25713     *         if native fullscreen is supported
25714     */
25715    ;
25716
25717    _proto.supportsFullScreen = function supportsFullScreen() {
25718      return this.techGet_('supportsFullScreen') || false;
25719    }
25720    /**
25721     * Check if the player is in fullscreen mode or tell the player that it
25722     * is or is not in fullscreen mode.
25723     *
25724     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
25725     * property and instead document.fullscreenElement is used. But isFullscreen is
25726     * still a valuable property for internal player workings.
25727     *
25728     * @param  {boolean} [isFS]
25729     *         Set the players current fullscreen state
25730     *
25731     * @return {boolean}
25732     *         - true if fullscreen is on and getting
25733     *         - false if fullscreen is off and getting
25734     */
25735    ;
25736
25737    _proto.isFullscreen = function isFullscreen(isFS) {
25738      if (isFS !== undefined) {
25739        var oldValue = this.isFullscreen_;
25740        this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
25741        // this is the only place where we trigger fullscreenchange events for older browsers
25742        // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
25743
25744        if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
25745          /**
25746             * @event Player#fullscreenchange
25747             * @type {EventTarget~Event}
25748             */
25749          this.trigger('fullscreenchange');
25750        }
25751
25752        this.toggleFullscreenClass_();
25753        return;
25754      }
25755
25756      return this.isFullscreen_;
25757    }
25758    /**
25759     * Increase the size of the video to full screen
25760     * In some browsers, full screen is not supported natively, so it enters
25761     * "full window mode", where the video fills the browser window.
25762     * In browsers and devices that support native full screen, sometimes the
25763     * browser's default controls will be shown, and not the Video.js custom skin.
25764     * This includes most mobile devices (iOS, Android) and older versions of
25765     * Safari.
25766     *
25767     * @param  {Object} [fullscreenOptions]
25768     *         Override the player fullscreen options
25769     *
25770     * @fires Player#fullscreenchange
25771     */
25772    ;
25773
25774    _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) {
25775      var _this10 = this;
25776
25777      var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
25778      // Use defaults or player configured option unless passed directly to this method.
25779
25780      if (!this.fsApi_.prefixed) {
25781        fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
25782
25783        if (fullscreenOptions !== undefined) {
25784          fsOptions = fullscreenOptions;
25785        }
25786      } // This method works as follows:
25787      // 1. if a fullscreen api is available, use it
25788      //   1. call requestFullscreen with potential options
25789      //   2. if we got a promise from above, use it to update isFullscreen()
25790      // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
25791      //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
25792      // 3. otherwise, use "fullWindow" mode
25793
25794
25795      if (this.fsApi_.requestFullscreen) {
25796        var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
25797
25798        if (promise) {
25799          promise.then(function () {
25800            return _this10.isFullscreen(true);
25801          }, function () {
25802            return _this10.isFullscreen(false);
25803          });
25804        }
25805
25806        return promise;
25807      } else if (this.tech_.supportsFullScreen()) {
25808        // we can't take the video.js controls fullscreen but we can go fullscreen
25809        // with native controls
25810        this.techCall_('enterFullScreen');
25811      } else {
25812        // fullscreen isn't supported so we'll just stretch the video element to
25813        // fill the viewport
25814        this.enterFullWindow();
25815      }
25816    }
25817    /**
25818     * Return the video to its normal size after having been in full screen mode
25819     *
25820     * @fires Player#fullscreenchange
25821     */
25822    ;
25823
25824    _proto.exitFullscreen = function exitFullscreen() {
25825      var _this11 = this;
25826
25827      if (this.fsApi_.requestFullscreen) {
25828        var promise = document[this.fsApi_.exitFullscreen]();
25829
25830        if (promise) {
25831          promise.then(function () {
25832            return _this11.isFullscreen(false);
25833          });
25834        }
25835
25836        return promise;
25837      } else if (this.tech_.supportsFullScreen()) {
25838        this.techCall_('exitFullScreen');
25839      } else {
25840        this.exitFullWindow();
25841      }
25842    }
25843    /**
25844     * When fullscreen isn't supported we can stretch the
25845     * video container to as wide as the browser will let us.
25846     *
25847     * @fires Player#enterFullWindow
25848     */
25849    ;
25850
25851    _proto.enterFullWindow = function enterFullWindow() {
25852      this.isFullscreen(true);
25853      this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
25854
25855      this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
25856
25857      on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
25858
25859      document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
25860
25861      addClass(document.body, 'vjs-full-window');
25862      /**
25863       * @event Player#enterFullWindow
25864       * @type {EventTarget~Event}
25865       */
25866
25867      this.trigger('enterFullWindow');
25868    }
25869    /**
25870     * Check for call to either exit full window or
25871     * full screen on ESC key
25872     *
25873     * @param {string} event
25874     *        Event to check for key press
25875     */
25876    ;
25877
25878    _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
25879      if (keycode.isEventKey(event, 'Esc')) {
25880        if (this.isFullscreen() === true) {
25881          this.exitFullscreen();
25882        } else {
25883          this.exitFullWindow();
25884        }
25885      }
25886    }
25887    /**
25888     * Exit full window
25889     *
25890     * @fires Player#exitFullWindow
25891     */
25892    ;
25893
25894    _proto.exitFullWindow = function exitFullWindow() {
25895      this.isFullscreen(false);
25896      this.isFullWindow = false;
25897      off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
25898
25899      document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
25900
25901      removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
25902      // this.positionAll();
25903
25904      /**
25905       * @event Player#exitFullWindow
25906       * @type {EventTarget~Event}
25907       */
25908
25909      this.trigger('exitFullWindow');
25910    }
25911    /**
25912     * Check if the player is in Picture-in-Picture mode or tell the player that it
25913     * is or is not in Picture-in-Picture mode.
25914     *
25915     * @param  {boolean} [isPiP]
25916     *         Set the players current Picture-in-Picture state
25917     *
25918     * @return {boolean}
25919     *         - true if Picture-in-Picture is on and getting
25920     *         - false if Picture-in-Picture is off and getting
25921     */
25922    ;
25923
25924    _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) {
25925      if (isPiP !== undefined) {
25926        this.isInPictureInPicture_ = !!isPiP;
25927        this.togglePictureInPictureClass_();
25928        return;
25929      }
25930
25931      return !!this.isInPictureInPicture_;
25932    }
25933    /**
25934     * Create a floating video window always on top of other windows so that users may
25935     * continue consuming media while they interact with other content sites, or
25936     * applications on their device.
25937     *
25938     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
25939     *
25940     * @fires Player#enterpictureinpicture
25941     *
25942     * @return {Promise}
25943     *         A promise with a Picture-in-Picture window.
25944     */
25945    ;
25946
25947    _proto.requestPictureInPicture = function requestPictureInPicture() {
25948      if ('pictureInPictureEnabled' in document) {
25949        /**
25950         * This event fires when the player enters picture in picture mode
25951         *
25952         * @event Player#enterpictureinpicture
25953         * @type {EventTarget~Event}
25954         */
25955        return this.techGet_('requestPictureInPicture');
25956      }
25957    }
25958    /**
25959     * Exit Picture-in-Picture mode.
25960     *
25961     * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
25962     *
25963     * @fires Player#leavepictureinpicture
25964     *
25965     * @return {Promise}
25966     *         A promise.
25967     */
25968    ;
25969
25970    _proto.exitPictureInPicture = function exitPictureInPicture() {
25971      if ('pictureInPictureEnabled' in document) {
25972        /**
25973         * This event fires when the player leaves picture in picture mode
25974         *
25975         * @event Player#leavepictureinpicture
25976         * @type {EventTarget~Event}
25977         */
25978        return document.exitPictureInPicture();
25979      }
25980    }
25981    /**
25982     * Called when this Player has focus and a key gets pressed down, or when
25983     * any Component of this player receives a key press that it doesn't handle.
25984     * This allows player-wide hotkeys (either as defined below, or optionally
25985     * by an external function).
25986     *
25987     * @param {EventTarget~Event} event
25988     *        The `keydown` event that caused this function to be called.
25989     *
25990     * @listens keydown
25991     */
25992    ;
25993
25994    _proto.handleKeyDown = function handleKeyDown(event) {
25995      var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
25996
25997      if (!userActions || !userActions.hotkeys) {
25998        return;
25999      } // Function that determines whether or not to exclude an element from
26000      // hotkeys handling.
26001
26002
26003      var excludeElement = function excludeElement(el) {
26004        var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements.
26005
26006        if (el.isContentEditable) {
26007          return true;
26008        } // Inputs matching these types will still trigger hotkey handling as
26009        // they are not text inputs.
26010
26011
26012        var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
26013
26014        if (tagName === 'input') {
26015          return allowedInputTypes.indexOf(el.type) === -1;
26016        } // The final test is by tag name. These tags will be excluded entirely.
26017
26018
26019        var excludedTags = ['textarea'];
26020        return excludedTags.indexOf(tagName) !== -1;
26021      }; // Bail out if the user is focused on an interactive form element.
26022
26023
26024      if (excludeElement(this.el_.ownerDocument.activeElement)) {
26025        return;
26026      }
26027
26028      if (typeof userActions.hotkeys === 'function') {
26029        userActions.hotkeys.call(this, event);
26030      } else {
26031        this.handleHotkeys(event);
26032      }
26033    }
26034    /**
26035     * Called when this Player receives a hotkey keydown event.
26036     * Supported player-wide hotkeys are:
26037     *
26038     *   f          - toggle fullscreen
26039     *   m          - toggle mute
26040     *   k or Space - toggle play/pause
26041     *
26042     * @param {EventTarget~Event} event
26043     *        The `keydown` event that caused this function to be called.
26044     */
26045    ;
26046
26047    _proto.handleHotkeys = function handleHotkeys(event) {
26048      var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
26049
26050      var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
26051          fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
26052        return keycode.isEventKey(keydownEvent, 'f');
26053      } : _hotkeys$fullscreenKe,
26054          _hotkeys$muteKey = hotkeys.muteKey,
26055          muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
26056        return keycode.isEventKey(keydownEvent, 'm');
26057      } : _hotkeys$muteKey,
26058          _hotkeys$playPauseKey = hotkeys.playPauseKey,
26059          playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
26060        return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
26061      } : _hotkeys$playPauseKey;
26062
26063      if (fullscreenKey.call(this, event)) {
26064        event.preventDefault();
26065        event.stopPropagation();
26066        var FSToggle = Component.getComponent('FullscreenToggle');
26067
26068        if (document[this.fsApi_.fullscreenEnabled] !== false) {
26069          FSToggle.prototype.handleClick.call(this, event);
26070        }
26071      } else if (muteKey.call(this, event)) {
26072        event.preventDefault();
26073        event.stopPropagation();
26074        var MuteToggle = Component.getComponent('MuteToggle');
26075        MuteToggle.prototype.handleClick.call(this, event);
26076      } else if (playPauseKey.call(this, event)) {
26077        event.preventDefault();
26078        event.stopPropagation();
26079        var PlayToggle = Component.getComponent('PlayToggle');
26080        PlayToggle.prototype.handleClick.call(this, event);
26081      }
26082    }
26083    /**
26084     * Check whether the player can play a given mimetype
26085     *
26086     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
vendor: 8,594 bytes, lines 26087-26348
26087     *
26088     * @param {string} type
26089     *        The mimetype to check
26090     *
26091     * @return {string}
26092     *         'probably', 'maybe', or '' (empty string)
26093     */
26094    ;
26095
26096    _proto.canPlayType = function canPlayType(type) {
26097      var can; // Loop through each playback technology in the options order
26098
26099      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
26100        var techName = j[i];
26101        var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
26102        // Remove once that deprecated behavior is removed.
26103
26104        if (!tech) {
26105          tech = Component.getComponent(techName);
26106        } // Check if the current tech is defined before continuing
26107
26108
26109        if (!tech) {
26110          log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
26111          continue;
26112        } // Check if the browser supports this technology
26113
26114
26115        if (tech.isSupported()) {
26116          can = tech.canPlayType(type);
26117
26118          if (can) {
26119            return can;
26120          }
26121        }
26122      }
26123
26124      return '';
26125    }
26126    /**
26127     * Select source based on tech-order or source-order
26128     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
26129     * defaults to tech-order selection
26130     *
26131     * @param {Array} sources
26132     *        The sources for a media asset
26133     *
26134     * @return {Object|boolean}
26135     *         Object of source and tech order or false
26136     */
26137    ;
26138
26139    _proto.selectSource = function selectSource(sources) {
26140      var _this12 = this;
26141
26142      // Get only the techs specified in `techOrder` that exist and are supported by the
26143      // current platform
26144      var techs = this.options_.techOrder.map(function (techName) {
26145        return [techName, Tech.getTech(techName)];
26146      }).filter(function (_ref) {
26147        var techName = _ref[0],
26148            tech = _ref[1];
26149
26150        // Check if the current tech is defined before continuing
26151        if (tech) {
26152          // Check if the browser supports this technology
26153          return tech.isSupported();
26154        }
26155
26156        log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
26157        return false;
26158      }); // Iterate over each `innerArray` element once per `outerArray` element and execute
26159      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
26160      // that value.
26161
26162      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
26163        var found;
26164        outerArray.some(function (outerChoice) {
26165          return innerArray.some(function (innerChoice) {
26166            found = tester(outerChoice, innerChoice);
26167
26168            if (found) {
26169              return true;
26170            }
26171          });
26172        });
26173        return found;
26174      };
26175
26176      var foundSourceAndTech;
26177
26178      var flip = function flip(fn) {
26179        return function (a, b) {
26180          return fn(b, a);
26181        };
26182      };
26183
26184      var finder = function finder(_ref2, source) {
26185        var techName = _ref2[0],
26186            tech = _ref2[1];
26187
26188        if (tech.canPlaySource(source, _this12.options_[techName.toLowerCase()])) {
26189          return {
26190            source: source,
26191            tech: techName
26192          };
26193        }
26194      }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
26195      // to select from them based on their priority.
26196
26197
26198      if (this.options_.sourceOrder) {
26199        // Source-first ordering
26200        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
26201      } else {
26202        // Tech-first ordering
26203        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
26204      }
26205
26206      return foundSourceAndTech || false;
26207    }
26208    /**
26209     * Get or set the video source.
26210     *
26211     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
26212     *        A SourceObject, an array of SourceObjects, or a string referencing
26213     *        a URL to a media source. It is _highly recommended_ that an object
26214     *        or array of objects is used here, so that source selection
26215     *        algorithms can take the `type` into account.
26216     *
26217     *        If not provided, this method acts as a getter.
26218     *
26219     * @return {string|undefined}
26220     *         If the `source` argument is missing, returns the current source
26221     *         URL. Otherwise, returns nothing/undefined.
26222     */
26223    ;
26224
26225    _proto.src = function src(source) {
26226      var _this13 = this;
26227
26228      // getter usage
26229      if (typeof source === 'undefined') {
26230        return this.cache_.src || '';
26231      } // filter out invalid sources and turn our source into
26232      // an array of source objects
26233
26234
26235      var sources = filterSource(source); // if a source was passed in then it is invalid because
26236      // it was filtered to a zero length Array. So we have to
26237      // show an error
26238
26239      if (!sources.length) {
26240        this.setTimeout(function () {
26241          this.error({
26242            code: 4,
26243            message: this.localize(this.options_.notSupportedMessage)
26244          });
26245        }, 0);
26246        return;
26247      } // intial sources
26248
26249
26250      this.changingSrc_ = true;
26251      this.cache_.sources = sources;
26252      this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
26253
26254      setSource(this, sources[0], function (middlewareSource, mws) {
26255        _this13.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
26256        // the source that is selected could be out of order from the cache update above this callback.
26257
26258        _this13.cache_.sources = sources;
26259
26260        _this13.updateSourceCaches_(middlewareSource);
26261
26262        var err = _this13.src_(middlewareSource);
26263
26264        if (err) {
26265          if (sources.length > 1) {
26266            return _this13.src(sources.slice(1));
26267          }
26268
26269          _this13.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
26270
26271          _this13.setTimeout(function () {
26272            this.error({
26273              code: 4,
26274              message: this.localize(this.options_.notSupportedMessage)
26275            });
26276          }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
26277          // this needs a better comment about why this is needed
26278
26279
26280          _this13.triggerReady();
26281
26282          return;
26283        }
26284
26285        setTech(mws, _this13.tech_);
26286      });
26287    }
26288    /**
26289     * Set the source object on the tech, returns a boolean that indicates whether
26290     * there is a tech that can play the source or not
26291     *
26292     * @param {Tech~SourceObject} source
26293     *        The source object to set on the Tech
26294     *
26295     * @return {boolean}
26296     *         - True if there is no Tech to playback this source
26297     *         - False otherwise
26298     *
26299     * @private
26300     */
26301    ;
26302
26303    _proto.src_ = function src_(source) {
26304      var _this14 = this;
26305
26306      var sourceTech = this.selectSource([source]);
26307
26308      if (!sourceTech) {
26309        return true;
26310      }
26311
26312      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
26313        this.changingSrc_ = true; // load this technology with the chosen source
26314
26315        this.loadTech_(sourceTech.tech, sourceTech.source);
26316        this.tech_.ready(function () {
26317          _this14.changingSrc_ = false;
26318        });
26319        return false;
26320      } // wait until the tech is ready to set the source
26321      // and set it synchronously if possible (#2326)
26322
26323
26324      this.ready(function () {
26325        // The setSource tech method was added with source handlers
26326        // so older techs won't support it
26327        // We need to check the direct prototype for the case where subclasses
26328        // of the tech do not support source handlers
26329        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
26330          this.techCall_('setSource', source);
26331        } else {
26332          this.techCall_('src', source.src);
26333        }
26334
26335        this.changingSrc_ = false;
26336      }, true);
26337      return false;
26338    }
26339    /**
26340     * Begin loading the src data.
26341     */
26342    ;
26343
26344    _proto.load = function load() {
26345      this.techCall_('load');
26346    }
26347    /**
26348     * Reset the player. Loads the first tech in the techOrder,
26349     * removes all the text tracks in the existing `tech`,
26350     * and calls `reset` on the `tech`.
26351     */
26352    ;
26353
26354    _proto.reset = function reset() {
26355      var _this15 = this;
26356
26357      var PromiseClass = this.options_.Promise || window$1.Promise;
26358
26359      if (this.paused() || !PromiseClass) {
26360        this.doReset_();
26361      } else {
26362        var playPromise = this.play();
26363        silencePromise(playPromise.then(function () {
26364          return _this15.doReset_();
26365        }));
26366      }
26367    };
26368
26369    _proto.doReset_ = function doReset_() {
26370      if (this.tech_) {
26371        this.tech_.clearTracks('text');
26372      }
26373
26374      this.resetCache_();
26375      this.poster('');
26376      this.loadTech_(this.options_.techOrder[0], null);
26377      this.techCall_('reset');
26378      this.resetControlBarUI_();
26379
26380      if (isEvented(this)) {
26381        this.trigger('playerreset');
26382      }
26383    }
26384    /**
26385     * Reset Control Bar's UI by calling sub-methods that reset
26386     * all of Control Bar's components
26387     */
26388    ;
26389
26390    _proto.resetControlBarUI_ = function resetControlBarUI_() {
26391      this.resetProgressBar_();
26392      this.resetPlaybackRate_();
26393      this.resetVolumeBar_();
26394    }
26395    /**
26396     * Reset tech's progress so progress bar is reset in the UI
26397     */
26398    ;
26399
26400    _proto.resetProgressBar_ = function resetProgressBar_() {
26401      this.currentTime(0);
26402      var _this$controlBar = this.controlBar,
26403          durationDisplay = _this$controlBar.durationDisplay,
26404          remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
26405
26406      if (durationDisplay) {
26407        durationDisplay.updateContent();
26408      }
26409
26410      if (remainingTimeDisplay) {
26411        remainingTimeDisplay.updateContent();
26412      }
26413    }
26414    /**
26415     * Reset Playback ratio
26416     */
26417    ;
26418
26419    _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
26420      this.playbackRate(this.defaultPlaybackRate());
26421      this.handleTechRateChange_();
26422    }
26423    /**
26424     * Reset Volume bar
26425     */
26426    ;
26427
26428    _proto.resetVolumeBar_ = function resetVolumeBar_() {
26429      this.volume(1.0);
26430      this.trigger('volumechange');
26431    }
26432    /**
26433     * Returns all of the current source objects.
26434     *
26435     * @return {Tech~SourceObject[]}
26436     *         The current source objects
26437     */
26438    ;
26439
26440    _proto.currentSources = function currentSources() {
26441      var source = this.currentSource();
26442      var sources = []; // assume `{}` or `{ src }`
26443
26444      if (Object.keys(source).length !== 0) {
26445        sources.push(source);
26446      }
26447
26448      return this.cache_.sources || sources;
26449    }
26450    /**
26451     * Returns the current source object.
26452     *
26453     * @return {Tech~SourceObject}
26454     *         The current source object
26455     */
26456    ;
26457
26458    _proto.currentSource = function currentSource() {
26459      return this.cache_.source || {};
26460    }
26461    /**
26462     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
26463     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
26464     *
26465     * @return {string}
26466     *         The current source
26467     */
26468    ;
26469
26470    _proto.currentSrc = function currentSrc() {
26471      return this.currentSource() && this.currentSource().src || '';
26472    }
26473    /**
26474     * Get the current source type e.g. video/mp4
26475     * This can allow you rebuild the current source object so that you could load the same
26476     * source and tech later
26477     *
26478     * @return {string}
26479     *         The source MIME type
26480     */
26481    ;
26482
26483    _proto.currentType = function currentType() {
26484      return this.currentSource() && this.currentSource().type || '';
26485    }
26486    /**
26487     * Get or set the preload attribute
26488     *
26489     * @param {boolean} [value]
26490     *        - true means that we should preload
26491     *        - false means that we should not preload
26492     *
26493     * @return {string}
26494     *         The preload attribute value when getting
26495     */
26496    ;
26497
26498    _proto.preload = function preload(value) {
26499      if (value !== undefined) {
26500        this.techCall_('setPreload', value);
26501        this.options_.preload = value;
26502        return;
26503      }
26504
26505      return this.techGet_('preload');
26506    }
26507    /**
26508     * Get or set the autoplay option. When this is a boolean it will
26509     * modify the attribute on the tech. When this is a string the attribute on
26510     * the tech will be removed and `Player` will handle autoplay on loadstarts.
26511     *
26512     * @param {boolean|string} [value]
26513     *        - true: autoplay using the browser behavior
26514     *        - false: do not autoplay
26515     *        - 'play': call play() on every loadstart
26516     *        - 'muted': call muted() then play() on every loadstart
26517     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
26518     *        - *: values other than those listed here will be set `autoplay` to true
26519     *
26520     * @return {boolean|string}
26521     *         The current value of autoplay when getting
26522     */
26523    ;
26524
26525    _proto.autoplay = function autoplay(value) {
26526      // getter usage
26527      if (value === undefined) {
26528        return this.options_.autoplay || false;
26529      }
26530
26531      var techAutoplay; // if the value is a valid string set it to that
26532
26533      if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
26534        this.options_.autoplay = value;
26535        this.manualAutoplay_(value);
26536        techAutoplay = false;
vendor: 25,380 bytes, lines 26536-27325
26536 // any falsy value sets autoplay to false in the browser,
26537        // lets do the same
26538      } else if (!value) {
26539        this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
26540      } else {
26541        this.options_.autoplay = true;
26542      }
26543
26544      techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
26545      // a setAutoplay call on tech ready. We do this because the
26546      // autoplay option will be passed in the constructor and we
26547      // do not need to set it twice
26548
26549      if (this.tech_) {
26550        this.techCall_('setAutoplay', techAutoplay);
26551      }
26552    }
26553    /**
26554     * Set or unset the playsinline attribute.
26555     * Playsinline tells the browser that non-fullscreen playback is preferred.
26556     *
26557     * @param {boolean} [value]
26558     *        - true means that we should try to play inline by default
26559     *        - false means that we should use the browser's default playback mode,
26560     *          which in most cases is inline. iOS Safari is a notable exception
26561     *          and plays fullscreen by default.
26562     *
26563     * @return {string|Player}
26564     *         - the current value of playsinline
26565     *         - the player when setting
26566     *
26567     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
26568     */
26569    ;
26570
26571    _proto.playsinline = function playsinline(value) {
26572      if (value !== undefined) {
26573        this.techCall_('setPlaysinline', value);
26574        this.options_.playsinline = value;
26575        return this;
26576      }
26577
26578      return this.techGet_('playsinline');
26579    }
26580    /**
26581     * Get or set the loop attribute on the video element.
26582     *
26583     * @param {boolean} [value]
26584     *        - true means that we should loop the video
26585     *        - false means that we should not loop the video
26586     *
26587     * @return {boolean}
26588     *         The current value of loop when getting
26589     */
26590    ;
26591
26592    _proto.loop = function loop(value) {
26593      if (value !== undefined) {
26594        this.techCall_('setLoop', value);
26595        this.options_.loop = value;
26596        return;
26597      }
26598
26599      return this.techGet_('loop');
26600    }
26601    /**
26602     * Get or set the poster image source url
26603     *
26604     * @fires Player#posterchange
26605     *
26606     * @param {string} [src]
26607     *        Poster image source URL
26608     *
26609     * @return {string}
26610     *         The current value of poster when getting
26611     */
26612    ;
26613
26614    _proto.poster = function poster(src) {
26615      if (src === undefined) {
26616        return this.poster_;
26617      } // The correct way to remove a poster is to set as an empty string
26618      // other falsey values will throw errors
26619
26620
26621      if (!src) {
26622        src = '';
26623      }
26624
26625      if (src === this.poster_) {
26626        return;
26627      } // update the internal poster variable
26628
26629
26630      this.poster_ = src; // update the tech's poster
26631
26632      this.techCall_('setPoster', src);
26633      this.isPosterFromTech_ = false; // alert components that the poster has been set
26634
26635      /**
26636       * This event fires when the poster image is changed on the player.
26637       *
26638       * @event Player#posterchange
26639       * @type {EventTarget~Event}
26640       */
26641
26642      this.trigger('posterchange');
26643    }
26644    /**
26645     * Some techs (e.g. YouTube) can provide a poster source in an
26646     * asynchronous way. We want the poster component to use this
26647     * poster source so that it covers up the tech's controls.
26648     * (YouTube's play button). However we only want to use this
26649     * source if the player user hasn't set a poster through
26650     * the normal APIs.
26651     *
26652     * @fires Player#posterchange
26653     * @listens Tech#posterchange
26654     * @private
26655     */
26656    ;
26657
26658    _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
26659      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
26660        var newPoster = this.tech_.poster() || '';
26661
26662        if (newPoster !== this.poster_) {
26663          this.poster_ = newPoster;
26664          this.isPosterFromTech_ = true; // Let components know the poster has changed
26665
26666          this.trigger('posterchange');
26667        }
26668      }
26669    }
26670    /**
26671     * Get or set whether or not the controls are showing.
26672     *
26673     * @fires Player#controlsenabled
26674     *
26675     * @param {boolean} [bool]
26676     *        - true to turn controls on
26677     *        - false to turn controls off
26678     *
26679     * @return {boolean}
26680     *         The current value of controls when getting
26681     */
26682    ;
26683
26684    _proto.controls = function controls(bool) {
26685      if (bool === undefined) {
26686        return !!this.controls_;
26687      }
26688
26689      bool = !!bool; // Don't trigger a change event unless it actually changed
26690
26691      if (this.controls_ === bool) {
26692        return;
26693      }
26694
26695      this.controls_ = bool;
26696
26697      if (this.usingNativeControls()) {
26698        this.techCall_('setControls', bool);
26699      }
26700
26701      if (this.controls_) {
26702        this.removeClass('vjs-controls-disabled');
26703        this.addClass('vjs-controls-enabled');
26704        /**
26705         * @event Player#controlsenabled
26706         * @type {EventTarget~Event}
26707         */
26708
26709        this.trigger('controlsenabled');
26710
26711        if (!this.usingNativeControls()) {
26712          this.addTechControlsListeners_();
26713        }
26714      } else {
26715        this.removeClass('vjs-controls-enabled');
26716        this.addClass('vjs-controls-disabled');
26717        /**
26718         * @event Player#controlsdisabled
26719         * @type {EventTarget~Event}
26720         */
26721
26722        this.trigger('controlsdisabled');
26723
26724        if (!this.usingNativeControls()) {
26725          this.removeTechControlsListeners_();
26726        }
26727      }
26728    }
26729    /**
26730     * Toggle native controls on/off. Native controls are the controls built into
26731     * devices (e.g. default iPhone controls), Flash, or other techs
26732     * (e.g. Vimeo Controls)
26733     * **This should only be set by the current tech, because only the tech knows
26734     * if it can support native controls**
26735     *
26736     * @fires Player#usingnativecontrols
26737     * @fires Player#usingcustomcontrols
26738     *
26739     * @param {boolean} [bool]
26740     *        - true to turn native controls on
26741     *        - false to turn native controls off
26742     *
26743     * @return {boolean}
26744     *         The current value of native controls when getting
26745     */
26746    ;
26747
26748    _proto.usingNativeControls = function usingNativeControls(bool) {
26749      if (bool === undefined) {
26750        return !!this.usingNativeControls_;
26751      }
26752
26753      bool = !!bool; // Don't trigger a change event unless it actually changed
26754
26755      if (this.usingNativeControls_ === bool) {
26756        return;
26757      }
26758
26759      this.usingNativeControls_ = bool;
26760
26761      if (this.usingNativeControls_) {
26762        this.addClass('vjs-using-native-controls');
26763        /**
26764         * player is using the native device controls
26765         *
26766         * @event Player#usingnativecontrols
26767         * @type {EventTarget~Event}
26768         */
26769
26770        this.trigger('usingnativecontrols');
26771      } else {
26772        this.removeClass('vjs-using-native-controls');
26773        /**
26774         * player is using the custom HTML controls
26775         *
26776         * @event Player#usingcustomcontrols
26777         * @type {EventTarget~Event}
26778         */
26779
26780        this.trigger('usingcustomcontrols');
26781      }
26782    }
26783    /**
26784     * Set or get the current MediaError
26785     *
26786     * @fires Player#error
26787     *
26788     * @param  {MediaError|string|number} [err]
26789     *         A MediaError or a string/number to be turned
26790     *         into a MediaError
26791     *
26792     * @return {MediaError|null}
26793     *         The current MediaError when getting (or null)
26794     */
26795    ;
26796
26797    _proto.error = function error(err) {
26798      if (err === undefined) {
26799        return this.error_ || null;
26800      } // Suppress the first error message for no compatible source until
26801      // user interaction
26802
26803
26804      if (this.options_.suppressNotSupportedError && err && err.code === 4) {
26805        var triggerSuppressedError = function triggerSuppressedError() {
26806          this.error(err);
26807        };
26808
26809        this.options_.suppressNotSupportedError = false;
26810        this.any(['click', 'touchstart'], triggerSuppressedError);
26811        this.one('loadstart', function () {
26812          this.off(['click', 'touchstart'], triggerSuppressedError);
26813        });
26814        return;
26815      } // restoring to default
26816
26817
26818      if (err === null) {
26819        this.error_ = err;
26820        this.removeClass('vjs-error');
26821
26822        if (this.errorDisplay) {
26823          this.errorDisplay.close();
26824        }
26825
26826        return;
26827      }
26828
26829      this.error_ = new MediaError(err); // add the vjs-error classname to the player
26830
26831      this.addClass('vjs-error'); // log the name of the error type and any message
26832      // IE11 logs "[object object]" and required you to expand message to see error object
26833
26834      log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
26835      /**
26836       * @event Player#error
26837       * @type {EventTarget~Event}
26838       */
26839
26840      this.trigger('error');
26841      return;
26842    }
26843    /**
26844     * Report user activity
26845     *
26846     * @param {Object} event
26847     *        Event object
26848     */
26849    ;
26850
26851    _proto.reportUserActivity = function reportUserActivity(event) {
26852      this.userActivity_ = true;
26853    }
26854    /**
26855     * Get/set if user is active
26856     *
26857     * @fires Player#useractive
26858     * @fires Player#userinactive
26859     *
26860     * @param {boolean} [bool]
26861     *        - true if the user is active
26862     *        - false if the user is inactive
26863     *
26864     * @return {boolean}
26865     *         The current value of userActive when getting
26866     */
26867    ;
26868
26869    _proto.userActive = function userActive(bool) {
26870      if (bool === undefined) {
26871        return this.userActive_;
26872      }
26873
26874      bool = !!bool;
26875
26876      if (bool === this.userActive_) {
26877        return;
26878      }
26879
26880      this.userActive_ = bool;
26881
26882      if (this.userActive_) {
26883        this.userActivity_ = true;
26884        this.removeClass('vjs-user-inactive');
26885        this.addClass('vjs-user-active');
26886        /**
26887         * @event Player#useractive
26888         * @type {EventTarget~Event}
26889         */
26890
26891        this.trigger('useractive');
26892        return;
26893      } // Chrome/Safari/IE have bugs where when you change the cursor it can
26894      // trigger a mousemove event. This causes an issue when you're hiding
26895      // the cursor when the user is inactive, and a mousemove signals user
26896      // activity. Making it impossible to go into inactive mode. Specifically
26897      // this happens in fullscreen when we really need to hide the cursor.
26898      //
26899      // When this gets resolved in ALL browsers it can be removed
26900      // https://code.google.com/p/chromium/issues/detail?id=103041
26901
26902
26903      if (this.tech_) {
26904        this.tech_.one('mousemove', function (e) {
26905          e.stopPropagation();
26906          e.preventDefault();
26907        });
26908      }
26909
26910      this.userActivity_ = false;
26911      this.removeClass('vjs-user-active');
26912      this.addClass('vjs-user-inactive');
26913      /**
26914       * @event Player#userinactive
26915       * @type {EventTarget~Event}
26916       */
26917
26918      this.trigger('userinactive');
26919    }
26920    /**
26921     * Listen for user activity based on timeout value
26922     *
26923     * @private
26924     */
26925    ;
26926
26927    _proto.listenForUserActivity_ = function listenForUserActivity_() {
26928      var mouseInProgress;
26929      var lastMoveX;
26930      var lastMoveY;
26931      var handleActivity = bind(this, this.reportUserActivity);
26932
26933      var handleMouseMove = function handleMouseMove(e) {
26934        // #1068 - Prevent mousemove spamming
26935        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
26936        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
26937          lastMoveX = e.screenX;
26938          lastMoveY = e.screenY;
26939          handleActivity();
26940        }
26941      };
26942
26943      var handleMouseDown = function handleMouseDown() {
26944        handleActivity(); // For as long as the they are touching the device or have their mouse down,
26945        // we consider them active even if they're not moving their finger or mouse.
26946        // So we want to continue to update that they are active
26947
26948        this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
26949        // as the activityCheck interval (250) should ensure we never miss the
26950        // next activityCheck
26951
26952        mouseInProgress = this.setInterval(handleActivity, 250);
26953      };
26954
26955      var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) {
26956        handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
26957
26958        this.clearInterval(mouseInProgress);
26959      }; // Any mouse movement will be considered user activity
26960
26961
26962      this.on('mousedown', handleMouseDown);
26963      this.on('mousemove', handleMouseMove);
26964      this.on('mouseup', handleMouseUpAndMouseLeave);
26965      this.on('mouseleave', handleMouseUpAndMouseLeave);
26966      var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
26967      // controlBar would no longer be hidden by default timeout.
26968
26969      if (controlBar && !IS_IOS && !IS_ANDROID) {
26970        controlBar.on('mouseenter', function (event) {
26971          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
26972          this.player().options_.inactivityTimeout = 0;
26973        });
26974        controlBar.on('mouseleave', function (event) {
26975          this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
26976        });
26977      } // Listen for keyboard navigation
26978      // Shouldn't need to use inProgress interval because of key repeat
26979
26980
26981      this.on('keydown', handleActivity);
26982      this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
26983      // the mousemove/touchmove function itself, to prevent performance degradation.
26984      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
26985      // then gets picked up by this loop
26986      // http://ejohn.org/blog/learning-from-twitter/
26987
26988      var inactivityTimeout;
26989      this.setInterval(function () {
26990        // Check to see if mouse/touch activity has happened
26991        if (!this.userActivity_) {
26992          return;
26993        } // Reset the activity tracker
26994
26995
26996        this.userActivity_ = false; // If the user state was inactive, set the state to active
26997
26998        this.userActive(true); // Clear any existing inactivity timeout to start the timer over
26999
27000        this.clearTimeout(inactivityTimeout);
27001        var timeout = this.options_.inactivityTimeout;
27002
27003        if (timeout <= 0) {
27004          return;
27005        } // In <timeout> milliseconds, if no more activity has occurred the
27006        // user will be considered inactive
27007
27008
27009        inactivityTimeout = this.setTimeout(function () {
27010          // Protect against the case where the inactivityTimeout can trigger just
27011          // before the next user activity is picked up by the activity check loop
27012          // causing a flicker
27013          if (!this.userActivity_) {
27014            this.userActive(false);
27015          }
27016        }, timeout);
27017      }, 250);
27018    }
27019    /**
27020     * Gets or sets the current playback rate. A playback rate of
27021     * 1.0 represents normal speed and 0.5 would indicate half-speed
27022     * playback, for instance.
27023     *
27024     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
27025     *
27026     * @param {number} [rate]
27027     *       New playback rate to set.
27028     *
27029     * @return {number}
27030     *         The current playback rate when getting or 1.0
27031     */
27032    ;
27033
27034    _proto.playbackRate = function playbackRate(rate) {
27035      if (rate !== undefined) {
27036        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
27037        // that is registered above
27038        this.techCall_('setPlaybackRate', rate);
27039        return;
27040      }
27041
27042      if (this.tech_ && this.tech_.featuresPlaybackRate) {
27043        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
27044      }
27045
27046      return 1.0;
27047    }
27048    /**
27049     * Gets or sets the current default playback rate. A default playback rate of
27050     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
27051     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
27052     * not the current playbackRate.
27053     *
27054     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
27055     *
27056     * @param {number} [rate]
27057     *       New default playback rate to set.
27058     *
27059     * @return {number|Player}
27060     *         - The default playback rate when getting or 1.0
27061     *         - the player when setting
27062     */
27063    ;
27064
27065    _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
27066      if (rate !== undefined) {
27067        return this.techCall_('setDefaultPlaybackRate', rate);
27068      }
27069
27070      if (this.tech_ && this.tech_.featuresPlaybackRate) {
27071        return this.techGet_('defaultPlaybackRate');
27072      }
27073
27074      return 1.0;
27075    }
27076    /**
27077     * Gets or sets the audio flag
27078     *
27079     * @param {boolean} bool
27080     *        - true signals that this is an audio player
27081     *        - false signals that this is not an audio player
27082     *
27083     * @return {boolean}
27084     *         The current value of isAudio when getting
27085     */
27086    ;
27087
27088    _proto.isAudio = function isAudio(bool) {
27089      if (bool !== undefined) {
27090        this.isAudio_ = !!bool;
27091        return;
27092      }
27093
27094      return !!this.isAudio_;
27095    }
27096    /**
27097     * A helper method for adding a {@link TextTrack} to our
27098     * {@link TextTrackList}.
27099     *
27100     * In addition to the W3C settings we allow adding additional info through options.
27101     *
27102     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
27103     *
27104     * @param {string} [kind]
27105     *        the kind of TextTrack you are adding
27106     *
27107     * @param {string} [label]
27108     *        the label to give the TextTrack label
27109     *
27110     * @param {string} [language]
27111     *        the language to set on the TextTrack
27112     *
27113     * @return {TextTrack|undefined}
27114     *         the TextTrack that was added or undefined
27115     *         if there is no tech
27116     */
27117    ;
27118
27119    _proto.addTextTrack = function addTextTrack(kind, label, language) {
27120      if (this.tech_) {
27121        return this.tech_.addTextTrack(kind, label, language);
27122      }
27123    }
27124    /**
27125     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
27126     * automatically removed from the video element whenever the source changes, unless
27127     * manualCleanup is set to false.
27128     *
27129     * @param {Object} options
27130     *        Options to pass to {@link HTMLTrackElement} during creation. See
27131     *        {@link HTMLTrackElement} for object properties that you should use.
27132     *
27133     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
27134     *
27135     * @return {HtmlTrackElement}
27136     *         the HTMLTrackElement that was created and added
27137     *         to the HtmlTrackElementList and the remote
27138     *         TextTrackList
27139     *
27140     * @deprecated The default value of the "manualCleanup" parameter will default
27141     *             to "false" in upcoming versions of Video.js
27142     */
27143    ;
27144
27145    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
27146      if (this.tech_) {
27147        return this.tech_.addRemoteTextTrack(options, manualCleanup);
27148      }
27149    }
27150    /**
27151     * Remove a remote {@link TextTrack} from the respective
27152     * {@link TextTrackList} and {@link HtmlTrackElementList}.
27153     *
27154     * @param {Object} track
27155     *        Remote {@link TextTrack} to remove
27156     *
27157     * @return {undefined}
27158     *         does not return anything
27159     */
27160    ;
27161
27162    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
27163      if (obj === void 0) {
27164        obj = {};
27165      }
27166
27167      var _obj = obj,
27168          track = _obj.track;
27169
27170      if (!track) {
27171        track = obj;
27172      } // destructure the input into an object with a track argument, defaulting to arguments[0]
27173      // default the whole argument to an empty object if nothing was passed in
27174
27175
27176      if (this.tech_) {
27177        return this.tech_.removeRemoteTextTrack(track);
27178      }
27179    }
27180    /**
27181     * Gets available media playback quality metrics as specified by the W3C's Media
27182     * Playback Quality API.
27183     *
27184     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
27185     *
27186     * @return {Object|undefined}
27187     *         An object with supported media playback quality metrics or undefined if there
27188     *         is no tech or the tech does not support it.
27189     */
27190    ;
27191
27192    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
27193      return this.techGet_('getVideoPlaybackQuality');
27194    }
27195    /**
27196     * Get video width
27197     *
27198     * @return {number}
27199     *         current video width
27200     */
27201    ;
27202
27203    _proto.videoWidth = function videoWidth() {
27204      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
27205    }
27206    /**
27207     * Get video height
27208     *
27209     * @return {number}
27210     *         current video height
27211     */
27212    ;
27213
27214    _proto.videoHeight = function videoHeight() {
27215      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
27216    }
27217    /**
27218     * The player's language code
27219     * NOTE: The language should be set in the player options if you want the
27220     * the controls to be built with a specific language. Changing the language
27221     * later will not update controls text.
27222     *
27223     * @param {string} [code]
27224     *        the language code to set the player to
27225     *
27226     * @return {string}
27227     *         The current language code when getting
27228     */
27229    ;
27230
27231    _proto.language = function language(code) {
27232      if (code === undefined) {
27233        return this.language_;
27234      }
27235
27236      this.language_ = String(code).toLowerCase();
27237    }
27238    /**
27239     * Get the player's language dictionary
27240     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
27241     * Languages specified directly in the player options have precedence
27242     *
27243     * @return {Array}
27244     *         An array of of supported languages
27245     */
27246    ;
27247
27248    _proto.languages = function languages() {
27249      return mergeOptions(Player.prototype.options_.languages, this.languages_);
27250    }
27251    /**
27252     * returns a JavaScript object reperesenting the current track
27253     * information. **DOES not return it as JSON**
27254     *
27255     * @return {Object}
27256     *         Object representing the current of track info
27257     */
27258    ;
27259
27260    _proto.toJSON = function toJSON() {
27261      var options = mergeOptions(this.options_);
27262      var tracks = options.tracks;
27263      options.tracks = [];
27264
27265      for (var i = 0; i < tracks.length; i++) {
27266        var track = tracks[i]; // deep merge tracks and null out player so no circular references
27267
27268        track = mergeOptions(track);
27269        track.player = undefined;
27270        options.tracks[i] = track;
27271      }
27272
27273      return options;
27274    }
27275    /**
27276     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
27277     * component) that immediately overlays the player with arbitrary
27278     * content and removes itself when closed.
27279     *
27280     * @param {string|Function|Element|Array|null} content
27281     *        Same as {@link ModalDialog#content}'s param of the same name.
27282     *        The most straight-forward usage is to provide a string or DOM
27283     *        element.
27284     *
27285     * @param {Object} [options]
27286     *        Extra options which will be passed on to the {@link ModalDialog}.
27287     *
27288     * @return {ModalDialog}
27289     *         the {@link ModalDialog} that was created
27290     */
27291    ;
27292
27293    _proto.createModal = function createModal(content, options) {
27294      var _this16 = this;
27295
27296      options = options || {};
27297      options.content = content || '';
27298      var modal = new ModalDialog(this, options);
27299      this.addChild(modal);
27300      modal.on('dispose', function () {
27301        _this16.removeChild(modal);
27302      });
27303      modal.open();
27304      return modal;
27305    }
27306    /**
27307     * Change breakpoint classes when the player resizes.
27308     *
27309     * @private
27310     */
27311    ;
27312
27313    _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
27314      if (!this.responsive()) {
27315        return;
27316      }
27317
27318      var currentBreakpoint = this.currentBreakpoint();
27319      var currentWidth = this.currentWidth();
27320
27321      for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
27322        var candidateBreakpoint = BREAKPOINT_ORDER[i];
27323        var maxWidth = this.breakpoints_[candidateBreakpoint];
27324
27325        if (currentWidth <= maxWidth) {
27326          // The current breakpoint did not change, nothing to do.
27327          if (currentBreakpoint === candidateBreakpoint) {
27328            return;
27329          } // Only remove a class if there is a current breakpoint.
27330
27331
27332          if (currentBreakpoint) {
27333            this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
27334          }
27335
27336          this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
27337          this.breakpoint_ = candidateBreakpoint;
27338          break;
27339        }
27340      }
27341    }
27342    /**
27343     * Removes the current breakpoint.
27344     *
27345     * @private
27346     */
27347    ;
27348
27349    _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
27350      var className = this.currentBreakpointClass();
27351      this.breakpoint_ = '';
27352
27353      if (className) {
27354        this.removeClass(className);
27355      }
27356    }
27357    /**
27358     * Get or set breakpoints on the player.
27359     *
27360     * Calling this method with an object or `true` will remove any previous
27361     * custom breakpoints and start from the defaults again.
27362     *
27363     * @param  {Object|boolean} [breakpoints]
27364     *         If an object is given, it can be used to provide custom
27365     *         breakpoints. If `true` is given, will set default breakpoints.
27366     *         If this argument is not given, will simply return the current
27367     *         breakpoints.
27368     *
27369     * @param  {number} [breakpoints.tiny]
27370     *         The maximum width for the "vjs-layout-tiny" class.
27371     *
27372     * @param  {number} [breakpoints.xsmall]
27373     *         The maximum width for the "vjs-layout-x-small" class.
27374     *
27375     * @param  {number} [breakpoints.small]
27376     *         The maximum width for the "vjs-layout-small" class.
27377     *
27378     * @param  {number} [breakpoints.medium]
27379     *         The maximum width for the "vjs-layout-medium" class.
27380     *
27381     * @param  {number} [breakpoints.large]
27382     *         The maximum width for the "vjs-layout-large" class.
27383     *
27384     * @param  {number} [breakpoints.xlarge]
27385     *         The maximum width for the "vjs-layout-x-large" class.
27386     *
27387     * @param  {number} [breakpoints.huge]
27388     *         The maximum width for the "vjs-layout-huge" class.
27389     *
27390     * @return {Object}
27391     *         An object mapping breakpoint names to maximum width values.
27392     */
27393    ;
27394
27395    _proto.breakpoints = function breakpoints(_breakpoints) {
27396      // Used as a getter.
27397      if (_breakpoints === undefined) {
27398        return assign(this.breakpoints_);
27399      }
27400
27401      this.breakpoint_ = '';
27402      this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
27403      // selected breakpoint.
27404
27405      this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
27406
27407      return assign(this.breakpoints_);
27408    }
27409    /**
27410     * Get or set a flag indicating whether or not this player should adjust
27411     * its UI based on its dimensions.
27412     *
27413     * @param  {boolean} value
27414     *         Should be `true` if the player should adjust its UI based on its
27415     *         dimensions; otherwise, should be `false`.
27416     *
27417     * @return {boolean}
27418     *         Will be `true` if this player should adjust its UI based on its
27419     *         dimensions; otherwise, will be `false`.
27420     */
27421    ;
27422
27423    _proto.responsive = function responsive(value) {
27424      // Used as a getter.
27425      if (value === undefined) {
27426        return this.responsive_;
27427      }
27428
27429      value = Boolean(value);
27430      var current = this.responsive_; // Nothing changed.
27431
27432      if (value === current) {
27433        return;
27434      } // The value actually changed, set it.
27435
27436
27437      this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
27438      // player is now responsive.
27439
27440      if (value) {
27441        this.on('playerresize', this.updateCurrentBreakpoint_);
27442        this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
27443      } else {
27444        this.off('playerresize', this.updateCurrentBreakpoint_);
27445        this.removeCurrentBreakpoint_();
27446      }
27447
27448      return value;
27449    }
27450    /**
27451     * Get current breakpoint name, if any.
27452     *
27453     * @return {string}
27454     *         If there is currently a breakpoint set, returns a the key from the
27455     *         breakpoints object matching it. Otherwise, returns an empty string.
27456     */
27457    ;
27458
27459    _proto.currentBreakpoint = function currentBreakpoint() {
27460      return this.breakpoint_;
27461    }
27462    /**
27463     * Get the current breakpoint class name.
27464     *
27465     * @return {string}
27466     *         The matching class name (e.g. `"vjs-layout-tiny"` or
27467     *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
27468     *         there is no current breakpoint.
27469     */
27470    ;
27471
27472    _proto.currentBreakpointClass = function currentBreakpointClass() {
27473      return BREAKPOINT_CLASSES[this.breakpoint_] || '';
27474    }
27475    /**
27476     * An object that describes a single piece of media.
27477     *
27478     * Properties that are not part of this type description will be retained; so,
27479     * this can be viewed as a generic metadata storage mechanism as well.
27480     *
27481     * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
27482     * @typedef  {Object} Player~MediaObject
27483     *
27484     * @property {string} [album]
27485     *           Unused, except if this object is passed to the `MediaSession`
27486     *           API.
27487     *
27488     * @property {string} [artist]
27489     *           Unused, except if this object is passed to the `MediaSession`
27490     *           API.
27491     *
27492     * @property {Object[]} [artwork]
27493     *           Unused, except if this object is passed to the `MediaSession`
27494     *           API. If not specified, will be populated via the `poster`, if
27495     *           available.
27496     *
27497     * @property {string} [poster]
27498     *           URL to an image that will display before playback.
27499     *
27500     * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
27501     *           A single source object, an array of source objects, or a string
27502     *           referencing a URL to a media source. It is _highly recommended_
27503     *           that an object or array of objects is used here, so that source
27504     *           selection algorithms can take the `type` into account.
27505     *
27506     * @property {string} [title]
27507     *           Unused, except if this object is passed to the `MediaSession`
27508     *           API.
27509     *
27510     * @property {Object[]} [textTracks]
27511     *           An array of objects to be used to create text tracks, following
27512     *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
27513     *           For ease of removal, these will be created as "remote" text
27514     *           tracks and set to automatically clean up on source changes.
27515     *
27516     *           These objects may have properties like `src`, `kind`, `label`,
27517     *           and `language`, see {@link Tech#createRemoteTextTrack}.
27518     */
27519
27520    /**
27521     * Populate the player using a {@link Player~MediaObject|MediaObject}.
27522     *
27523     * @param  {Player~MediaObject} media
27524     *         A media object.
27525     *
27526     * @param  {Function} ready
27527     *         A callback to be called when the player is ready.
27528     */
27529    ;
27530
27531    _proto.loadMedia = function loadMedia(media, ready) {
27532      var _this17 = this;
27533
27534      if (!media || typeof media !== 'object') {
27535        return;
27536      }
27537
27538      this.reset(); // Clone the media object so it cannot be mutated from outside.
27539
27540      this.cache_.media = mergeOptions(media);
27541      var _this$cache_$media = this.cache_.media,
27542          artwork = _this$cache_$media.artwork,
27543          poster = _this$cache_$media.poster,
27544          src = _this$cache_$media.src,
27545          textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
27546
27547      if (!artwork && poster) {
27548        this.cache_.media.artwork = [{
27549          src: poster,
27550          type: getMimetype(poster)
27551        }];
27552      }
27553
27554      if (src) {
27555        this.src(src);
27556      }
27557
27558      if (poster) {
27559        this.poster(poster);
27560      }
27561
27562      if (Array.isArray(textTracks)) {
27563        textTracks.forEach(function (tt) {
27564          return _this17.addRemoteTextTrack(tt, false);
27565        });
27566      }
27567
27568      this.ready(ready);
27569    }
27570    /**
27571     * Get a clone of the current {@link Player~MediaObject} for this player.
27572     *
27573     * If the `loadMedia` method has not been used, will attempt to return a
27574     * {@link Player~MediaObject} based on the current state of the player.
27575     *
27576     * @return {Player~MediaObject}
27577     */
27578    ;
27579
27580    _proto.getMedia = function getMedia() {
27581      if (!this.cache_.media) {
27582        var poster = this.poster();
27583        var src = this.currentSources();
27584        var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
27585          return {
27586            kind: tt.kind,
27587            label: tt.label,
27588            language: tt.language,
27589            src: tt.src
27590          };
27591        });
27592        var media = {
27593          src: src,
27594          textTracks: textTracks
27595        };
27596
27597        if (poster) {
27598          media.poster = poster;
27599          media.artwork = [{
27600            src: media.poster,
27601            type: getMimetype(media.poster)
27602          }];
27603        }
27604
27605        return media;
27606      }
27607
27608      return mergeOptions(this.cache_.media);
27609    }
27610    /**
27611     * Gets tag settings
27612     *
27613     * @param {Element} tag
27614     *        The player tag
27615     *
27616     * @return {Object}
27617     *         An object containing all of the settings
27618     *         for a player tag
27619     */
27620    ;
27621
27622    Player.getTagSettings = function getTagSettings(tag) {
27623      var baseOptions = {
27624        sources: [],
27625        tracks: []
27626      };
27627      var tagOptions = getAttributes(tag);
27628      var dataSetup = tagOptions['data-setup'];
27629
27630      if (hasClass(tag, 'vjs-fill')) {
27631        tagOptions.fill = true;
27632      }
27633
27634      if (hasClass(tag, 'vjs-fluid')) {
27635        tagOptions.fluid = true;
27636      } // Check if data-setup attr exists.
27637
27638
27639      if (dataSetup !== null) {
27640        // Parse options JSON
27641        // If empty string, make it a parsable json object.
27642        var _safeParseTuple = tuple(dataSetup || '{}'),
27643            err = _safeParseTuple[0],
27644            data = _safeParseTuple[1];
vendor: 7,667 bytes, lines 27645-27874
27645
27646        if (err) {
27647          log.error(err);
27648        }
27649
27650        assign(tagOptions, data);
27651      }
27652
27653      assign(baseOptions, tagOptions); // Get tag children settings
27654
27655      if (tag.hasChildNodes()) {
27656        var children = tag.childNodes;
27657
27658        for (var i = 0, j = children.length; i < j; i++) {
27659          var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
27660
27661          var childName = child.nodeName.toLowerCase();
27662
27663          if (childName === 'source') {
27664            baseOptions.sources.push(getAttributes(child));
27665          } else if (childName === 'track') {
27666            baseOptions.tracks.push(getAttributes(child));
27667          }
27668        }
27669      }
27670
27671      return baseOptions;
27672    }
27673    /**
27674     * Determine whether or not flexbox is supported
27675     *
27676     * @return {boolean}
27677     *         - true if flexbox is supported
27678     *         - false if flexbox is not supported
27679     */
27680    ;
27681
27682    _proto.flexNotSupported_ = function flexNotSupported_() {
27683      var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
27684      // common flex features that we can rely on when checking for flex support.
27685
27686      return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || // IE10-specific (2012 flex spec), available for completeness
27687      'msFlexOrder' in elem.style);
27688    };
27689
27690    return Player;
27691  }(Component);
27692  /**
27693   * Get the {@link VideoTrackList}
27694   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
27695   *
27696   * @return {VideoTrackList}
27697   *         the current video track list
27698   *
27699   * @method Player.prototype.videoTracks
27700   */
27701
27702  /**
27703   * Get the {@link AudioTrackList}
27704   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
27705   *
27706   * @return {AudioTrackList}
27707   *         the current audio track list
27708   *
27709   * @method Player.prototype.audioTracks
27710   */
27711
27712  /**
27713   * Get the {@link TextTrackList}
27714   *
27715   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
27716   *
27717   * @return {TextTrackList}
27718   *         the current text track list
27719   *
27720   * @method Player.prototype.textTracks
27721   */
27722
27723  /**
27724   * Get the remote {@link TextTrackList}
27725   *
27726   * @return {TextTrackList}
27727   *         The current remote text track list
27728   *
27729   * @method Player.prototype.remoteTextTracks
27730   */
27731
27732  /**
27733   * Get the remote {@link HtmlTrackElementList} tracks.
27734   *
27735   * @return {HtmlTrackElementList}
27736   *         The current remote text track element list
27737   *
27738   * @method Player.prototype.remoteTextTrackEls
27739   */
27740
27741
27742  ALL.names.forEach(function (name) {
27743    var props = ALL[name];
27744
27745    Player.prototype[props.getterName] = function () {
27746      if (this.tech_) {
27747        return this.tech_[props.getterName]();
27748      } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
27749      // these will be passed to the tech during loading
27750
27751
27752      this[props.privateName] = this[props.privateName] || new props.ListClass();
27753      return this[props.privateName];
27754    };
27755  });
27756  /**
27757   * Global enumeration of players.
27758   *
27759   * The keys are the player IDs and the values are either the {@link Player}
27760   * instance or `null` for disposed players.
27761   *
27762   * @type {Object}
27763   */
27764
27765  Player.players = {};
27766  var navigator = window$1.navigator;
27767  /*
27768   * Player instance options, surfaced using options
27769   * options = Player.prototype.options_
27770   * Make changes in options, not here.
27771   *
27772   * @type {Object}
27773   * @private
27774   */
27775
27776  Player.prototype.options_ = {
27777    // Default order of fallback technology
27778    techOrder: Tech.defaultTechOrder_,
27779    html5: {},
27780    flash: {},
27781    // default inactivity timeout
27782    inactivityTimeout: 2000,
27783    // default playback rates
27784    playbackRates: [],
27785    // Add playback rate selection by adding rates
27786    // 'playbackRates': [0.5, 1, 1.5, 2],
27787    liveui: false,
27788    // Included control sets
27789    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
27790    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
27791    // locales and their language translations
27792    languages: {},
27793    // Default message to show when a video cannot be played.
27794    notSupportedMessage: 'No compatible source was found for this media.',
27795    fullscreen: {
27796      options: {
27797        navigationUI: 'hide'
27798      }
27799    },
27800    breakpoints: {},
27801    responsive: false
27802  };
27803  [
27804  /**
27805   * Returns whether or not the player is in the "ended" state.
27806   *
27807   * @return {Boolean} True if the player is in the ended state, false if not.
27808   * @method Player#ended
27809   */
27810  'ended',
27811  /**
27812   * Returns whether or not the player is in the "seeking" state.
27813   *
27814   * @return {Boolean} True if the player is in the seeking state, false if not.
27815   * @method Player#seeking
27816   */
27817  'seeking',
27818  /**
27819   * Returns the TimeRanges of the media that are currently available
27820   * for seeking to.
27821   *
27822   * @return {TimeRanges} the seekable intervals of the media timeline
27823   * @method Player#seekable
27824   */
27825  'seekable',
27826  /**
27827   * Returns the current state of network activity for the element, from
27828   * the codes in the list below.
27829   * - NETWORK_EMPTY (numeric value 0)
27830   *   The element has not yet been initialised. All attributes are in
27831   *   their initial states.
27832   * - NETWORK_IDLE (numeric value 1)
27833   *   The element's resource selection algorithm is active and has
27834   *   selected a resource, but it is not actually using the network at
27835   *   this time.
27836   * - NETWORK_LOADING (numeric value 2)
27837   *   The user agent is actively trying to download data.
27838   * - NETWORK_NO_SOURCE (numeric value 3)
27839   *   The element's resource selection algorithm is active, but it has
27840   *   not yet found a resource to use.
27841   *
27842   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
27843   * @return {number} the current network activity state
27844   * @method Player#networkState
27845   */
27846  'networkState',
27847  /**
27848   * Returns a value that expresses the current state of the element
27849   * with respect to rendering the current playback position, from the
27850   * codes in the list below.
27851   * - HAVE_NOTHING (numeric value 0)
27852   *   No information regarding the media resource is available.
27853   * - HAVE_METADATA (numeric value 1)
27854   *   Enough of the resource has been obtained that the duration of the
27855   *   resource is available.
27856   * - HAVE_CURRENT_DATA (numeric value 2)
27857   *   Data for the immediate current playback position is available.
27858   * - HAVE_FUTURE_DATA (numeric value 3)
27859   *   Data for the immediate current playback position is available, as
27860   *   well as enough data for the user agent to advance the current
27861   *   playback position in the direction of playback.
27862   * - HAVE_ENOUGH_DATA (numeric value 4)
27863   *   The user agent estimates that enough data is available for
27864   *   playback to proceed uninterrupted.
27865   *
27866   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
27867   * @return {number} the current playback rendering state
27868   * @method Player#readyState
27869   */
27870  'readyState'].forEach(function (fn) {
27871    Player.prototype[fn] = function () {
27872      return this.techGet_(fn);
27873    };
27874  });
27875  TECH_EVENTS_RETRIGGER.forEach(function (event) {
27876    Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
27877      return this.trigger(event);
27878    };
27879  });
27880  /**
27881   * Fired when the player has initial duration and dimension information
27882   *
27883   * @event Player#loadedmetadata
27884   * @type {EventTarget~Event}
27885   */
27886
27887  /**
27888   * Fired when the player has downloaded data at the current playback position
27889   *
27890   * @event Player#loadeddata
27891   * @type {EventTarget~Event}
27892   */
27893
27894  /**
27895   * Fired when the current playback position has changed *
27896   * During playback this is fired every 15-250 milliseconds, depending on the
27897   * playback technology in use.
27898   *
27899   * @event Player#timeupdate
27900   * @type {EventTarget~Event}
27901   */
27902
27903  /**
27904   * Fired when the volume changes
27905   *
27906   * @event Player#volumechange
27907   * @type {EventTarget~Event}
27908   */
27909
27910  /**
27911   * Reports whether or not a player has a plugin available.
27912   *
27913   * This does not report whether or not the plugin has ever been initialized
27914   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
27915   *
27916   * @method Player#hasPlugin
27917   * @param  {string}  name
27918   *         The name of a plugin.
27919   *
27920   * @return {boolean}
27921   *         Whether or not this player has the requested plugin available.
27922   */
27923
27924  /**
27925   * Reports whether or not a player is using a plugin by name.
27926   *
27927   * For basic plugins, this only reports whether the plugin has _ever_ been
27928   * initialized on this player.
27929   *
27930   * @method Player#usingPlugin
27931   * @param  {string} name
27932   *         The name of a plugin.
27933   *
27934   * @return {boolean}
27935   *         Whether or not this player is using the requested plugin.
27936   */
27937
27938  Component.registerComponent('Player', Player);
27939
27940  var setPrototypeOf = createCommonjsModule(function (module) {
27941    function _setPrototypeOf(o, p) {
27942      module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
27943        o.__proto__ = p;
27944        return o;
27945      };
27946
27947      return _setPrototypeOf(o, p);
27948    }
27949
27950    module.exports = _setPrototypeOf;
27951  });
27952
27953  var construct = createCommonjsModule(function (module) {
27954    function isNativeReflectConstruct() {
27955      if (typeof Reflect === "undefined" || !Reflect.construct) return false;
27956      if (Reflect.construct.sham) return false;
27957      if (typeof Proxy === "function") return true;
27958
27959      try {
27960        Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
27961        return true;
27962      } catch (e) {
27963        return false;
27964      }
27965    }
27966
27967    function _construct(Parent, args, Class) {
27968      if (isNativeReflectConstruct()) {
27969        module.exports = _construct = Reflect.construct;
27970      } else {
27971        module.exports = _construct = function _construct(Parent, args, Class) {
27972          var a = [null];
27973          a.push.apply(a, args);
27974          var Constructor = Function.bind.apply(Parent, a);
27975          var instance = new Constructor();
27976          if (Class) setPrototypeOf(instance, Class.prototype);
27977          return instance;
27978        };
27979      }
27980
27981      return _construct.apply(null, arguments);
27982    }
27983
27984    module.exports = _construct;
27985  });
27986
27987  /**
27988   * The base plugin name.
27989   *
27990   * @private
27991   * @constant
27992   * @type {string}
27993   */
27994
27995  var BASE_PLUGIN_NAME = 'plugin';
27996  /**
27997   * The key on which a player's active plugins cache is stored.
27998   *
27999   * @private
28000   * @constant
28001   * @type     {string}
28002   */
28003
28004  var PLUGIN_CACHE_KEY = 'activePlugins_';
28005  /**
28006   * Stores registered plugins in a private space.
28007   *
28008   * @private
28009   * @type    {Object}
28010   */
28011
28012  var pluginStorage = {};
28013  /**
28014   * Reports whether or not a plugin has been registered.
28015   *
28016   * @private
28017   * @param   {string} name
28018   *          The name of a plugin.
28019   *
28020   * @return {boolean}
28021   *          Whether or not the plugin has been registered.
28022   */
28023
28024  var pluginExists = function pluginExists(name) {
28025    return pluginStorage.hasOwnProperty(name);
28026  };
28027  /**
28028   * Get a single registered plugin by name.
28029   *
28030   * @private
28031   * @param   {string} name
28032   *          The name of a plugin.
28033   *
28034   * @return {Function|undefined}
28035   *          The plugin (or undefined).
28036   */
28037
28038
28039  var getPlugin = function getPlugin(name) {
28040    return pluginExists(name) ? pluginStorage[name] : undefined;
28041  };
28042  /**
28043   * Marks a plugin as "active" on a player.
28044   *
28045   * Also, ensures that the player has an object for tracking active plugins.
28046   *
28047   * @private
28048   * @param   {Player} player
28049   *          A Video.js player instance.
28050   *
28051   * @param   {string} name
28052   *          The name of a plugin.
28053   */
28054
28055
28056  var markPluginAsActive = function markPluginAsActive(player, name) {
28057    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
28058    player[PLUGIN_CACHE_KEY][name] = true;
28059  };
28060  /**
28061   * Triggers a pair of plugin setup events.
28062   *
28063   * @private
28064   * @param  {Player} player
28065   *         A Video.js player instance.
28066   *
28067   * @param  {Plugin~PluginEventHash} hash
28068   *         A plugin event hash.
28069   *
28070   * @param  {boolean} [before]
28071   *         If true, prefixes the event name with "before". In other words,
28072   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
28073   */
28074
28075
28076  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
28077    var eventName = (before ? 'before' : '') + 'pluginsetup';
28078    player.trigger(eventName, hash);
28079    player.trigger(eventName + ':' + hash.name, hash);
28080  };
28081  /**
28082   * Takes a basic plugin function and returns a wrapper function which marks
28083   * on the player that the plugin has been activated.
28084   *
28085   * @private
28086   * @param   {string} name
28087   *          The name of the plugin.
28088   *
28089   * @param   {Function} plugin
28090   *          The basic plugin.
28091   *
28092   * @return {Function}
28093   *          A wrapper function for the given plugin.
28094   */
28095
28096
28097  var createBasicPlugin = function createBasicPlugin(name, plugin) {
28098    var basicPluginWrapper = function basicPluginWrapper() {
28099      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
28100      // regardless, but we want the hash to be consistent with the hash provided
28101      // for advanced plugins.
28102      //
28103      // The only potentially counter-intuitive thing here is the `instance` in
28104      // the "pluginsetup" event is the value returned by the `plugin` function.
28105      triggerSetupEvent(this, {
28106        name: name,
28107        plugin: plugin,
28108        instance: null
28109      }, true);
28110      var instance = plugin.apply(this, arguments);
28111      markPluginAsActive(this, name);
28112      triggerSetupEvent(this, {
28113        name: name,
28114        plugin: plugin,
28115        instance: instance
28116      });
28117      return instance;
28118    };
28119
28120    Object.keys(plugin).forEach(function (prop) {
28121      basicPluginWrapper[prop] = plugin[prop];
28122    });
28123    return basicPluginWrapper;
28124  };
28125  /**
28126   * Takes a plugin sub-class and returns a factory function for generating
28127   * instances of it.
28128   *
28129   * This factory function will replace itself with an instance of the requested
28130   * sub-class of Plugin.
28131   *
28132   * @private
28133   * @param   {string} name
28134   *          The name of the plugin.
28135   *
28136   * @param   {Plugin} PluginSubClass
28137   *          The advanced plugin.
28138   *
28139   * @return {Function}
28140   */
28141
28142
28143  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
28144    // Add a `name` property to the plugin prototype so that each plugin can
28145    // refer to itself by name.
28146    PluginSubClass.prototype.name = name;
28147    return function () {
28148      triggerSetupEvent(this, {
28149        name: name,
28150        plugin: PluginSubClass,
28151        instance: null
28152      }, true);
28153
28154      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
28155        args[_key] = arguments[_key];
28156      }
28157
28158      var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
28159
28160
28161      this[name] = function () {
28162        return instance;
28163      };
28164
28165      triggerSetupEvent(this, instance.getEventHash());
28166      return instance;
28167    };
28168  };
28169  /**
28170   * Parent class for all advanced plugins.
28171   *
28172   * @mixes   module:evented~EventedMixin
28173   * @mixes   module:stateful~StatefulMixin
28174   * @fires   Player#beforepluginsetup
28175   * @fires   Player#beforepluginsetup:$name
28176   * @fires   Player#pluginsetup
28177   * @fires   Player#pluginsetup:$name
28178   * @listens Player#dispose
28179   * @throws  {Error}
28180   *          If attempting to instantiate the base {@link Plugin} class
28181   *          directly instead of via a sub-class.
28182   */
28183
28184
28185  var Plugin =
28186  /*#__PURE__*/
28187  function () {
28188    /**
28189     * Creates an instance of this class.
28190     *
28191     * Sub-classes should call `super` to ensure plugins are properly initialized.
28192     *
28193     * @param {Player} player
28194     *        A Video.js player instance.
28195     */
28196    function Plugin(player) {
28197      if (this.constructor === Plugin) {
28198        throw new Error('Plugin must be sub-classed; not directly instantiated.');
28199      }
28200
28201      this.player = player; // Make this object evented, but remove the added `trigger` method so we
28202      // use the prototype version instead.
28203
28204      evented(this);
28205      delete this.trigger;
28206      stateful(this, this.constructor.defaultState);
28207      markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
28208      // it later easily.
28209
28210      this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
28211
28212      player.on('dispose', this.dispose);
28213    }
28214    /**
28215     * Get the version of the plugin that was set on <pluginName>.VERSION
28216     */
28217
28218
28219    var _proto = Plugin.prototype;
28220
28221    _proto.version = function version() {
28222      return this.constructor.VERSION;
28223    }
28224    /**
28225     * Each event triggered by plugins includes a hash of additional data with
28226     * conventional properties.
28227     *
28228     * This returns that object or mutates an existing hash.
28229     *
28230     * @param   {Object} [hash={}]
28231     *          An object to be used as event an event hash.
28232     *
28233     * @return {Plugin~PluginEventHash}
28234     *          An event hash object with provided properties mixed-in.
28235     */
28236    ;
28237
28238    _proto.getEventHash = function getEventHash(hash) {
28239      if (hash === void 0) {
28240        hash = {};
28241      }
28242
28243      hash.name = this.name;
28244      hash.plugin = this.constructor;
28245      hash.instance = this;
28246      return hash;
28247    }
28248    /**
28249     * Triggers an event on the plugin object and overrides
28250     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
28251     *
28252     * @param   {string|Object} event
28253     *          An event type or an object with a type property.
28254     *
28255     * @param   {Object} [hash={}]
28256     *          Additional data hash to merge with a
28257     *          {@link Plugin~PluginEventHash|PluginEventHash}.
28258     *
28259     * @return {boolean}
28260     *          Whether or not default was prevented.
28261     */
28262    ;
28263
28264    _proto.trigger = function trigger$1(event, hash) {
28265      if (hash === void 0) {
28266        hash = {};
28267      }
28268
28269      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
28270    }
28271    /**
28272     * Handles "statechanged" events on the plugin. No-op by default, override by
28273     * subclassing.
28274     *
28275     * @abstract
28276     * @param    {Event} e
28277     *           An event object provided by a "statechanged" event.
28278     *
28279     * @param    {Object} e.changes
28280     *           An object describing changes that occurred with the "statechanged"
28281     *           event.
28282     */
28283    ;
28284
28285    _proto.handleStateChanged = function handleStateChanged(e) {}
28286    /**
vendor: 4,134 bytes, lines 28287-28401
28287     * Disposes a plugin.
28288     *
28289     * Subclasses can override this if they want, but for the sake of safety,
28290     * it's probably best to subscribe the "dispose" event.
28291     *
28292     * @fires Plugin#dispose
28293     */
28294    ;
28295
28296    _proto.dispose = function dispose() {
28297      var name = this.name,
28298          player = this.player;
28299      /**
28300       * Signals that a advanced plugin is about to be disposed.
28301       *
28302       * @event Plugin#dispose
28303       * @type  {EventTarget~Event}
28304       */
28305
28306      this.trigger('dispose');
28307      this.off();
28308      player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
28309      // references between the player and the plugin instance and nulling out
28310      // the plugin's state and replacing methods with a function that throws.
28311
28312      player[PLUGIN_CACHE_KEY][name] = false;
28313      this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
28314      // function, so that the plugin is ready to be set up again.
28315
28316      player[name] = createPluginFactory(name, pluginStorage[name]);
28317    }
28318    /**
28319     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
28320     *
28321     * @param   {string|Function} plugin
28322     *          If a string, matches the name of a plugin. If a function, will be
28323     *          tested directly.
28324     *
28325     * @return {boolean}
28326     *          Whether or not a plugin is a basic plugin.
28327     */
28328    ;
28329
28330    Plugin.isBasic = function isBasic(plugin) {
28331      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
28332      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
28333    }
28334    /**
28335     * Register a Video.js plugin.
28336     *
28337     * @param   {string} name
28338     *          The name of the plugin to be registered. Must be a string and
28339     *          must not match an existing plugin or a method on the `Player`
28340     *          prototype.
28341     *
28342     * @param   {Function} plugin
28343     *          A sub-class of `Plugin` or a function for basic plugins.
28344     *
28345     * @return {Function}
28346     *          For advanced plugins, a factory function for that plugin. For
28347     *          basic plugins, a wrapper function that initializes the plugin.
28348     */
28349    ;
28350
28351    Plugin.registerPlugin = function registerPlugin(name, plugin) {
28352      if (typeof name !== 'string') {
28353        throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
28354      }
28355
28356      if (pluginExists(name)) {
28357        log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
28358      } else if (Player.prototype.hasOwnProperty(name)) {
28359        throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
28360      }
28361
28362      if (typeof plugin !== 'function') {
28363        throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
28364      }
28365
28366      pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
28367      // the base Plugin class).
28368
28369      if (name !== BASE_PLUGIN_NAME) {
28370        if (Plugin.isBasic(plugin)) {
28371          Player.prototype[name] = createBasicPlugin(name, plugin);
28372        } else {
28373          Player.prototype[name] = createPluginFactory(name, plugin);
28374        }
28375      }
28376
28377      return plugin;
28378    }
28379    /**
28380     * De-register a Video.js plugin.
28381     *
28382     * @param  {string} name
28383     *         The name of the plugin to be de-registered. Must be a string that
28384     *         matches an existing plugin.
28385     *
28386     * @throws {Error}
28387     *         If an attempt is made to de-register the base plugin.
28388     */
28389    ;
28390
28391    Plugin.deregisterPlugin = function deregisterPlugin(name) {
28392      if (name === BASE_PLUGIN_NAME) {
28393        throw new Error('Cannot de-register base plugin.');
28394      }
28395
28396      if (pluginExists(name)) {
28397        delete pluginStorage[name];
28398        delete Player.prototype[name];
28399      }
28400    }
28401    /**
28402     * Gets an object containing multiple Video.js plugins.
28403     *
28404     * @param   {Array} [names]
28405     *          If provided, should be an array of plugin names. Defaults to _all_
28406     *          plugin names.
28407     *
28408     * @return {Object|undefined}
28409     *          An object containing plugin(s) associated with their name(s) or
28410     *          `undefined` if no matching plugins exist).
28411     */
28412    ;
28413
28414    Plugin.getPlugins = function getPlugins(names) {
28415      if (names === void 0) {
28416        names = Object.keys(pluginStorage);
28417      }
28418
28419      var result;
28420      names.forEach(function (name) {
28421        var plugin = getPlugin(name);
28422
28423        if (plugin) {
28424          result = result || {};
28425          result[name] = plugin;
28426        }
28427      });
28428      return result;
28429    }
28430    /**
28431     * Gets a plugin's version, if available
28432     *
28433     * @param   {string} name
28434     *          The name of a plugin.
28435     *
28436     * @return {string}
28437     *          The plugin's version or an empty string.
28438     */
28439    ;
28440
28441    Plugin.getPluginVersion = function getPluginVersion(name) {
28442      var plugin = getPlugin(name);
28443      return plugin && plugin.VERSION || '';
28444    };
28445
28446    return Plugin;
28447  }();
28448  /**
28449   * Gets a plugin by name if it exists.
28450   *
28451   * @static
28452   * @method   getPlugin
28453   * @memberOf Plugin
28454   * @param    {string} name
28455   *           The name of a plugin.
28456   *
28457   * @returns  {Function|undefined}
28458   *           The plugin (or `undefined`).
28459   */
28460
28461
28462  Plugin.getPlugin = getPlugin;
28463  /**
28464   * The name of the base plugin class as it is registered.
28465   *
28466   * @type {string}
28467   */
28468
28469  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
28470  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
28471  /**
28472   * Documented in player.js
28473   *
28474   * @ignore
28475   */
28476
28477  Player.prototype.usingPlugin = function (name) {
28478    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
28479  };
28480  /**
28481   * Documented in player.js
28482   *
28483   * @ignore
28484   */
28485
28486
28487  Player.prototype.hasPlugin = function (name) {
28488    return !!pluginExists(name);
28489  };
28490  /**
28491   * Signals that a plugin is about to be set up on a player.
28492   *
28493   * @event    Player#beforepluginsetup
28494   * @type     {Plugin~PluginEventHash}
28495   */
28496
28497  /**
28498   * Signals that a plugin is about to be set up on a player - by name. The name
28499   * is the name of the plugin.
28500   *
28501   * @event    Player#beforepluginsetup:$name
28502   * @type     {Plugin~PluginEventHash}
28503   */
28504
28505  /**
28506   * Signals that a plugin has just been set up on a player.
28507   *
28508   * @event    Player#pluginsetup
28509   * @type     {Plugin~PluginEventHash}
28510   */
28511
28512  /**
28513   * Signals that a plugin has just been set up on a player - by name. The name
28514   * is the name of the plugin.
28515   *
28516   * @event    Player#pluginsetup:$name
28517   * @type     {Plugin~PluginEventHash}
28518   */
28519
28520  /**
28521   * @typedef  {Object} Plugin~PluginEventHash
28522   *
28523   * @property {string} instance
28524   *           For basic plugins, the return value of the plugin function. For
28525   *           advanced plugins, the plugin instance on which the event is fired.
28526   *
28527   * @property {string} name
28528   *           The name of the plugin.
28529   *
28530   * @property {string} plugin
28531   *           For basic plugins, the plugin function. For advanced plugins, the
28532   *           plugin class/constructor.
28533   */
28534
28535  function _inherits(subClass, superClass) {
28536    if (typeof superClass !== "function" && superClass !== null) {
28537      throw new TypeError("Super expression must either be null or a function");
28538    }
28539
28540    subClass.prototype = Object.create(superClass && superClass.prototype, {
28541      constructor: {
28542        value: subClass,
28543        writable: true,
28544        configurable: true
28545      }
28546    });
28547    if (superClass) setPrototypeOf(subClass, superClass);
28548  }
28549
28550  var inherits = _inherits;
28551
28552  /**
28553   * @file extend.js
28554   * @module extend
28555   */
28556  /**
28557   * Used to subclass an existing class by emulating ES subclassing using the
28558   * `extends` keyword.
28559   *
28560   * @function
28561   * @example
28562   * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
28563   *   myCustomMethod: function() {
28564   *     // Do things in my method.
28565   *   }
28566   * });
28567   *
28568   * @param    {Function} superClass
28569   *           The class to inherit from
28570   *
28571   * @param    {Object}   [subClassMethods={}]
28572   *           Methods of the new class
28573   *
28574   * @return   {Function}
28575   *           The new class with subClassMethods that inherited superClass.
28576   */
28577
28578  var extend = function extend(superClass, subClassMethods) {
28579    if (subClassMethods === void 0) {
28580      subClassMethods = {};
28581    }
28582
28583    var subClass = function subClass() {
28584      superClass.apply(this, arguments);
28585    };
28586
28587    var methods = {};
28588
28589    if (typeof subClassMethods === 'object') {
28590      if (subClassMethods.constructor !== Object.prototype.constructor) {
28591        subClass = subClassMethods.constructor;
28592      }
28593
28594      methods = subClassMethods;
28595    } else if (typeof subClassMethods === 'function') {
28596      subClass = subClassMethods;
28597    }
28598
28599    inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility.
28600
28601
28602    if (superClass) {
28603      subClass.super_ = superClass;
28604    } // Extend subObj's prototype with functions and other properties from props
28605
28606
28607    for (var name in methods) {
28608      if (methods.hasOwnProperty(name)) {
28609        subClass.prototype[name] = methods[name];
28610      }
28611    }
28612
28613    return subClass;
28614  };
28615
28616  /**
28617   * @file video.js
28618   * @module videojs
28619   */
28620  /**
28621   * Normalize an `id` value by trimming off a leading `#`
28622   *
28623   * @private
28624   * @param   {string} id
28625   *          A string, maybe with a leading `#`.
28626   *
28627   * @return {string}
28628   *          The string, without any leading `#`.
28629   */
28630
28631  var normalizeId = function normalizeId(id) {
28632    return id.indexOf('#') === 0 ? id.slice(1) : id;
28633  };
28634  /**
28635   * The `videojs()` function doubles as the main function for users to create a
28636   * {@link Player} instance as well as the main library namespace.
28637   *
28638   * It can also be used as a getter for a pre-existing {@link Player} instance.
28639   * However, we _strongly_ recommend using `videojs.getPlayer()` for this
28640   * purpose because it avoids any potential for unintended initialization.
28641   *
28642   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
28643   * of our JSDoc template, we cannot properly document this as both a function
28644   * and a namespace, so its function signature is documented here.
28645   *
28646   * #### Arguments
28647   * ##### id
28648   * string|Element, **required**
28649   *
28650   * Video element or video element ID.
28651   *
28652   * ##### options
28653   * Object, optional
28654   *
28655   * Options object for providing settings.
28656   * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
28657   *
28658   * ##### ready
28659   * {@link Component~ReadyCallback}, optional
28660   *
28661   * A function to be called when the {@link Player} and {@link Tech} are ready.
28662   *
28663   * #### Return Value
28664   *
28665   * The `videojs()` function returns a {@link Player} instance.
28666   *
28667   * @namespace
28668   *
28669   * @borrows AudioTrack as AudioTrack
28670   * @borrows Component.getComponent as getComponent
28671   * @borrows module:computed-style~computedStyle as computedStyle
28672   * @borrows module:events.on as on
28673   * @borrows module:events.one as one
28674   * @borrows module:events.off as off
28675   * @borrows module:events.trigger as trigger
28676   * @borrows EventTarget as EventTarget
28677   * @borrows module:extend~extend as extend
28678   * @borrows module:fn.bind as bind
28679   * @borrows module:format-time.formatTime as formatTime
28680   * @borrows module:format-time.resetFormatTime as resetFormatTime
28681   * @borrows module:format-time.setFormatTime as setFormatTime
28682   * @borrows module:merge-options.mergeOptions as mergeOptions
28683   * @borrows module:middleware.use as use
28684   * @borrows Player.players as players
28685   * @borrows Plugin.registerPlugin as registerPlugin
28686   * @borrows Plugin.deregisterPlugin as deregisterPlugin
28687   * @borrows Plugin.getPlugins as getPlugins
28688   * @borrows Plugin.getPlugin as getPlugin
28689   * @borrows Plugin.getPluginVersion as getPluginVersion
28690   * @borrows Tech.getTech as getTech
28691   * @borrows Tech.registerTech as registerTech
28692   * @borrows TextTrack as TextTrack
28693   * @borrows module:time-ranges.createTimeRanges as createTimeRange
28694   * @borrows module:time-ranges.createTimeRanges as createTimeRanges
28695   * @borrows module:url.isCrossOrigin as isCrossOrigin
28696   * @borrows module:url.parseUrl as parseUrl
28697   * @borrows VideoTrack as VideoTrack
28698   *
28699   * @param  {string|Element}
28699 id
28700   *         Video element or video element ID.
28701   *
28702   * @param  {Object} [options]
28703   *         Options object for providing settings.
28704   *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
28705   *
28706   * @param  {Component~ReadyCallback} [ready]
28707   *         A function to be called when the {@link Player} and {@link Tech} are
28708   *         ready.
28709   *
28710   * @return {Player}
28711   *         The `videojs()` function returns a {@link Player|Player} instance.
28712   */
28713
28714
28715  function videojs$1(id, options, ready) {
28716    var player = videojs$1.getPlayer(id);
28717
28718    if (player) {
28719      if (options) {
28720        log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
28721      }
28722
28723      if (ready) {
28724        player.ready(ready);
28725      }
28726
28727      return player;
28728    }
28729
28730    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
28731
28732    if (!isEl(el)) {
28733      throw new TypeError('The element or ID supplied is not valid. (videojs)');
28734    } // document.body.contains(el) will only check if el is contained within that one document.
28735    // This causes problems for elements in iframes.
28736    // Instead, use the element's ownerDocument instead of the global document.
28737    // This will make sure that the element is indeed in the dom of that document.
28738    // Additionally, check that the document in question has a default view.
28739    // If the document is no longer attached to the dom, the defaultView of the document will be null.
28740
28741
28742    if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
28743      log.warn('The element supplied is not included in the DOM');
28744    }
28745
28746    options = options || {};
28747    videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
28748      var opts = hookFunction(el, mergeOptions(options));
28749
28750      if (!isObject(opts) || Array.isArray(opts)) {
28751        log.error('please return an object in beforesetup hooks');
28752        return;
28753      }
28754
28755      options = mergeOptions(options, opts);
28756    }); // We get the current "Player" component here in case an integration has
28757    // replaced it with a custom player.
28758
28759    var PlayerComponent = Component.getComponent('Player');
28760    player = new PlayerComponent(el, options, ready);
28761    videojs$1.hooks('setup').forEach(function (hookFunction) {
28762      return hookFunction(player);
28763    });
28764    return player;
28765  }
28766  /**
28767   * An Object that contains lifecycle hooks as keys which point to an array
28768   * of functions that are run when a lifecycle is triggered
28769   *
28770   * @private
28771   */
28772
28773
28774  videojs$1.hooks_ = {};
28775  /**
28776   * Get a list of hooks for a specific lifecycle
28777   *
28778   * @param  {string} type
28779   *         the lifecyle to get hooks from
28780   *
28781   * @param  {Function|Function[]} [fn]
28782   *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
28783   *
28784   * @return {Array}
28785   *         an array of hooks, or an empty array if there are none.
28786   */
28787
28788  videojs$1.hooks = function (type, fn) {
28789    videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
28790
28791    if (fn) {
28792      videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
28793    }
28794
28795    return videojs$1.hooks_[type];
28796  };
28797  /**
28798   * Add a function hook to a specific videojs lifecycle.
28799   *
28800   * @param {string} type
28801   *        the lifecycle to hook the function to.
28802   *
28803   * @param {Function|Function[]}
28804   *        The function or array of functions to attach.
28805   */
28806
28807
28808  videojs$1.hook = function (type, fn) {
28809    videojs$1.hooks(type, fn);
28810  };
28811  /**
28812   * Add a function hook that will only run once to a specific videojs lifecycle.
28813   *
28814   * @param {string} type
28815   *        the lifecycle to hook the function to.
28816   *
28817   * @param {Function|Function[]}
28818   *        The function or array of functions to attach.
28819   */
28820
28821
28822  videojs$1.hookOnce = function (type, fn) {
28823    videojs$1.hooks(type, [].concat(fn).map(function (original) {
28824      var wrapper = function wrapper() {
28825        videojs$1.removeHook(type, wrapper);
28826        return original.apply(void 0, arguments);
28827      };
28828
28829      return wrapper;
28830    }));
28831  };
28832  /**
28833   * Remove a hook from a specific videojs lifecycle.
28834   *
28835   * @param  {string} type
28836   *         the lifecycle that the function hooked to
28837   *
28838   * @param  {Function} fn
28839   *         The hooked function to remove
28840   *
28841   * @return {boolean}
28842   *         The function that was removed or undef
28843   */
28844
28845
vendor: 13,853 bytes, lines 28846-29249
28846  videojs$1.removeHook = function (type, fn) {
28847    var index = videojs$1.hooks(type).indexOf(fn);
28848
28849    if (index <= -1) {
28850      return false;
28851    }
28852
28853    videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
28854    videojs$1.hooks_[type].splice(index, 1);
28855    return true;
28856  }; // Add default styles
28857
28858
28859  if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
28860    var style = $('.vjs-styles-defaults');
28861
28862    if (!style) {
28863      style = createStyleElement('vjs-styles-defaults');
28864      var head = $('head');
28865
28866      if (head) {
28867        head.insertBefore(style, head.firstChild);
28868      }
28869
28870      setTextContent(style, "\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ");
28871    }
28872  } // Run Auto-load players
28873  // You have to wait at least once in case this script is loaded after your
28874  // video in the DOM (weird behavior only with minified version)
28875
28876
28877  autoSetupTimeout(1, videojs$1);
28878  /**
28879   * Current Video.js version. Follows [semantic versioning](https://semver.org/).
28880   *
28881   * @type {string}
28882   */
28883
28884  videojs$1.VERSION = version;
28885  /**
28886   * The global options object. These are the settings that take effect
28887   * if no overrides are specified when the player is created.
28888   *
28889   * @type {Object}
28890   */
28891
28892  videojs$1.options = Player.prototype.options_;
28893  /**
28894   * Get an object with the currently created players, keyed by player ID
28895   *
28896   * @return {Object}
28897   *         The created players
28898   */
28899
28900  videojs$1.getPlayers = function () {
28901    return Player.players;
28902  };
28903  /**
28904   * Get a single player based on an ID or DOM element.
28905   *
28906   * This is useful if you want to check if an element or ID has an associated
28907   * Video.js player, but not create one if it doesn't.
28908   *
28909   * @param   {string|Element} id
28910   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
28911   *          or a string matching the `id` of such an element.
28912   *
28913   * @return {Player|undefined}
28914   *          A player instance or `undefined` if there is no player instance
28915   *          matching the argument.
28916   */
28917
28918
28919  videojs$1.getPlayer = function (id) {
28920    var players = Player.players;
28921    var tag;
28922
28923    if (typeof id === 'string') {
28924      var nId = normalizeId(id);
28925      var player = players[nId];
28926
28927      if (player) {
28928        return player;
28929      }
28930
28931      tag = $('#' + nId);
28932    } else {
28933      tag = id;
28934    }
28935
28936    if (isEl(tag)) {
28937      var _tag = tag,
28938          _player = _tag.player,
28939          playerId = _tag.playerId; // Element may have a `player` property referring to an already created
28940      // player instance. If so, return that.
28941
28942      if (_player || players[playerId]) {
28943        return _player || players[playerId];
28944      }
28945    }
28946  };
28947  /**
28948   * Returns an array of all current players.
28949   *
28950   * @return {Array}
28951   *         An array of all players. The array will be in the order that
28952   *         `Object.keys` provides, which could potentially vary between
28953   *         JavaScript engines.
28954   *
28955   */
28956
28957
28958  videojs$1.getAllPlayers = function () {
28959    return (// Disposed players leave a key with a `null` value, so we need to make sure
28960      // we filter those out.
28961      Object.keys(Player.players).map(function (k) {
28962        return Player.players[k];
28963      }).filter(Boolean)
28964    );
28965  };
28966
28967  videojs$1.players = Player.players;
28968  videojs$1.getComponent = Component.getComponent;
28969  /**
28970   * Register a component so it can referred to by name. Used when adding to other
28971   * components, either through addChild `component.addChild('myComponent')` or through
28972   * default children options  `{ children: ['myComponent'] }`.
28973   *
28974   * > NOTE: You could also just initialize the component before adding.
28975   * `component.addChild(new MyComponent());`
28976   *
28977   * @param {string} name
28978   *        The class name of the component
28979   *
28980   * @param {Component} comp
28981   *        The component class
28982   *
28983   * @return {Component}
28984   *         The newly registered component
28985   */
28986
28987  videojs$1.registerComponent = function (name, comp) {
28988    if (Tech.isTech(comp)) {
28989      log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
28990    }
28991
28992    Component.registerComponent.call(Component, name, comp);
28993  };
28994
28995  videojs$1.getTech = Tech.getTech;
28996  videojs$1.registerTech = Tech.registerTech;
28997  videojs$1.use = use;
28998  /**
28999   * An object that can be returned by a middleware to signify
29000   * that the middleware is being terminated.
29001   *
29002   * @type {object}
29003   * @property {object} middleware.TERMINATOR
29004   */
29005
29006  Object.defineProperty(videojs$1, 'middleware', {
29007    value: {},
29008    writeable: false,
29009    enumerable: true
29010  });
29011  Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
29012    value: TERMINATOR,
29013    writeable: false,
29014    enumerable: true
29015  });
29016  /**
29017   * A reference to the {@link module:browser|browser utility module} as an object.
29018   *
29019   * @type {Object}
29020   * @see  {@link module:browser|browser}
29021   */
29022
29023  videojs$1.browser = browser;
29024  /**
29025   * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
29026   * included for backward-compatibility with 4.x.
29027   *
29028   * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
29029   * @type {boolean}
29030   */
29031
29032  videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
29033  videojs$1.extend = extend;
29034  videojs$1.mergeOptions = mergeOptions;
29035  videojs$1.bind = bind;
29036  videojs$1.registerPlugin = Plugin.registerPlugin;
29037  videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
29038  /**
29039   * Deprecated method to register a plugin with Video.js
29040   *
29041   * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
29042   *
29043   * @param {string} name
29044   *        The plugin name
29045   *
29046   * @param {Plugin|Function} plugin
29047   *         The plugin sub-class or function
29048   */
29049
29050  videojs$1.plugin = function (name, plugin) {
29051    log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
29052    return Plugin.registerPlugin(name, plugin);
29053  };
29054
29055  videojs$1.getPlugins = Plugin.getPlugins;
29056  videojs$1.getPlugin = Plugin.getPlugin;
29057  videojs$1.getPluginVersion = Plugin.getPluginVersion;
29058  /**
29059   * Adding languages so that they're available to all players.
29060   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
29061   *
29062   * @param {string} code
29063   *        The language code or dictionary property
29064   *
29065   * @param {Object} data
29066   *        The data values to be translated
29067   *
29068   * @return {Object}
29069   *         The resulting language dictionary object
29070   */
29071
29072  videojs$1.addLanguage = function (code, data) {
29073    var _mergeOptions;
29074
29075    code = ('' + code).toLowerCase();
29076    videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
29077    return videojs$1.options.languages[code];
29078  };
29079  /**
29080   * A reference to the {@link module:log|log utility module} as an object.
29081   *
29082   * @type {Function}
29083   * @see  {@link module:log|log}
29084   */
29085
29086
29087  videojs$1.log = log;
29088  videojs$1.createLogger = createLogger$1;
29089  videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
29090  videojs$1.formatTime = formatTime;
29091  videojs$1.setFormatTime = setFormatTime;
29092  videojs$1.resetFormatTime = resetFormatTime;
29093  videojs$1.parseUrl = parseUrl;
29094  videojs$1.isCrossOrigin = isCrossOrigin;
29095  videojs$1.EventTarget = EventTarget;
29096  videojs$1.on = on;
29097  videojs$1.one = one;
29098  videojs$1.off = off;
29099  videojs$1.trigger = trigger;
29100  /**
29101   * A cross-browser XMLHttpRequest wrapper.
29102   *
29103   * @function
29104   * @param    {Object} options
29105   *           Settings for the request.
29106   *
29107   * @return   {XMLHttpRequest|XDomainRequest}
29108   *           The request object.
29109   *
29110   * @see      https://github.com/Raynos/xhr
29111   */
29112
29113  videojs$1.xhr = xhr;
29114  videojs$1.TextTrack = TextTrack;
29115  videojs$1.AudioTrack = AudioTrack;
29116  videojs$1.VideoTrack = VideoTrack;
29117  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
29118    videojs$1[k] = function () {
29119      log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
29120      return Dom[k].apply(null, arguments);
29121    };
29122  });
29123  videojs$1.computedStyle = computedStyle;
29124  /**
29125   * A reference to the {@link module:dom|DOM utility module} as an object.
29126   *
29127   * @type {Object}
29128   * @see  {@link module:dom|dom}
29129   */
29130
29131  videojs$1.dom = Dom;
29132  /**
29133   * A reference to the {@link module:url|URL utility module} as an object.
29134   *
29135   * @type {Object}
29136   * @see  {@link module:url|url}
29137   */
29138
29139  videojs$1.url = Url;
29140  videojs$1.defineLazyProperty = defineLazyProperty;
29141
29142  var urlToolkit = createCommonjsModule(function (module, exports) {
29143    // see https://tools.ietf.org/html/rfc1808
29144
29145    /* jshint ignore:start */
29146    (function (root) {
29147      /* jshint ignore:end */
29148      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
29149      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
29150      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
29151      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
29152      var URLToolkit = {
29153        // jshint ignore:line
29154        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
29155        // E.g
29156        // With opts.alwaysNormalize = false (default, spec compliant)
29157        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
29158        // With opts.alwaysNormalize = true (not spec compliant)
29159        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
29160        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
29161          opts = opts || {}; // remove any remaining space and CRLF
29162
29163          baseURL = baseURL.trim();
29164          relativeURL = relativeURL.trim();
29165
29166          if (!relativeURL) {
29167            // 2a) If the embedded URL is entirely empty, it inherits the
29168            // entire base URL (i.e., is set equal to the base URL)
29169            // and we are done.
29170            if (!opts.alwaysNormalize) {
29171              return baseURL;
29172            }
29173
29174            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
29175
29176            if (!basePartsForNormalise) {
29177              throw new Error('Error trying to parse base URL.');
29178            }
29179
29180            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
29181            return URLToolkit.buildURLFromParts(basePartsForNormalise);
29182          }
29183
29184          var relativeParts = URLToolkit.parseURL(relativeURL);
29185
29186          if (!relativeParts) {
29187            throw new Error('Error trying to parse relative URL.');
29188          }
29189
29190          if (relativeParts.scheme) {
29191            // 2b) If the embedded URL starts with a scheme name, it is
29192            // interpreted as an absolute URL and we are done.
29193            if (!opts.alwaysNormalize) {
29194              return relativeURL;
29195            }
29196
29197            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
29198            return URLToolkit.buildURLFromParts(relativeParts);
29199          }
29200
29201          var baseParts = URLToolkit.parseURL(baseURL);
29202
29203          if (!baseParts) {
29204            throw new Error('Error trying to parse base URL.');
29205          }
29206
29207          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
29208            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
29209            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
29210            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
29211            baseParts.netLoc = pathParts[1];
29212            baseParts.path = pathParts[2];
29213          }
29214
29215          if (baseParts.netLoc && !baseParts.path) {
29216            baseParts.path = '/';
29217          }
29218
29219          var builtParts = {
29220            // 2c) Otherwise, the embedded URL inherits the scheme of
29221            // the base URL.
29222            scheme: baseParts.scheme,
29223            netLoc: relativeParts.netLoc,
29224            path: null,
29225            params: relativeParts.params,
29226            query: relativeParts.query,
29227            fragment: relativeParts.fragment
29228          };
29229
29230          if (!relativeParts.netLoc) {
29231            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
29232            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
29233            // (if any) of the base URL.
29234            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
29235            // path is not relative and we skip to Step 7.
29236
29237            if (relativeParts.path[0] !== '/') {
29238              if (!relativeParts.path) {
29239                // 5) If the embedded URL path is empty (and not preceded by a
29240                // slash), then the embedded URL inherits the base URL path
29241                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
29242                // step 7; otherwise, it inherits the <params> of the base
29243                // URL (if any) and
29244
29245                if (!relativeParts.params) {
29246                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
29247                  // step 7; otherwise, it inherits the <query> of the base
29248                  // URL (if any) and we skip to step 7.
29249
29250                  if (!relativeParts.query) {
29251                    builtParts.query = baseParts.query;
29252                  }
29253                }
29254              } else {
29255                // 6) The last segment of the base URL's path (anything
29256                // following the rightmost slash "/", or the entire path if no
29257                // slash is present) is removed and the embedded URL's path is
29258                // appended in its place.
29259                var baseURLPath = baseParts.path;
29260                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
29261                builtParts.path = URLToolkit.normalizePath(newPath);
29262              }
29263            }
29264          }
29265
29266          if (builtParts.path === null) {
29267            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
29268          }
29269
29270          return URLToolkit.buildURLFromParts(builtParts);
29271        },
29272        parseURL: function parseURL(url) {
29273          var parts = URL_REGEX.exec(url);
29274
29275          if (!parts) {
29276            return null;
29277          }
29278
29279          return {
29280            scheme: parts[1] || '',
29281            netLoc: parts[2] || '',
29282            path: parts[3] || '',
29283            params: parts[4] || '',
29284            query: parts[5] || '',
29285            fragment: parts[6] || ''
29286          };
29287        },
29288        normalizePath: function normalizePath(path) {
29289          // The following operations are
29290          // then applied, in order, to the new path:
29291          // 6a) All occurrences of "./", where "." is a complete path
29292          // segment, are removed.
29293          // 6b) If the path ends with "." as a complete path segment,
29294          // that "." is removed.
29295          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
29296          // complete path segment not equal to "..", are removed.
29297          // Removal of these path segments is performed iteratively,
29298          // removing the leftmost matching pattern on each iteration,
29299          // until no matching pattern remains.
29300          // 6d) If the path ends with "<segment>/..", where <segment> is a
29301          // complete path segment not equal to "..", that
29302          // "<segment>/.." is removed.
29303
29304          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
29305
29306
29307          return path.split('').reverse().join('');
29308        },
29309        buildURLFromParts: function buildURLFromParts(parts) {
29310          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
29311        }
29312      };
29313      /* jshint ignore:start */
29314
29315      module.exports = URLToolkit;
29316    })();
29317    /* jshint ignore:end */
29318
29319  });
29320
29321  /*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */
29322
29323  function _extends() {
29324    _extends = Object.assign || function (target) {
29325      for (var i = 1; i < arguments.length; i++) {
29326        var source = arguments[i];
29327
29328        for (var key in source) {
29329          if (Object.prototype.hasOwnProperty.call(source, key)) {
29330            target[key] = source[key];
29331          }
29332        }
29333      }
29334
29335      return target;
29336    };
29337
29338    return _extends.apply(this, arguments);
29339  }
29340
29341  function _inheritsLoose$1(subClass, superClass) {
29342    subClass.prototype = Object.create(superClass.prototype);
29343    subClass.prototype.constructor = subClass;
29344    subClass.__proto__ = superClass;
29345  }
29346
29347  function _assertThisInitialized$1(self) {
29348    if (self === void 0) {
29349      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
29350    }
29351
29352    return self;
29353  }
29354  /**
29355   * @file stream.js
29356   */
29357
29358  /**
29359   * A lightweight readable stream implementation that handles event dispatching.
29360   *
29361   * @class Stream
29362   */
29363
29364
29365  var Stream =
29366  /*#__PURE__*/
29367  function () {
29368    function Stream() {
29369      this.listeners = {};
29370    }
29371    /**
29372     * Add a listener for a specified event type.
29373     *
29374     * @param {string} type the event name
29375     * @param {Function} listener the callback to be invoked when an event of
29376     * the specified type occurs
29377     */
29378
29379
29380    var _proto = Stream.prototype;
29381
29382    _proto.on = function on(type, listener) {
29383      if (!this.listeners[type]) {
29384        this.listeners[type] = [];
29385      }
29386
29387      this.listeners[type].push(listener);
29388    }
29389    /**
29390     * Remove a listener for a specified event type.
29391     *
29392     * @param {string} type the event name
29393     * @param {Function} listener  a function previously registered for this
29394     * type of event through `on`
29395     * @return {boolean} if we could turn it off or not
29396     */
29397    ;
29398
29399    _proto.off = function off(type, listener) {
29400      if (!this.listeners[type]) {
29401        return false;
29402      }
29403
29404      var index = this.listeners[type].indexOf(listener);
29405      this.listeners[type].splice(index, 1);
29406      return index > -1;
29407    }
29408    /**
29409     * Trigger an event of the specified type on this stream. Any additional
29410     * arguments to this function are passed as parameters to event listeners.
29411     *
29412     * @param {string} type the event name
29413     */
29414    ;
29415
29416    _proto.trigger = function trigger(type) {
29417      var callbacks = this.listeners[type];
29418      var i;
29419      var length;
29420      var args;
29421
29422      if (!callbacks) {
29423        return;
29424      } // Slicing the arguments on every invocation of this method
29425      // can add a significant amount of overhead. Avoid the
29426      // intermediate object creation for the common case of a
29427      // single callback argument
29428
29429
29430      if (arguments.length === 2) {
29431        length = callbacks.length;
29432
29433        for (i = 0; i < length; ++i) {
29434          callbacks[i].call(this, arguments[1]);
29435        }
29436      } else {
29437        args = Array.prototype.slice.call(arguments, 1);
29438        length = callbacks.length;
29439
29440        for (i = 0; i < length; ++i) {
29441          callbacks[i].apply(this, args);
29442        }
29443      }
29444    }
29445    /**
29446     * Destroys the stream and cleans up.
29447     */
29448    ;
29449
29450    _proto.dispose = function dispose() {
29451      this.listeners = {};
29452    }
29453    /**
29454     * Forwards all `data` events on this stream to the destination stream. The
29455     * destination stream should provide a method `push` to receive the data
29456     * events as they arrive.
29457     *
29458     * @param {Stream} destination the stream that will receive all `data` events
29459     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
29460     */
29461    ;
29462
29463    _proto.pipe = function pipe(destination) {
29464      this.on('data', function (data) {
29465        destination.push(data);
29466      });
29467    };
29468
29469    return Stream;
29470  }();
29471  /**
29472   * A stream that buffers string input and generates a `data` event for each
29473   * line.
29474   *
29475   * @class LineStream
29476   * @extends Stream
29477   */
29478
29479
29480  var LineStream =
29481  /*#__PURE__*/
29482  function (_Stream) {
29483    _inheritsLoose$1(LineStream, _Stream);
29484
29485    function LineStream() {
29486      var _this;
29487
29488      _this = _Stream.call(this) || this;
29489      _this.buffer = '';
29490      return _this;
29491    }
29492    /**
29493     * Add new data to be parsed.
29494     *
29495     * @param {string} data the text to process
29496     */
29497
29498
29499    var _proto = LineStream.prototype;
29500
29501    _proto.push = function push(data) {
29502      var nextNewline;
29503      this.buffer += data;
29504      nextNewline = this.buffer.indexOf('\n');
29505
29506      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
29507        this.trigger('data', this.buffer.substring(0, nextNewline));
29508        this.buffer = this.buffer.substring(nextNewline + 1);
29509      }
29510    };
29511
29512    return LineStream;
29513  }(Stream);
29514  /**
29515   * "forgiving" attribute list psuedo-grammar:
29516   * attributes -> keyvalue (',' keyvalue)*
29517   * keyvalue   -> key '=' value
29518   * key        -> [^=]*
29519   * value      -> '"' [^"]* '"' | [^,]*
29520   */
29521
29522
29523  var attributeSeparator = function attributeSeparator() {
29524    var key = '[^=]*';
29525    var value = '"[^"]*"|[^,]*';
29526    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
29527    return new RegExp('(?:^|,)(' + keyvalue + ')');
29528  };
29529  /**
29530   * Parse attributes from a line given the separator
29531   *
29532   * @param {string} attributes the attribute line to parse
29533   */
29534
29535
29536  var parseAttributes = function parseAttributes(attributes) {
29537    // split the string using attributes as the separator
29538    var attrs = attributes.split(attributeSeparator());
29539    var result = {};
29540    var i = attrs.length;
29541    var attr;
29542
29543    while (i--) {
29544      // filter out unmatched portions of the string
29545      if (attrs[i] === '') {
29546        continue;
29547      } // split the key and value
29548
29549
29550      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
29551
29552      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
29553      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
29554      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
29555      result[attr[0]] = attr[1];
29556    }
29557
29558    return result;
29559  };
29560  /**
29561   * A line-level M3U8 parser event stream. It expects to receive input one
29562   * line at a time and performs a context-free parse of its contents. A stream
29563   * interpretation of a manifest can be useful if the manifest is expected to
29564   * be too large to fit comfortably into memory or the entirety of the input
29565   * is not immediately available. Otherwise, it's probably much easier to work
29566   * with a regular `Parser` object.
29567   *
29568   * Produces `data` events with an object that captures the parser's
29569   * interpretation of the input. That object has a property `tag` that is one
29570   * of `uri`, `comment`, or `tag`. URIs only have a single additional
29571   * property, `line`, which captures the entirety of the input without
29572   * interpretation. Comments similarly have a single additional property
29573   * `text` which is the input without the leading `#`.
29574   *
29575   * Tags always have a property `tagType` which is the lower-cased version of
29576   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
29577   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
29578   * tags are given the tag type `unknown` and a single additional property
29579   * `data` with the remainder of the input.
29580   *
29581   * @class ParseStream
29582   * @extends Stream
29583   */
29584
29585
29586  var ParseStream =
29587  /*#__PURE__*/
29588  function (_Stream) {
29589    _inheritsLoose$1(ParseStream, _Stream);
29590
29591    function ParseStream() {
29592      var _this;
29593
29594      _this = _Stream.call(this) || this;
29595      _this.customParsers = [];
29596      _this.tagMappers = [];
29597      return _this;
29598    }
29599    /**
29600     * Parses an additional line of input.
29601     *
29602     * @param {string} line a single line of an M3U8 file to parse
29603     */
29604
29605
29606    var _proto = ParseStream.prototype;
29607
29608    _proto.push = function push(line) {
29609      var _this2 = this;
29610
29611      var match;
29612      var event; // strip whitespace
29613
29614      line = line.trim();
29615
29616      if (line.length === 0) {
29617        // ignore empty lines
29618        return;
29619      } // URIs
29620
29621
29622      if (line[0] !== '#') {
29623        this.trigger('data', {
29624          type: 'uri',
29625          uri: line
29626        });
29627        return;
29628      } // map tags
29629
29630
29631      var newLines = this.tagMappers.reduce(function (acc, mapper) {
29632        var mappedLine = mapper(line);
vendor: 33,998 bytes, lines 29632-30744
29632 // skip if unchanged
29633
29634        if (mappedLine === line) {
29635          return acc;
29636        }
29637
29638        return acc.concat([mappedLine]);
29639      }, [line]);
29640      newLines.forEach(function (newLine) {
29641        for (var i = 0; i < _this2.customParsers.length; i++) {
29642          if (_this2.customParsers[i].call(_this2, newLine)) {
29643            return;
29644          }
29645        } // Comments
29646
29647
29648        if (newLine.indexOf('#EXT') !== 0) {
29649          _this2.trigger('data', {
29650            type: 'comment',
29651            text: newLine.slice(1)
29652          });
29653
29654          return;
29655        } // strip off any carriage returns here so the regex matching
29656        // doesn't have to account for them.
29657
29658
29659        newLine = newLine.replace('\r', ''); // Tags
29660
29661        match = /^#EXTM3U/.exec(newLine);
29662
29663        if (match) {
29664          _this2.trigger('data', {
29665            type: 'tag',
29666            tagType: 'm3u'
29667          });
29668
29669          return;
29670        }
29671
29672        match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
29673
29674        if (match) {
29675          event = {
29676            type: 'tag',
29677            tagType: 'inf'
29678          };
29679
29680          if (match[1]) {
29681            event.duration = parseFloat(match[1]);
29682          }
29683
29684          if (match[2]) {
29685            event.title = match[2];
29686          }
29687
29688          _this2.trigger('data', event);
29689
29690          return;
29691        }
29692
29693        match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
29694
29695        if (match) {
29696          event = {
29697            type: 'tag',
29698            tagType: 'targetduration'
29699          };
29700
29701          if (match[1]) {
29702            event.duration = parseInt(match[1], 10);
29703          }
29704
29705          _this2.trigger('data', event);
29706
29707          return;
29708        }
29709
29710        match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
29711
29712        if (match) {
29713          event = {
29714            type: 'tag',
29715            tagType: 'totalduration'
29716          };
29717
29718          if (match[1]) {
29719            event.duration = parseInt(match[1], 10);
29720          }
29721
29722          _this2.trigger('data', event);
29723
29724          return;
29725        }
29726
29727        match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
29728
29729        if (match) {
29730          event = {
29731            type: 'tag',
29732            tagType: 'version'
29733          };
29734
29735          if (match[1]) {
29736            event.version = parseInt(match[1], 10);
29737          }
29738
29739          _this2.trigger('data', event);
29740
29741          return;
29742        }
29743
29744        match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
29745
29746        if (match) {
29747          event = {
29748            type: 'tag',
29749            tagType: 'media-sequence'
29750          };
29751
29752          if (match[1]) {
29753            event.number = parseInt(match[1], 10);
29754          }
29755
29756          _this2.trigger('data', event);
29757
29758          return;
29759        }
29760
29761        match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
29762
29763        if (match) {
29764          event = {
29765            type: 'tag',
29766            tagType: 'discontinuity-sequence'
29767          };
29768
29769          if (match[1]) {
29770            event.number = parseInt(match[1], 10);
29771          }
29772
29773          _this2.trigger('data', event);
29774
29775          return;
29776        }
29777
29778        match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
29779
29780        if (match) {
29781          event = {
29782            type: 'tag',
29783            tagType: 'playlist-type'
29784          };
29785
29786          if (match[1]) {
29787            event.playlistType = match[1];
29788          }
29789
29790          _this2.trigger('data', event);
29791
29792          return;
29793        }
29794
29795        match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
29796
29797        if (match) {
29798          event = {
29799            type: 'tag',
29800            tagType: 'byterange'
29801          };
29802
29803          if (match[1]) {
29804            event.length = parseInt(match[1], 10);
29805          }
29806
29807          if (match[2]) {
29808            event.offset = parseInt(match[2], 10);
29809          }
29810
29811          _this2.trigger('data', event);
29812
29813          return;
29814        }
29815
29816        match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
29817
29818        if (match) {
29819          event = {
29820            type: 'tag',
29821            tagType: 'allow-cache'
29822          };
29823
29824          if (match[1]) {
29825            event.allowed = !/NO/.test(match[1]);
29826          }
29827
29828          _this2.trigger('data', event);
29829
29830          return;
29831        }
29832
29833        match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
29834
29835        if (match) {
29836          event = {
29837            type: 'tag',
29838            tagType: 'map'
29839          };
29840
29841          if (match[1]) {
29842            var attributes = parseAttributes(match[1]);
29843
29844            if (attributes.URI) {
29845              event.uri = attributes.URI;
29846            }
29847
29848            if (attributes.BYTERANGE) {
29849              var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
29850                  length = _attributes$BYTERANGE[0],
29851                  offset = _attributes$BYTERANGE[1];
29852
29853              event.byterange = {};
29854
29855              if (length) {
29856                event.byterange.length = parseInt(length, 10);
29857              }
29858
29859              if (offset) {
29860                event.byterange.offset = parseInt(offset, 10);
29861              }
29862            }
29863          }
29864
29865          _this2.trigger('data', event);
29866
29867          return;
29868        }
29869
29870        match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
29871
29872        if (match) {
29873          event = {
29874            type: 'tag',
29875            tagType: 'stream-inf'
29876          };
29877
29878          if (match[1]) {
29879            event.attributes = parseAttributes(match[1]);
29880
29881            if (event.attributes.RESOLUTION) {
29882              var split = event.attributes.RESOLUTION.split('x');
29883              var resolution = {};
29884
29885              if (split[0]) {
29886                resolution.width = parseInt(split[0], 10);
29887              }
29888
29889              if (split[1]) {
29890                resolution.height = parseInt(split[1], 10);
29891              }
29892
29893              event.attributes.RESOLUTION = resolution;
29894            }
29895
29896            if (event.attributes.BANDWIDTH) {
29897              event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
29898            }
29899
29900            if (event.attributes['PROGRAM-ID']) {
29901              event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
29902            }
29903          }
29904
29905          _this2.trigger('data', event);
29906
29907          return;
29908        }
29909
29910        match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
29911
29912        if (match) {
29913          event = {
29914            type: 'tag',
29915            tagType: 'media'
29916          };
29917
29918          if (match[1]) {
29919            event.attributes = parseAttributes(match[1]);
29920          }
29921
29922          _this2.trigger('data', event);
29923
29924          return;
29925        }
29926
29927        match = /^#EXT-X-ENDLIST/.exec(newLine);
29928
29929        if (match) {
29930          _this2.trigger('data', {
29931            type: 'tag',
29932            tagType: 'endlist'
29933          });
29934
29935          return;
29936        }
29937
29938        match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
29939
29940        if (match) {
29941          _this2.trigger('data', {
29942            type: 'tag',
29943            tagType: 'discontinuity'
29944          });
29945
29946          return;
29947        }
29948
29949        match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
29950
29951        if (match) {
29952          event = {
29953            type: 'tag',
29954            tagType: 'program-date-time'
29955          };
29956
29957          if (match[1]) {
29958            event.dateTimeString = match[1];
29959            event.dateTimeObject = new Date(match[1]);
29960          }
29961
29962          _this2.trigger('data', event);
29963
29964          return;
29965        }
29966
29967        match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
29968
29969        if (match) {
29970          event = {
29971            type: 'tag',
29972            tagType: 'key'
29973          };
29974
29975          if (match[1]) {
29976            event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
29977
29978            if (event.attributes.IV) {
29979              if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
29980                event.attributes.IV = event.attributes.IV.substring(2);
29981              }
29982
29983              event.attributes.IV = event.attributes.IV.match(/.{8}/g);
29984              event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
29985              event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
29986              event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
29987              event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
29988              event.attributes.IV = new Uint32Array(event.attributes.IV);
29989            }
29990          }
29991
29992          _this2.trigger('data', event);
29993
29994          return;
29995        }
29996
29997        match = /^#EXT-X-START:?(.*)$/.exec(newLine);
29998
29999        if (match) {
30000          event = {
30001            type: 'tag',
30002            tagType: 'start'
30003          };
30004
30005          if (match[1]) {
30006            event.attributes = parseAttributes(match[1]);
30007            event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
30008            event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
30009          }
30010
30011          _this2.trigger('data', event);
30012
30013          return;
30014        }
30015
30016        match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
30017
30018        if (match) {
30019          event = {
30020            type: 'tag',
30021            tagType: 'cue-out-cont'
30022          };
30023
30024          if (match[1]) {
30025            event.data = match[1];
30026          } else {
30027            event.data = '';
30028          }
30029
30030          _this2.trigger('data', event);
30031
30032          return;
30033        }
30034
30035        match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
30036
30037        if (match) {
30038          event = {
30039            type: 'tag',
30040            tagType: 'cue-out'
30041          };
30042
30043          if (match[1]) {
30044            event.data = match[1];
30045          } else {
30046            event.data = '';
30047          }
30048
30049          _this2.trigger('data', event);
30050
30051          return;
30052        }
30053
30054        match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
30055
30056        if (match) {
30057          event = {
30058            type: 'tag',
30059            tagType: 'cue-in'
30060          };
30061
30062          if (match[1]) {
30063            event.data = match[1];
30064          } else {
30065            event.data = '';
30066          }
30067
30068          _this2.trigger('data', event);
30069
30070          return;
30071        } // unknown tag type
30072
30073
30074        _this2.trigger('data', {
30075          type: 'tag',
30076          data: newLine.slice(4)
30077        });
30078      });
30079    }
30080    /**
30081     * Add a parser for custom headers
30082     *
30083     * @param {Object}   options              a map of options for the added parser
30084     * @param {RegExp}   options.expression   a regular expression to match the custom header
30085     * @param {string}   options.customType   the custom type to register to the output
30086     * @param {Function} [options.dataParser] function to parse the line into an object
30087     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
30088     */
30089    ;
30090
30091    _proto.addParser = function addParser(_ref) {
30092      var _this3 = this;
30093
30094      var expression = _ref.expression,
30095          customType = _ref.customType,
30096          dataParser = _ref.dataParser,
30097          segment = _ref.segment;
30098
30099      if (typeof dataParser !== 'function') {
30100        dataParser = function dataParser(line) {
30101          return line;
30102        };
30103      }
30104
30105      this.customParsers.push(function (line) {
30106        var match = expression.exec(line);
30107
30108        if (match) {
30109          _this3.trigger('data', {
30110            type: 'custom',
30111            data: dataParser(line),
30112            customType: customType,
30113            segment: segment
30114          });
30115
30116          return true;
30117        }
30118      });
30119    }
30120    /**
30121     * Add a custom header mapper
30122     *
30123     * @param {Object}   options
30124     * @param {RegExp}   options.expression   a regular expression to match the custom header
30125     * @param {Function} options.map          function to translate tag into a different tag
30126     */
30127    ;
30128
30129    _proto.addTagMapper = function addTagMapper(_ref2) {
30130      var expression = _ref2.expression,
30131          map = _ref2.map;
30132
30133      var mapFn = function mapFn(line) {
30134        if (expression.test(line)) {
30135          return map(line);
30136        }
30137
30138        return line;
30139      };
30140
30141      this.tagMappers.push(mapFn);
30142    };
30143
30144    return ParseStream;
30145  }(Stream);
30146
30147  function decodeB64ToUint8Array(b64Text) {
30148    var decodedString = window$1.atob(b64Text || '');
30149    var array = new Uint8Array(decodedString.length);
30150
30151    for (var i = 0; i < decodedString.length; i++) {
30152      array[i] = decodedString.charCodeAt(i);
30153    }
30154
30155    return array;
30156  }
30157  /**
30158   * A parser for M3U8 files. The current interpretation of the input is
30159   * exposed as a property `manifest` on parser objects. It's just two lines to
30160   * create and parse a manifest once you have the contents available as a string:
30161   *
30162   * ```js
30163   * var parser = new m3u8.Parser();
30164   * parser.push(xhr.responseText);
30165   * ```
30166   *
30167   * New input can later be applied to update the manifest object by calling
30168   * `push` again.
30169   *
30170   * The parser attempts to create a usable manifest object even if the
30171   * underlying input is somewhat nonsensical. It emits `info` and `warning`
30172   * events during the parse if it encounters input that seems invalid or
30173   * requires some property of the manifest object to be defaulted.
30174   *
30175   * @class Parser
30176   * @extends Stream
30177   */
30178
30179
30180  var Parser =
30181  /*#__PURE__*/
30182  function (_Stream) {
30183    _inheritsLoose$1(Parser, _Stream);
30184
30185    function Parser() {
30186      var _this;
30187
30188      _this = _Stream.call(this) || this;
30189      _this.lineStream = new LineStream();
30190      _this.parseStream = new ParseStream();
30191
30192      _this.lineStream.pipe(_this.parseStream);
30193      /* eslint-disable consistent-this */
30194
30195
30196      var self = _assertThisInitialized$1(_this);
30197      /* eslint-enable consistent-this */
30198
30199
30200      var uris = [];
30201      var currentUri = {}; // if specified, the active EXT-X-MAP definition
30202
30203      var currentMap; // if specified, the active decryption key
30204
30205      var _key;
30206
30207      var noop = function noop() {};
30208
30209      var defaultMediaGroups = {
30210        'AUDIO': {},
30211        'VIDEO': {},
30212        'CLOSED-CAPTIONS': {},
30213        'SUBTITLES': {}
30214      }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
30215      // used in MPDs with Widevine encrypted streams.
30216
30217      var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
30218
30219      var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
30220
30221      _this.manifest = {
30222        allowCache: true,
30223        discontinuityStarts: [],
30224        segments: []
30225      }; // update the manifest with the m3u8 entry from the parse stream
30226
30227      _this.parseStream.on('data', function (entry) {
30228        var mediaGroup;
30229        var rendition;
30230        ({
30231          tag: function tag() {
30232            // switch based on the tag type
30233            (({
30234              'allow-cache': function allowCache() {
30235                this.manifest.allowCache = entry.allowed;
30236
30237                if (!('allowed' in entry)) {
30238                  this.trigger('info', {
30239                    message: 'defaulting allowCache to YES'
30240                  });
30241                  this.manifest.allowCache = true;
30242                }
30243              },
30244              byterange: function byterange() {
30245                var byterange = {};
30246
30247                if ('length' in entry) {
30248                  currentUri.byterange = byterange;
30249                  byterange.length = entry.length;
30250
30251                  if (!('offset' in entry)) {
30252                    this.trigger('info', {
30253                      message: 'defaulting offset to zero'
30254                    });
30255                    entry.offset = 0;
30256                  }
30257                }
30258
30259                if ('offset' in entry) {
30260                  currentUri.byterange = byterange;
30261                  byterange.offset = entry.offset;
30262                }
30263              },
30264              endlist: function endlist() {
30265                this.manifest.endList = true;
30266              },
30267              inf: function inf() {
30268                if (!('mediaSequence' in this.manifest)) {
30269                  this.manifest.mediaSequence = 0;
30270                  this.trigger('info', {
30271                    message: 'defaulting media sequence to zero'
30272                  });
30273                }
30274
30275                if (!('discontinuitySequence' in this.manifest)) {
30276                  this.manifest.discontinuitySequence = 0;
30277                  this.trigger('info', {
30278                    message: 'defaulting discontinuity sequence to zero'
30279                  });
30280                }
30281
30282                if (entry.duration > 0) {
30283                  currentUri.duration = entry.duration;
30284                }
30285
30286                if (entry.duration === 0) {
30287                  currentUri.duration = 0.01;
30288                  this.trigger('info', {
30289                    message: 'updating zero segment duration to a small value'
30290                  });
30291                }
30292
30293                this.manifest.segments = uris;
30294              },
30295              key: function key() {
30296                if (!entry.attributes) {
30297                  this.trigger('warn', {
30298                    message: 'ignoring key declaration without attribute list'
30299                  });
30300                  return;
30301                } // clear the active encryption key
30302
30303
30304                if (entry.attributes.METHOD === 'NONE') {
30305                  _key = null;
30306                  return;
30307                }
30308
30309                if (!entry.attributes.URI) {
30310                  this.trigger('warn', {
30311                    message: 'ignoring key declaration without URI'
30312                  });
30313                  return;
30314                } // check if the content is encrypted for Widevine
30315                // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
30316
30317
30318                if (entry.attributes.KEYFORMAT === widevineUuid) {
30319                  var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
30320
30321                  if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
30322                    this.trigger('warn', {
30323                      message: 'invalid key method provided for Widevine'
30324                    });
30325                    return;
30326                  }
30327
30328                  if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
30329                    this.trigger('warn', {
30330                      message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
30331                    });
30332                  }
30333
30334                  if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
30335                    this.trigger('warn', {
30336                      message: 'invalid key URI provided for Widevine'
30337                    });
30338                    return;
30339                  }
30340
30341                  if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
30342                    this.trigger('warn', {
30343                      message: 'invalid key ID provided for Widevine'
30344                    });
30345                    return;
30346                  } // if Widevine key attributes are valid, store them as `contentProtection`
30347                  // on the manifest to emulate Widevine tag structure in a DASH mpd
30348
30349
30350                  this.manifest.contentProtection = {
30351                    'com.widevine.alpha': {
30352                      attributes: {
30353                        schemeIdUri: entry.attributes.KEYFORMAT,
30354                        // remove '0x' from the key id string
30355                        keyId: entry.attributes.KEYID.substring(2)
30356                      },
30357                      // decode the base64-encoded PSSH box
30358                      pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
30359                    }
30360                  };
30361                  return;
30362                }
30363
30364                if (!entry.attributes.METHOD) {
30365                  this.trigger('warn', {
30366                    message: 'defaulting key method to AES-128'
30367                  });
30368                } // setup an encryption key for upcoming segments
30369
30370
30371                _key = {
30372                  method: entry.attributes.METHOD || 'AES-128',
30373                  uri: entry.attributes.URI
30374                };
30375
30376                if (typeof entry.attributes.IV !== 'undefined') {
30377                  _key.iv = entry.attributes.IV;
30378                }
30379              },
30380              'media-sequence': function mediaSequence() {
30381                if (!isFinite(entry.number)) {
30382                  this.trigger('warn', {
30383                    message: 'ignoring invalid media sequence: ' + entry.number
30384                  });
30385                  return;
30386                }
30387
30388                this.manifest.mediaSequence = entry.number;
30389              },
30390              'discontinuity-sequence': function discontinuitySequence() {
30391                if (!isFinite(entry.number)) {
30392                  this.trigger('warn', {
30393                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
30394                  });
30395                  return;
30396                }
30397
30398                this.manifest.discontinuitySequence = entry.number;
30399                currentTimeline = entry.number;
30400              },
30401              'playlist-type': function playlistType() {
30402                if (!/VOD|EVENT/.test(entry.playlistType)) {
30403                  this.trigger('warn', {
30404                    message: 'ignoring unknown playlist type: ' + entry.playlist
30405                  });
30406                  return;
30407                }
30408
30409                this.manifest.playlistType = entry.playlistType;
30410              },
30411              map: function map() {
30412                currentMap = {};
30413
30414                if (entry.uri) {
30415                  currentMap.uri = entry.uri;
30416                }
30417
30418                if (entry.byterange) {
30419                  currentMap.byterange = entry.byterange;
30420                }
30421              },
30422              'stream-inf': function streamInf() {
30423                this.manifest.playlists = uris;
30424                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30425
30426                if (!entry.attributes) {
30427                  this.trigger('warn', {
30428                    message: 'ignoring empty stream-inf attributes'
30429                  });
30430                  return;
30431                }
30432
30433                if (!currentUri.attributes) {
30434                  currentUri.attributes = {};
30435                }
30436
30437                _extends(currentUri.attributes, entry.attributes);
30438              },
30439              media: function media() {
30440                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30441
30442                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
30443                  this.trigger('warn', {
30444                    message: 'ignoring incomplete or missing media group'
30445                  });
30446                  return;
30447                } // find the media group, creating defaults as necessary
30448
30449
30450                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
30451                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
30452                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
30453
30454                rendition = {
30455                  "default": /yes/i.test(entry.attributes.DEFAULT)
30456                };
30457
30458                if (rendition["default"]) {
30459                  rendition.autoselect = true;
30460                } else {
30461                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
30462                }
30463
30464                if (entry.attributes.LANGUAGE) {
30465                  rendition.language = entry.attributes.LANGUAGE;
30466                }
30467
30468                if (entry.attributes.URI) {
30469                  rendition.uri = entry.attributes.URI;
30470                }
30471
30472                if (entry.attributes['INSTREAM-ID']) {
30473                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
30474                }
30475
30476                if (entry.attributes.CHARACTERISTICS) {
30477                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
30478                }
30479
30480                if (entry.attributes.FORCED) {
30481                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
30482                } // insert the new rendition
30483
30484
30485                mediaGroup[entry.attributes.NAME] = rendition;
30486              },
30487              discontinuity: function discontinuity() {
30488                currentTimeline += 1;
30489                currentUri.discontinuity = true;
30490                this.manifest.discontinuityStarts.push(uris.length);
30491              },
30492              'program-date-time': function programDateTime() {
30493                if (typeof this.manifest.dateTimeString === 'undefined') {
30494                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
30495                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
30496                  // to the manifest object
30497                  // TODO: Consider removing this in future major version
30498                  this.manifest.dateTimeString = entry.dateTimeString;
30499                  this.manifest.dateTimeObject = entry.dateTimeObject;
30500                }
30501
30502                currentUri.dateTimeString = entry.dateTimeString;
30503                currentUri.dateTimeObject = entry.dateTimeObject;
30504              },
30505              targetduration: function targetduration() {
30506                if (!isFinite(entry.duration) || entry.duration < 0) {
30507                  this.trigger('warn', {
30508                    message: 'ignoring invalid target duration: ' + entry.duration
30509                  });
30510                  return;
30511                }
30512
30513                this.manifest.targetDuration = entry.duration;
30514              },
30515              totalduration: function totalduration() {
30516                if (!isFinite(entry.duration) || entry.duration < 0) {
30517                  this.trigger('warn', {
30518                    message: 'ignoring invalid total duration: ' + entry.duration
30519                  });
30520                  return;
30521                }
30522
30523                this.manifest.totalDuration = entry.duration;
30524              },
30525              start: function start() {
30526                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
30527                  this.trigger('warn', {
30528                    message: 'ignoring start declaration without appropriate attribute list'
30529                  });
30530                  return;
30531                }
30532
30533                this.manifest.start = {
30534                  timeOffset: entry.attributes['TIME-OFFSET'],
30535                  precise: entry.attributes.PRECISE
30536                };
30537              },
30538              'cue-out': function cueOut() {
30539                currentUri.cueOut = entry.data;
30540              },
30541              'cue-out-cont': function cueOutCont() {
30542                currentUri.cueOutCont = entry.data;
30543              },
30544              'cue-in': function cueIn() {
30545                currentUri.cueIn = entry.data;
30546              }
30547            })[entry.tagType] || noop).call(self);
30548          },
30549          uri: function uri() {
30550            currentUri.uri = entry.uri;
30551            uris.push(currentUri); // if no explicit duration was declared, use the target duration
30552
30553            if (this.manifest.targetDuration && !('duration' in currentUri)) {
30554              this.trigger('warn', {
30555                message: 'defaulting segment duration to the target duration'
30556              });
30557              currentUri.duration = this.manifest.targetDuration;
30558            } // annotate with encryption information, if necessary
30559
30560
30561            if (_key) {
30562              currentUri.key = _key;
30563            }
30564
30565            currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
30566
30567            if (currentMap) {
30568              currentUri.map = currentMap;
30569            } // prepare for the next URI
30570
30571
30572            currentUri = {};
30573          },
30574          comment: function comment() {// comments are not important for playback
30575          },
30576          custom: function custom() {
30577            // if this is segment-level data attach the output to the segment
30578            if (entry.segment) {
30579              currentUri.custom = currentUri.custom || {};
30580              currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
30581            } else {
30582              this.manifest.custom = this.manifest.custom || {};
30583              this.manifest.custom[entry.customType] = entry.data;
30584            }
30585          }
30586        })[entry.type].call(self);
30587      });
30588
30589      return _this;
30590    }
30591    /**
30592     * Parse the input string and update the manifest object.
30593     *
30594     * @param {string} chunk a potentially incomplete portion of the manifest
30595     */
30596
30597
30598    var _proto = Parser.prototype;
30599
30600    _proto.push = function push(chunk) {
30601      this.lineStream.push(chunk);
30602    }
30603    /**
30604     * Flush any remaining input. This can be handy if the last line of an M3U8
30605     * manifest did not contain a trailing newline but the file has been
30606     * completely received.
30607     */
30608    ;
30609
30610    _proto.end = function end() {
30611      // flush any buffered input
30612      this.lineStream.push('\n');
30613    }
30614    /**
30615     * Add an additional parser for non-standard tags
30616     *
30617     * @param {Object}   options              a map of options for the added parser
30618     * @param {RegExp}   options.expression   a regular expression to match the custom header
30619     * @param {string}   options.type         the type to register to the output
30620     * @param {Function} [options.dataParser] function to parse the line into an object
30621     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
30622     */
30623    ;
30624
30625    _proto.addParser = function addParser(options) {
30626      this.parseStream.addParser(options);
30627    }
30628    /**
30629     * Add a custom header mapper
30630     *
30631     * @param {Object}   options
30632     * @param {RegExp}   options.expression   a regular expression to match the custom header
30633     * @param {Function} options.map          function to translate tag into a different tag
30634     */
30635    ;
30636
30637    _proto.addTagMapper = function addTagMapper(options) {
30638      this.parseStream.addTagMapper(options);
30639    };
30640
30641    return Parser;
30642  }(Stream);
30643
30644  /*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */
30645
30646  var isObject$1 = function isObject(obj) {
30647    return !!obj && typeof obj === 'object';
30648  };
30649
30650  var merge = function merge() {
30651    for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
30652      objects[_key] = arguments[_key];
30653    }
30654
30655    return objects.reduce(function (result, source) {
30656      Object.keys(source).forEach(function (key) {
30657        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
30658          result[key] = result[key].concat(source[key]);
30659        } else if (isObject$1(result[key]) && isObject$1(source[key])) {
30660          result[key] = merge(result[key], source[key]);
30661        } else {
30662          result[key] = source[key];
30663        }
30664      });
30665      return result;
30666    }, {});
30667  };
30668
30669  var values = function values(o) {
30670    return Object.keys(o).map(function (k) {
30671      return o[k];
30672    });
30673  };
30674
30675  var range = function range(start, end) {
30676    var result = [];
30677
30678    for (var i = start; i < end; i++) {
30679      result.push(i);
30680    }
30681
30682    return result;
30683  };
30684
30685  var flatten = function flatten(lists) {
30686    return lists.reduce(function (x, y) {
30687      return x.concat(y);
30688    }, []);
30689  };
30690
30691  var from = function from(list) {
30692    if (!list.length) {
30693      return [];
30694    }
30695
30696    var result = [];
30697
30698    for (var i = 0; i < list.length; i++) {
30699      result.push(list[i]);
30700    }
30701
30702    return result;
30703  };
30704
30705  var findIndexes = function findIndexes(l, key) {
30706    return l.reduce(function (a, e, i) {
30707      if (e[key]) {
30708        a.push(i);
30709      }
30710
30711      return a;
30712    }, []);
30713  };
30714
30715  var errors = {
30716    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
30717    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
30718    DASH_INVALID_XML: 'DASH_INVALID_XML',
30719    NO_BASE_URL: 'NO_BASE_URL',
30720    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
30721    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
30722    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
30723  };
30724
30725  function createCommonjsModule$1(fn, module) {
30726    return module = {
30727      exports: {}
30728    }, fn(module, module.exports), module.exports;
30729  }
30730
30731  var urlToolkit$1 = createCommonjsModule$1(function (module, exports) {
30732    // see https://tools.ietf.org/html/rfc1808
30733
30734    /* jshint ignore:start */
30735    (function (root) {
30736      /* jshint ignore:end */
30737      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
30738      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
30739      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
30740      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
30741      var URLToolkit = {
30742        // jshint ignore:line
30743        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
30744        // E.g
vendor: 4,132 bytes, lines 30745-30838
30745        // With opts.alwaysNormalize = false (default, spec compliant)
30746        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
30747        // With opts.alwaysNormalize = true (not spec compliant)
30748        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
30749        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
30750          opts = opts || {}; // remove any remaining space and CRLF
30751
30752          baseURL = baseURL.trim();
30753          relativeURL = relativeURL.trim();
30754
30755          if (!relativeURL) {
30756            // 2a) If the embedded URL is entirely empty, it inherits the
30757            // entire base URL (i.e., is set equal to the base URL)
30758            // and we are done.
30759            if (!opts.alwaysNormalize) {
30760              return baseURL;
30761            }
30762
30763            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
30764
30765            if (!basePartsForNormalise) {
30766              throw new Error('Error trying to parse base URL.');
30767            }
30768
30769            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
30770            return URLToolkit.buildURLFromParts(basePartsForNormalise);
30771          }
30772
30773          var relativeParts = URLToolkit.parseURL(relativeURL);
30774
30775          if (!relativeParts) {
30776            throw new Error('Error trying to parse relative URL.');
30777          }
30778
30779          if (relativeParts.scheme) {
30780            // 2b) If the embedded URL starts with a scheme name, it is
30781            // interpreted as an absolute URL and we are done.
30782            if (!opts.alwaysNormalize) {
30783              return relativeURL;
30784            }
30785
30786            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
30787            return URLToolkit.buildURLFromParts(relativeParts);
30788          }
30789
30790          var baseParts = URLToolkit.parseURL(baseURL);
30791
30792          if (!baseParts) {
30793            throw new Error('Error trying to parse base URL.');
30794          }
30795
30796          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
30797            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
30798            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
30799            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
30800            baseParts.netLoc = pathParts[1];
30801            baseParts.path = pathParts[2];
30802          }
30803
30804          if (baseParts.netLoc && !baseParts.path) {
30805            baseParts.path = '/';
30806          }
30807
30808          var builtParts = {
30809            // 2c) Otherwise, the embedded URL inherits the scheme of
30810            // the base URL.
30811            scheme: baseParts.scheme,
30812            netLoc: relativeParts.netLoc,
30813            path: null,
30814            params: relativeParts.params,
30815            query: relativeParts.query,
30816            fragment: relativeParts.fragment
30817          };
30818
30819          if (!relativeParts.netLoc) {
30820            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
30821            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
30822            // (if any) of the base URL.
30823            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
30824            // path is not relative and we skip to Step 7.
30825
30826            if (relativeParts.path[0] !== '/') {
30827              if (!relativeParts.path) {
30828                // 5) If the embedded URL path is empty (and not preceded by a
30829                // slash), then the embedded URL inherits the base URL path
30830                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
30831                // step 7; otherwise, it inherits the <params> of the base
30832                // URL (if any) and
30833
30834                if (!relativeParts.params) {
30835                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
30836                  // step 7; otherwise, it inherits the <query> of the base
30837                  // URL (if any) and we skip to step 7.
30838
vendor: 4,111 bytes, lines 30839-30941
30839                  if (!relativeParts.query) {
30840                    builtParts.query = baseParts.query;
30841                  }
30842                }
30843              } else {
30844                // 6) The last segment of the base URL's path (anything
30845                // following the rightmost slash "/", or the entire path if no
30846                // slash is present) is removed and the embedded URL's path is
30847                // appended in its place.
30848                var baseURLPath = baseParts.path;
30849                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
30850                builtParts.path = URLToolkit.normalizePath(newPath);
30851              }
30852            }
30853          }
30854
30855          if (builtParts.path === null) {
30856            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
30857          }
30858
30859          return URLToolkit.buildURLFromParts(builtParts);
30860        },
30861        parseURL: function parseURL(url) {
30862          var parts = URL_REGEX.exec(url);
30863
30864          if (!parts) {
30865            return null;
30866          }
30867
30868          return {
30869            scheme: parts[1] || '',
30870            netLoc: parts[2] || '',
30871            path: parts[3] || '',
30872            params: parts[4] || '',
30873            query: parts[5] || '',
30874            fragment: parts[6] || ''
30875          };
30876        },
30877        normalizePath: function normalizePath(path) {
30878          // The following operations are
30879          // then applied, in order, to the new path:
30880          // 6a) All occurrences of "./", where "." is a complete path
30881          // segment, are removed.
30882          // 6b) If the path ends with "." as a complete path segment,
30883          // that "." is removed.
30884          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
30885          // complete path segment not equal to "..", are removed.
30886          // Removal of these path segments is performed iteratively,
30887          // removing the leftmost matching pattern on each iteration,
30888          // until no matching pattern remains.
30889          // 6d) If the path ends with "<segment>/..", where <segment> is a
30890          // complete path segment not equal to "..", that
30891          // "<segment>/.." is removed.
30892
30893          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
30894
30895
30896          return path.split('').reverse().join('');
30897        },
30898        buildURLFromParts: function buildURLFromParts(parts) {
30899          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
30900        }
30901      };
30902      /* jshint ignore:start */
30903
30904      module.exports = URLToolkit;
30905    })();
30906    /* jshint ignore:end */
30907
30908  });
30909
30910  var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
30911    // return early if we don't need to resolve
30912    if (/^[a-z]+:/i.test(relativeUrl)) {
30913      return relativeUrl;
30914    } // if the base URL is relative then combine with the current location
30915
30916
30917    if (!/\/\//i.test(baseUrl)) {
30918      baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl);
30919    }
30920
30921    return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl);
30922  };
30923  /**
30924   * @typedef {Object} SingleUri
30925   * @property {string} uri - relative location of segment
30926   * @property {string} resolvedUri - resolved location of segment
30927   * @property {Object} byterange - Object containing information on how to make byte range
30928   *   requests following byte-range-spec per RFC2616.
30929   * @property {String} byterange.length - length of range request
30930   * @property {String} byterange.offset - byte offset of range request
30931   *
30932   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
30933   */
30934
30935  /**
30936   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
30937   * that conforms to how m3u8-parser is structured
30938   *
30939   * @see https://github.com/videojs/m3u8-parser
30940   *
30941   * @param {string} baseUrl - baseUrl provided by <BaseUrl>
30941 nodes
30942   * @param {string} source - source url for segment
30943   * @param {string} range - optional range used for range calls,
30944   *   follows  RFC 2616, Clause 14.35.1
30945   * @return {SingleUri} full segment information transformed into a format similar
30946   *   to m3u8-parser
30947   */
30948
30949
30950  var urlTypeToSegment = function urlTypeToSegment(_ref) {
30951    var _ref$baseUrl = _ref.baseUrl,
30952        baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
30953        _ref$source = _ref.source,
30954        source = _ref$source === void 0 ? '' : _ref$source,
30955        _ref$range = _ref.range,
30956        range = _ref$range === void 0 ? '' : _ref$range,
30957        _ref$indexRange = _ref.indexRange,
30958        indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange;
30959    var segment = {
30960      uri: source,
30961      resolvedUri: resolveUrl(baseUrl || '', source)
30962    };
30963
30964    if (range || indexRange) {
30965      var rangeStr = range ? range : indexRange;
30966      var ranges = rangeStr.split('-');
30967      var startRange = parseInt(ranges[0], 10);
30968      var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
30969      // RFC 2616, Clause 14.35.1
30970
30971      segment.byterange = {
30972        length: endRange - startRange + 1,
30973        offset: startRange
30974      };
30975    }
30976
30977    return segment;
30978  };
30979
30980  var byteRangeToString = function byteRangeToString(byterange) {
30981    // `endRange` is one less than `offset + length` because the HTTP range
30982    // header uses inclusive ranges
30983    var endRange = byterange.offset + byterange.length - 1;
30984    return byterange.offset + "-" + endRange;
30985  };
30986  /**
30987   * Functions for calculating the range of available segments in static and dynamic
30988   * manifests.
30989   */
30990
30991
30992  var segmentRange = {
30993    /**
30994     * Returns the entire range of available segments for a static MPD
30995     *
30996     * @param {Object} attributes
30997     *        Inheritied MPD attributes
30998     * @return {{ start: number, end: number }}
30999     *         The start and end numbers for available segments
31000     */
31001    "static": function _static(attributes) {
31002      var duration = attributes.duration,
31003          _attributes$timescale = attributes.timescale,
31004          timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
31005          sourceDuration = attributes.sourceDuration;
31006      return {
31007        start: 0,
31008        end: Math.ceil(sourceDuration / (duration / timescale))
31009      };
31010    },
31011
31012    /**
31013     * Returns the current live window range of available segments for a dynamic MPD
31014     *
31015     * @param {Object} attributes
31016     *        Inheritied MPD attributes
31017     * @return {{ start: number, end: number }}
31018     *         The start and end numbers for available segments
31019     */
31020    dynamic: function dynamic(attributes) {
31021      var NOW = attributes.NOW,
31022          clientOffset = attributes.clientOffset,
31023          availabilityStartTime = attributes.availabilityStartTime,
31024          _attributes$timescale2 = attributes.timescale,
31025          timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
31026          duration = attributes.duration,
31027          _attributes$start = attributes.start,
31028          start = _attributes$start === void 0 ? 0 : _attributes$start,
31029          _attributes$minimumUp = attributes.minimumUpdatePeriod,
31030          minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
31031          _attributes$timeShift = attributes.timeShiftBufferDepth,
31032          timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
31033      var now = (NOW + clientOffset) / 1000;
31034      var periodStartWC = availabilityStartTime + start;
31035      var periodEndWC = now + minimumUpdatePeriod;
31036      var periodDuration = periodEndWC - periodStartWC;
31037      var segmentCount = Math.ceil(periodDuration * timescale / duration);
31038      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
31039      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
31040      return {
31041        start: Math.max(0, availableStart),
31042        end: Math.min(segmentCount, availableEnd)
31043      };
31044    }
31045  };
31046  /**
31047   * Maps a range of numbers to objects with information needed to build the corresponding
31048   * segment list
31049   *
31050   * @name toSegmentsCallback
31051   * @function
31052   * @param {number} number
31053   *        Number of the segment
31054   * @param {number} index
31055   *        Index of the number in the range list
31056   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
31057   *         Object with segment timing and duration info
31058   */
31059
31060  /**
31061   * Returns a callback for Array.prototype.map for mapping a range of numbers to
31062   * information needed to build the segment list.
31063   *
31064   * @param {Object} attributes
31065   *        Inherited MPD attributes
31066   * @return {toSegmentsCallback}
31067   *         Callback map function
31068   */
31069
31070  var toSegments = function toSegments(attributes) {
31071    return function (number, index) {
31072      var duration = attributes.duration,
31073          _attributes$timescale3 = attributes.timescale,
31074          timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
31075          periodIndex = attributes.periodIndex,
31076          _attributes$startNumb = attributes.startNumber,
31077          startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
31078      return {
31079        number: startNumber + number,
31080        duration: duration / timescale,
31081        timeline: periodIndex,
31082        time: index * duration
31083      };
31084    };
31085  };
31086  /**
31087   * Returns a list of objects containing segment timing and duration info used for
31088   * building the list of segments. This uses the @duration attribute specified
31089   * in the MPD manifest to derive the range of segments.
31090   *
31091   * @param {Object} attributes
31092   *        Inherited MPD attributes
31093   * @return {{number: number, duration: number, time: number, timeline: number}[]}
31094   *         List of Objects with segment timing and duration info
31095   */
31096
31097
31098  var parseByDuration = function parseByDuration(attributes) {
31099    var _attributes$type = attributes.type,
31100        type = _attributes$type === void 0 ? 'static' : _attributes$type,
31101        duration = attributes.duration,
31102        _attributes$timescale4 = attributes.timescale,
31103        timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
31104        sourceDuration = attributes.sourceDuration;
31105
31106    var _segmentRange$type = segmentRange[type](attributes),
31107        start = _segmentRange$type.start,
31108        end = _segmentRange$type.end;
31109
31110    var segments = range(start, end).map(toSegments(attributes));
31111
31112    if (type === 'static') {
31113      var index = segments.length - 1; // final segment may be less than full segment duration
31114
31115      segments[index].duration = sourceDuration - duration / timescale * index;
31116    }
31117
31118    return segments;
31119  };
31120  /**
31121   * Translates SegmentBase into a set of segments.
31122   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
31123   * node should be translated into segment.
31124   *
31125   * @param {Object} attributes
31126   *   Object containing all inherited attributes from parent elements with attribute
31127   *   names as keys
31128   * @return {Object.<Array>} list of segments
31129   */
31130
31131
31132  var segmentsFromBase = function segmentsFromBase(attributes) {
31133    var baseUrl = attributes.baseUrl,
31134        _attributes$initializ = attributes.initialization,
31135        initialization = _attributes$initializ === void 0 ? {}
31135 : _attributes$initializ,
31136        sourceDuration = attributes.sourceDuration,
31137        _attributes$timescale = attributes.timescale,
31138        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
31139        _attributes$indexRang = attributes.indexRange,
31140        indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
31141        duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
31142
31143    if (!baseUrl) {
31144      throw new Error(errors.NO_BASE_URL);
31145    }
31146
31147    var initSegment = urlTypeToSegment({
31148      baseUrl: baseUrl,
31149      source: initialization.sourceURL,
31150      range: initialization.range
31151    });
31152    var segment = urlTypeToSegment({
31153      baseUrl: baseUrl,
31154      source: baseUrl,
31155      indexRange: indexRange
31156    });
31157    segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
31158    // (since SegmentBase is only for one total segment)
31159
31160    if (duration) {
31161      var segmentTimeInfo = parseByDuration(attributes);
31162
31163      if (segmentTimeInfo.length) {
31164        segment.duration = segmentTimeInfo[0].duration;
31165        segment.timeline = segmentTimeInfo[0].timeline;
31166      }
31167    } else if (sourceDuration) {
31168      segment.duration = sourceDuration / timescale;
31169      segment.timeline = 0;
31170    } // This is used for mediaSequence
31171
31172
31173    segment.number = 0;
31174    return [segment];
31175  };
31176  /**
31177   * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
31178   * according to the sidx information given.
31179   *
31180   * playlist.sidx has metadadata about the sidx where-as the sidx param
31181   * is the parsed sidx box itself.
31182   *
31183   * @param {Object} playlist the playlist to update the sidx information for
31184   * @param {Object} sidx the parsed sidx box
31185   * @return {Object} the playlist object with the updated sidx information
31186   */
31187
31188
31189  var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) {
31190    // Retain init segment information
31191    var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing
31192
31193    var sourceDuration = playlist.sidx.duration; // Retain source timeline
31194
31195    var timeline = playlist.timeline || 0;
31196    var sidxByteRange = playlist.sidx.byterange;
31197    var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
31198
31199    var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
31200
31201    var mediaReferences = sidx.references.filter(function (r) {
31202      return r.referenceType !== 1;
31203    });
31204    var segments = []; // firstOffset is the offset from the end of the sidx box
31205
31206    var startIndex = sidxEnd + sidx.firstOffset;
31207
31208    for (var i = 0; i < mediaReferences.length; i++) {
31209      var reference = sidx.references[i]; // size of the referenced (sub)segment
31210
31211      var size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
31212      // this will be converted to seconds when generating segments
31213
31214      var duration = reference.subsegmentDuration; // should be an inclusive range
31215
31216      var endIndex = startIndex + size - 1;
31217      var indexRange = startIndex + "-" + endIndex;
31218      var attributes = {
31219        baseUrl: baseUrl,
31220        timescale: timescale,
31221        timeline: timeline,
31222        // this is used in parseByDuration
31223        periodIndex: timeline,
31224        duration: duration,
31225        sourceDuration: sourceDuration,
31226        indexRange: indexRange
31227      };
31228      var segment = segmentsFromBase(attributes)[0];
31229
31230      if (initSegment) {
31231        segment.map = initSegment;
31232      }
31233
31234      segments.push(segment);
31235      startIndex += size;
31236    }
31237
31238    playlist.segments = segments;
31239    return playlist;
31240  };
31241
31242  var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
31243    var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
31244      // assuming playlist IDs are the same across periods
31245      // TODO: handle multiperiod where representation sets are not the same
31246      // across periods
31247      var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
31248
31249      if (acc[name]) {
31250        var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
31251
31252
31253        if (playlist.segments[0]) {
31254          playlist.segments[0].discontinuity = true;
31255        }
31256
31257        (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
31258        // has the same contentProtection
31259
31260
31261        if (playlist.attributes.contentProtection) {
31262          acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
31263        }
31264      } else {
31265        // first Period
31266        acc[name] = playlist;
31267      }
31268
31269      return acc;
31270    }, {}));
31271    return mergedPlaylists.map(function (playlist) {
31272      playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
31273      return playlist;
31274    });
vendor: 20,528 bytes, lines 31275-31868
31275  };
31276
31277  var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) {
31278    if (sidxMapping === void 0) {
31279      sidxMapping = {};
31280    }
31281
31282    if (!Object.keys(sidxMapping).length) {
31283      return playlists;
31284    }
31285
31286    for (var i in playlists) {
31287      var playlist = playlists[i];
31288
31289      if (!playlist.sidx) {
31290        continue;
31291      }
31292
31293      var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange);
31294      var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
31295
31296      if (playlist.sidx && sidxMatch) {
31297        addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
31298      }
31299    }
31300
31301    return playlists;
31302  };
31303
31304  var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
31305    var _attributes;
31306
31307    var attributes = _ref.attributes,
31308        segments = _ref.segments,
31309        sidx = _ref.sidx;
31310    var playlist = {
31311      attributes: (_attributes = {
31312        NAME: attributes.id,
31313        BANDWIDTH: attributes.bandwidth,
31314        CODECS: attributes.codecs
31315      }, _attributes['PROGRAM-ID'] = 1, _attributes),
31316      uri: '',
31317      endList: (attributes.type || 'static') === 'static',
31318      timeline: attributes.periodIndex,
31319      resolvedUri: '',
31320      targetDuration: attributes.duration,
31321      segments: segments,
31322      mediaSequence: segments.length ? segments[0].number : 1
31323    };
31324
31325    if (attributes.contentProtection) {
31326      playlist.contentProtection = attributes.contentProtection;
31327    }
31328
31329    if (sidx) {
31330      playlist.sidx = sidx;
31331    }
31332
31333    return playlist;
31334  };
31335
31336  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
31337    var _attributes2;
31338
31339    var attributes = _ref2.attributes,
31340        segments = _ref2.segments;
31341
31342    if (typeof segments === 'undefined') {
31343      // vtt tracks may use single file in BaseURL
31344      segments = [{
31345        uri: attributes.baseUrl,
31346        timeline: attributes.periodIndex,
31347        resolvedUri: attributes.baseUrl || '',
31348        duration: attributes.sourceDuration,
31349        number: 0
31350      }]; // targetDuration should be the same duration as the only segment
31351
31352      attributes.duration = attributes.sourceDuration;
31353    }
31354
31355    return {
31356      attributes: (_attributes2 = {
31357        NAME: attributes.id,
31358        BANDWIDTH: attributes.bandwidth
31359      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
31360      uri: '',
31361      endList: (attributes.type || 'static') === 'static',
31362      timeline: attributes.periodIndex,
31363      resolvedUri: attributes.baseUrl || '',
31364      targetDuration: attributes.duration,
31365      segments: segments,
31366      mediaSequence: segments.length ? segments[0].number : 1
31367    };
31368  };
31369
31370  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) {
31371    if (sidxMapping === void 0) {
31372      sidxMapping = {};
31373    }
31374
31375    var mainPlaylist;
31376    var formattedPlaylists = playlists.reduce(function (a, playlist) {
31377      var role = playlist.attributes.role && playlist.attributes.role.value || '';
31378      var language = playlist.attributes.lang || '';
31379      var label = 'main';
31380
31381      if (language) {
31382        var roleLabel = role ? " (" + role + ")" : '';
31383        label = "" + playlist.attributes.lang + roleLabel;
31384      } // skip if we already have the highest quality audio for a language
31385
31386
31387      if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
31388        return a;
31389      }
31390
31391      a[label] = {
31392        language: language,
31393        autoselect: true,
31394        "default": role === 'main',
31395        playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping),
31396        uri: ''
31397      };
31398
31399      if (typeof mainPlaylist === 'undefined' && role === 'main') {
31400        mainPlaylist = playlist;
31401        mainPlaylist["default"] = true;
31402      }
31403
31404      return a;
31405    }, {}); // if no playlists have role "main", mark the first as main
31406
31407    if (!mainPlaylist) {
31408      var firstLabel = Object.keys(formattedPlaylists)[0];
31409      formattedPlaylists[firstLabel]["default"] = true;
31410    }
31411
31412    return formattedPlaylists;
31413  };
31414
31415  var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) {
31416    if (sidxMapping === void 0) {
31417      sidxMapping = {};
31418    }
31419
31420    return playlists.reduce(function (a, playlist) {
31421      var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
31422
31423      if (a[label]) {
31424        return a;
31425      }
31426
31427      a[label] = {
31428        language: label,
31429        "default": false,
31430        autoselect: false,
31431        playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping),
31432        uri: ''
31433      };
31434      return a;
31435    }, {});
31436  };
31437
31438  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
31439    var _attributes3;
31440
31441    var attributes = _ref3.attributes,
31442        segments = _ref3.segments,
31443        sidx = _ref3.sidx;
31444    var playlist = {
31445      attributes: (_attributes3 = {
31446        NAME: attributes.id,
31447        AUDIO: 'audio',
31448        SUBTITLES: 'subs',
31449        RESOLUTION: {
31450          width: attributes.width,
31451          height: attributes.height
31452        },
31453        CODECS: attributes.codecs,
31454        BANDWIDTH: attributes.bandwidth
31455      }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
31456      uri: '',
31457      endList: (attributes.type || 'static') === 'static',
31458      timeline: attributes.periodIndex,
31459      resolvedUri: '',
31460      targetDuration: attributes.duration,
31461      segments: segments,
31462      mediaSequence: segments.length ? segments[0].number : 1
31463    };
31464
31465    if (attributes.contentProtection) {
31466      playlist.contentProtection = attributes.contentProtection;
31467    }
31468
31469    if (sidx) {
31470      playlist.sidx = sidx;
31471    }
31472
31473    return playlist;
31474  };
31475
31476  var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) {
31477    var _mediaGroups;
31478
31479    if (sidxMapping === void 0) {
31480      sidxMapping = {};
31481    }
31482
31483    if (!dashPlaylists.length) {
31484      return {};
31485    } // grab all master attributes
31486
31487
31488    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
31489        duration = _dashPlaylists$0$attr.sourceDuration,
31490        _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
31491        minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2;
31492
31493    var videoOnly = function videoOnly(_ref4) {
31494      var attributes = _ref4.attributes;
31495      return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
31496    };
31497
31498    var audioOnly = function audioOnly(_ref5) {
31499      var attributes = _ref5.attributes;
31500      return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
31501    };
31502
31503    var vttOnly = function vttOnly(_ref6) {
31504      var attributes = _ref6.attributes;
31505      return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
31506    };
31507
31508    var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
31509    var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
31510    var vttPlaylists = dashPlaylists.filter(vttOnly);
31511    var master = {
31512      allowCache: true,
31513      discontinuityStarts: [],
31514      segments: [],
31515      endList: true,
31516      mediaGroups: (_mediaGroups = {
31517        AUDIO: {},
31518        VIDEO: {}
31519      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
31520      uri: '',
31521      duration: duration,
31522      playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping),
31523      minimumUpdatePeriod: minimumUpdatePeriod * 1000
31524    };
31525
31526    if (audioPlaylists.length) {
31527      master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
31528    }
31529
31530    if (vttPlaylists.length) {
31531      master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
31532    }
31533
31534    return master;
31535  };
31536  /**
31537   * Calculates the R (repetition) value for a live stream (for the final segment
31538   * in a manifest where the r value is negative 1)
31539   *
31540   * @param {Object} attributes
31541   *        Object containing all inherited attributes from parent elements with attribute
31542   *        names as keys
31543   * @param {number} time
31544   *        current time (typically the total time up until the final segment)
31545   * @param {number} duration
31546   *        duration property for the given <S />
31547   *
31548   * @return {number}
31549   *        R value to reach the end of the given period
31550   */
31551
31552
31553  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
31554    var NOW = attributes.NOW,
31555        clientOffset = attributes.clientOffset,
31556        availabilityStartTime = attributes.availabilityStartTime,
31557        _attributes$timescale = attributes.timescale,
31558        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
31559        _attributes$start = attributes.start,
31560        start = _attributes$start === void 0 ? 0 : _attributes$start,
31561        _attributes$minimumUp = attributes.minimumUpdatePeriod,
31562        minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
31563    var now = (NOW + clientOffset) / 1000;
31564    var periodStartWC = availabilityStartTime + start;
31565    var periodEndWC = now + minimumUpdatePeriod;
31566    var periodDuration = periodEndWC - periodStartWC;
31567    return Math.ceil((periodDuration * timescale - time) / duration);
31568  };
31569  /**
31570   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
31571   * timing and duration
31572   *
31573   * @param {Object} attributes
31574   *        Object containing all inherited attributes from parent elements with attribute
31575   *        names as keys
31576   * @param {Object[]} segmentTimeline
31577   *        List of objects representing the attributes of each S element contained within
31578   *
31579   * @return {{number: number, duration: number, time: number, timeline: number}[]}
31580   *         List of Objects with segment timing and duration info
31581   */
31582
31583
31584  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
31585    var _attributes$type = attributes.type,
31586        type = _attributes$type === void 0 ? 'static' : _attributes$type,
31587        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
31588        minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
31589        _attributes$media = attributes.media,
31590        media = _attributes$media === void 0 ? '' : _attributes$media,
31591        sourceDuration = attributes.sourceDuration,
31592        _attributes$timescale2 = attributes.timescale,
31593        timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
31594        _attributes$startNumb = attributes.startNumber,
31595        startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
31596        timeline = attributes.periodIndex;
31597    var segments = [];
31598    var time = -1;
31599
31600    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
31601      var S = segmentTimeline[sIndex];
31602      var duration = S.d;
31603      var repeat = S.r || 0;
31604      var segmentTime = S.t || 0;
31605
31606      if (time < 0) {
31607        // first segment
31608        time = segmentTime;
31609      }
31610
31611      if (segmentTime && segmentTime > time) {
31612        // discontinuity
31613        // TODO: How to handle this type of discontinuity
31614        // timeline++ here would treat it like HLS discontuity and content would
31615        // get appended without gap
31616        // E.G.
31617        //  <S t="0" d="1" />
31618        //  <S d="1" />
31619        //  <S d="1" />
31620        //  <S t="5" d="1" />
31621        // would have $Time$ values of [0, 1, 2, 5]
31622        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
31623        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
31624        // does the value of sourceDuration consider this when calculating arbitrary
31625        // negative @r repeat value?
31626        // E.G. Same elements as above with this added at the end
31627        //  <S d="1" r="-1" />
31628        //  with a sourceDuration of 10
31629        // Would the 2 gaps be included in the time duration calculations resulting in
31630        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
31631        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
31632        time = segmentTime;
31633      }
31634
31635      var count = void 0;
31636
31637      if (repeat < 0) {
31638        var nextS = sIndex + 1;
31639
31640        if (nextS === segmentTimeline.length) {
31641          // last segment
31642          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
31643            count = getLiveRValue(attributes, time, duration);
31644          } else {
31645            // TODO: This may be incorrect depending on conclusion of TODO above
31646            count = (sourceDuration * timescale - time) / duration;
31647          }
31648        } else {
31649          count = (segmentTimeline[nextS].t - time) / duration;
31650        }
31651      } else {
31652        count = repeat + 1;
31653      }
31654
31655      var end = startNumber + segments.length + count;
31656      var number = startNumber + segments.length;
31657
31658      while (number < end) {
31659        segments.push({
31660          number: number,
31661          duration: duration / timescale,
31662          time: time,
31663          timeline: timeline
31664        });
31665        time += duration;
31666        number++;
31667      }
31668    }
31669
31670    return segments;
31671  };
31672
31673  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
31674  /**
31675   * Replaces template identifiers with corresponding values. To be used as the callback
31676   * for String.prototype.replace
31677   *
31678   * @name replaceCallback
31679   * @function
31680   * @param {string} match
31681   *        Entire match of identifier
31682   * @param {string} identifier
31683   *        Name of matched identifier
31684   * @param {string} format
31685   *        Format tag string. Its presence indicates that padding is expected
31686   * @param {string} width
31687   *        Desired length of the replaced value. Values less than this width shall be left
31688   *        zero padded
31689   * @return {string}
31690   *         Replacement for the matched identifier
31691   */
31692
31693  /**
31694   * Returns a function to be used as a callback for String.prototype.replace to replace
31695   * template identifiers
31696   *
31697   * @param {Obect} values
31698   *        Object containing values that shall be used to replace known identifiers
31699   * @param {number} values.RepresentationID
31700   *        Value of the Representation@id attribute
31701   * @param {number} values.Number
31702   *        Number of the corresponding segment
31703   * @param {number} values.Bandwidth
31704   *        Value of the Representation@bandwidth attribute.
31705   * @param {number} values.Time
31706   *        Timestamp value of the corresponding segment
31707   * @return {replaceCallback}
31708   *         Callback to be used with String.prototype.replace to replace identifiers
31709   */
31710
31711  var identifierReplacement = function identifierReplacement(values) {
31712    return function (match, identifier, format, width) {
31713      if (match === '$$') {
31714        // escape sequence
31715        return '$';
31716      }
31717
31718      if (typeof values[identifier] === 'undefined') {
31719        return match;
31720      }
31721
31722      var value = '' + values[identifier];
31723
31724      if (identifier === 'RepresentationID') {
31725        // Format tag shall not be present with RepresentationID
31726        return value;
31727      }
31728
31729      if (!format) {
31730        width = 1;
31731      } else {
31732        width = parseInt(width, 10);
31733      }
31734
31735      if (value.length >= width) {
31736        return value;
31737      }
31738
31739      return "" + new Array(width - value.length + 1).join('0') + value;
31740    };
31741  };
31742  /**
31743   * Constructs a segment url from a template string
31744   *
31745   * @param {string} url
31746   *        Template string to construct url from
31747   * @param {Obect} values
31748   *        Object containing values that shall be used to replace known identifiers
31749   * @param {number} values.RepresentationID
31750   *        Value of the Representation@id attribute
31751   * @param {number} values.Number
31752   *        Number of the corresponding segment
31753   * @param {number} values.Bandwidth
31754   *        Value of the Representation@bandwidth attribute.
31755   * @param {number} values.Time
31756   *        Timestamp value of the corresponding segment
31757   * @return {string}
31758   *         Segment url with identifiers replaced
31759   */
31760
31761
31762  var constructTemplateUrl = function constructTemplateUrl(url, values) {
31763    return url.replace(identifierPattern, identifierReplacement(values));
31764  };
31765  /**
31766   * Generates a list of objects containing timing and duration information about each
31767   * segment needed to generate segment uris and the complete segment object
31768   *
31769   * @param {Object} attributes
31770   *        Object containing all inherited attributes from parent elements with attribute
31771   *        names as keys
31772   * @param {Object[]|undefined} segmentTimeline
31773   *        List of objects representing the attributes of each S element contained within
31774   *        the SegmentTimeline element
31775   * @return {{number: number, duration: number, time: number, timeline: number}[]}
31776   *         List of Objects with segment timing and duration info
31777   */
31778
31779
31780  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
31781    if (!attributes.duration && !segmentTimeline) {
31782      // if neither @duration or SegmentTimeline are present, then there shall be exactly
31783      // one media segment
31784      return [{
31785        number: attributes.startNumber || 1,
31786        duration: attributes.sourceDuration,
31787        time: 0,
31788        timeline: attributes.periodIndex
31789      }];
31790    }
31791
31792    if (attributes.duration) {
31793      return parseByDuration(attributes);
31794    }
31795
31796    return parseByTimeline(attributes, segmentTimeline);
31797  };
31798  /**
31799   * Generates a list of segments using information provided by the SegmentTemplate element
31800   *
31801   * @param {Object} attributes
31802   *        Object containing all inherited attributes from parent elements with attribute
31803   *        names as keys
31804   * @param {Object[]|undefined} segmentTimeline
31805   *        List of objects representing the attributes of each S element contained within
31806   *        the SegmentTimeline element
31807   * @return {Object[]}
31808   *         List of segment objects
31809   */
31810
31811
31812  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
31813    var templateValues = {
31814      RepresentationID: attributes.id,
31815      Bandwidth: attributes.bandwidth || 0
31816    };
31817    var _attributes$initializ = attributes.initialization,
31818        initialization = _attributes$initializ === void 0 ? {
31819      sourceURL: '',
31820      range: ''
31821    } : _attributes$initializ;
31822    var mapSegment = urlTypeToSegment({
31823      baseUrl: attributes.baseUrl,
31824      source: constructTemplateUrl(initialization.sourceURL, templateValues),
31825      range: initialization.range
31826    });
31827    var segments = parseTemplateInfo(attributes, segmentTimeline);
31828    return segments.map(function (segment) {
31829      templateValues.Number = segment.number;
31830      templateValues.Time = segment.time;
31831      var uri = constructTemplateUrl(attributes.media || '', templateValues);
31832      return {
31833        uri: uri,
31834        timeline: segment.timeline,
31835        duration: segment.duration,
31836        resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
31837        map: mapSegment,
31838        number: segment.number
31839      };
31840    });
31841  };
31842  /**
31843   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
31844   * to an object that matches the output of a segment in videojs/mpd-parser
31845   *
31846   * @param {Object} attributes
31847   *   Object containing all inherited attributes from parent elements with attribute
31848   *   names as keys
31849   * @param {Object} segmentUrl
31850   *   <SegmentURL> node to translate into a segment object
31851   * @return {Object} translated segment object
31852   */
31853
31854
31855  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
31856    var baseUrl = attributes.baseUrl,
31857        _attributes$initializ = attributes.initialization,
31858        initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
31859    var initSegment = urlTypeToSegment({
31860      baseUrl: baseUrl,
31861      source: initialization.sourceURL,
31862      range: initialization.range
31863    });
31864    var segment = urlTypeToSegment({
31865      baseUrl: baseUrl,
31866      source: segmentUrl.media,
31867      range: segmentUrl.mediaRange
31868    });
31869    segment.map = initSegment;
31870    return segment;
31871  };
31872  /**
31873   * Generates a list of segments using information provided by the SegmentList element
31874   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
31875   * node should be translated into segment.
31876   *
31877   * @param {Object} attributes
31878   *   Object containing all inherited attributes from parent elements with attribute
31879   *   names as keys
31880   * @param {Object[]|undefined} segmentTimeline
31881   *        List of objects representing the attributes of each S element contained within
31882   *        the SegmentTimeline element
31883   * @return {Object.<Array>} list of segments
31884   */
31885
31886
31887  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
31888    var duration = attributes.duration,
31889        _attributes$segmentUr = attributes.segmentUrls,
31890        segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
31891    // if both SegmentTimeline and @duration are defined, it is outside of spec.
31892
31893    if (!duration && !segmentTimeline || duration && segmentTimeline) {
31894      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
31895    }
31896
31897    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
31898      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
31899    });
31900    var segmentTimeInfo;
31901
31902    if (duration) {
31903      segmentTimeInfo = parseByDuration(attributes);
31904    }
31905
31906    if (segmentTimeline) {
31907      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
31908    }
31909
31910    var segments = segmentTimeInfo.map(function (segmentTime, index) {
31911      if (segmentUrlMap[index]) {
31912        var segment = segmentUrlMap[index];
31913        segment.timeline = segmentTime.timeline;
31914        segment.duration = segmentTime.duration;
31915        segment.number = segmentTime.number;
31916        return segment;
31917      } // Since we're mapping we should get rid of any blank segments (in case
31918      // the given SegmentTimeline is handling for more elements than we have
31919      // SegmentURLs for).
31920
31921    }).filter(function (segment) {
31922      return segment;
31923    });
31924    return segments;
31925  };
31926
31927  var generateSegments = function generateSegments(_ref) {
31928    var attributes = _ref.attributes,
31929        segmentInfo = _ref.segmentInfo;
31930    var segmentAttributes;
31931    var segmentsFn;
31932
31933    if (segmentInfo.template) {
31934      segmentsFn = segmentsFromTemplate;
31935      segmentAttributes = merge(attributes, segmentInfo.template);
31936    } else if (segmentInfo.base) {
31937      segmentsFn = segmentsFromBase;
31938      segmentAttributes = merge(attributes, segmentInfo.base);
31939    } else if (segmentInfo.list) {
31940      segmentsFn = segmentsFromList;
31941      segmentAttributes = merge(attributes, segmentInfo.list);
31942    }
31943
31944    var segmentsInfo = {
31945      attributes: attributes
31946    };
31947
31948    if (!segmentsFn) {
31949      return segmentsInfo;
31950    }
31951
31952    var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
31953    // must be in seconds. Since we've generated the segment list, we no longer need
31954    // @duration to be in @timescale units, so we can convert it here.
31955
31956    if (segmentAttributes.duration) {
31957      var _segmentAttributes = segmentAttributes,
31958          duration = _segmentAttributes.duration,
31959          _segmentAttributes$ti = _segmentAttributes.timescale,
31960          timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
31961      segmentAttributes.duration = duration / timescale;
31962    } else if (segments.length) {
31963      // if there is no @duration attribute, use the largest segment duration as
31964      // as target duration
31965      segmentAttributes.duration = segments.reduce(function (max, segment) {
31966        return Math.max(max, Math.ceil(segment.duration));
31967      }, 0);
31968    } else {
31969      segmentAttributes.duration = 0;
31970    }
31971
31972    segmentsInfo.attributes = segmentAttributes;
31973    segmentsInfo.segments = segments; // This is a sidx box without actual segment information
31974
31975    if (segmentInfo.base && segmentAttributes.indexRange) {
31976      segmentsInfo.sidx = segments[0];
31977      segmentsInfo.segments = [];
31978    }
31979
31980    return segmentsInfo;
31981  };
31982
31983  var toPlaylists = function toPlaylists(representations) {
31984    return representations.map(generateSegments);
31985  };
31986
vendor: 13,526 bytes, lines 31987-32383
31987  var findChildren = function findChildren(element, name) {
31988    return from(element.childNodes).filter(function (_ref) {
31989      var tagName = _ref.tagName;
31990      return tagName === name;
31991    });
31992  };
31993
31994  var getContent = function getContent(element) {
31995    return element.textContent.trim();
31996  };
31997
31998  var parseDuration = function parseDuration(str) {
31999    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
32000    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
32001    var SECONDS_IN_DAY = 24 * 60 * 60;
32002    var SECONDS_IN_HOUR = 60 * 60;
32003    var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
32004
32005    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
32006    var match = durationRegex.exec(str);
32007
32008    if (!match) {
32009      return 0;
32010    }
32011
32012    var _match$slice = match.slice(1),
32013        year = _match$slice[0],
32014        month = _match$slice[1],
32015        day = _match$slice[2],
32016        hour = _match$slice[3],
32017        minute = _match$slice[4],
32018        second = _match$slice[5];
32019
32020    return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
32021  };
32022
32023  var parseDate = function parseDate(str) {
32024    // Date format without timezone according to ISO 8601
32025    // YYY-MM-DDThh:mm:ss.ssssss
32026    var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
32027    // expressed by ending with 'Z'
32028
32029    if (dateRegex.test(str)) {
32030      str += 'Z';
32031    }
32032
32033    return Date.parse(str);
32034  };
32035
32036  var parsers = {
32037    /**
32038     * Specifies the duration of the entire Media Presentation. Format is a duration string
32039     * as specified in ISO 8601
32040     *
32041     * @param {string} value
32042     *        value of attribute as a string
32043     * @return {number}
32044     *         The duration in seconds
32045     */
32046    mediaPresentationDuration: function mediaPresentationDuration(value) {
32047      return parseDuration(value);
32048    },
32049
32050    /**
32051     * Specifies the Segment availability start time for all Segments referred to in this
32052     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
32053     * time. Format is a date string as specified in ISO 8601
32054     *
32055     * @param {string} value
32056     *        value of attribute as a string
32057     * @return {number}
32058     *         The date as seconds from unix epoch
32059     */
32060    availabilityStartTime: function availabilityStartTime(value) {
32061      return parseDate(value) / 1000;
32062    },
32063
32064    /**
32065     * Specifies the smallest period between potential changes to the MPD. Format is a
32066     * duration string as specified in ISO 8601
32067     *
32068     * @param {string} value
32069     *        value of attribute as a string
32070     * @return {number}
32071     *         The duration in seconds
32072     */
32073    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
32074      return parseDuration(value);
32075    },
32076
32077    /**
32078     * Specifies the duration of the smallest time shifting buffer for any Representation
32079     * in the MPD. Format is a duration string as specified in ISO 8601
32080     *
32081     * @param {string} value
32082     *        value of attribute as a string
32083     * @return {number}
32084     *         The duration in seconds
32085     */
32086    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
32087      return parseDuration(value);
32088    },
32089
32090    /**
32091     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
32092     * Format is a duration string as specified in ISO 8601
32093     *
32094     * @param {string} value
32095     *        value of attribute as a string
32096     * @return {number}
32097     *         The duration in seconds
32098     */
32099    start: function start(value) {
32100      return parseDuration(value);
32101    },
32102
32103    /**
32104     * Specifies the width of the visual presentation
32105     *
32106     * @param {string} value
32107     *        value of attribute as a string
32108     * @return {number}
32109     *         The parsed width
32110     */
32111    width: function width(value) {
32112      return parseInt(value, 10);
32113    },
32114
32115    /**
32116     * Specifies the height of the visual presentation
32117     *
32118     * @param {string} value
32119     *        value of attribute as a string
32120     * @return {number}
32121     *         The parsed height
32122     */
32123    height: function height(value) {
32124      return parseInt(value, 10);
32125    },
32126
32127    /**
32128     * Specifies the bitrate of the representation
32129     *
32130     * @param {string} value
32131     *        value of attribute as a string
32132     * @return {number}
32133     *         The parsed bandwidth
32134     */
32135    bandwidth: function bandwidth(value) {
32136      return parseInt(value, 10);
32137    },
32138
32139    /**
32140     * Specifies the number of the first Media Segment in this Representation in the Period
32141     *
32142     * @param {string} value
32143     *        value of attribute as a string
32144     * @return {number}
32145     *         The parsed number
32146     */
32147    startNumber: function startNumber(value) {
32148      return parseInt(value, 10);
32149    },
32150
32151    /**
32152     * Specifies the timescale in units per seconds
32153     *
32154     * @param {string} value
32155     *        value of attribute as a string
32156     * @return {number}
32157     *         The aprsed timescale
32158     */
32159    timescale: function timescale(value) {
32160      return parseInt(value, 10);
32161    },
32162
32163    /**
32164     * Specifies the constant approximate Segment duration
32165     * NOTE: The <Period> element also contains an @duration attribute. This duration
32166     *       specifies the duration of the Period. This attribute is currently not
32167     *       supported by the rest of the parser, however we still check for it to prevent
32168     *       errors.
32169     *
32170     * @param {string} value
32171     *        value of attribute as a string
32172     * @return {number}
32173     *         The parsed duration
32174     */
32175    duration: function duration(value) {
32176      var parsedValue = parseInt(value, 10);
32177
32178      if (isNaN(parsedValue)) {
32179        return parseDuration(value);
32180      }
32181
32182      return parsedValue;
32183    },
32184
32185    /**
32186     * Specifies the Segment duration, in units of the value of the @timescale.
32187     *
32188     * @param {string} value
32189     *        value of attribute as a string
32190     * @return {number}
32191     *         The parsed duration
32192     */
32193    d: function d(value) {
32194      return parseInt(value, 10);
32195    },
32196
32197    /**
32198     * Specifies the MPD start time, in @timescale units, the first Segment in the series
32199     * starts relative to the beginning of the Period
32200     *
32201     * @param {string} value
32202     *        value of attribute as a string
32203     * @return {number}
32204     *         The parsed time
32205     */
32206    t: function t(value) {
32207      return parseInt(value, 10);
32208    },
32209
32210    /**
32211     * Specifies the repeat count of the number of following contiguous Segments with the
32212     * same duration expressed by the value of @d
32213     *
32214     * @param {string} value
32215     *        value of attribute as a string
32216     * @return {number}
32217     *         The parsed number
32218     */
32219    r: function r(value) {
32220      return parseInt(value, 10);
32221    },
32222
32223    /**
32224     * Default parser for all other attributes. Acts as a no-op and just returns the value
32225     * as a string
32226     *
32227     * @param {string} value
32228     *        value of attribute as a string
32229     * @return {string}
32230     *         Unparsed value
32231     */
32232    DEFAULT: function DEFAULT(value) {
32233      return value;
32234    }
32235  };
32236  /**
32237   * Gets all the attributes and values of the provided node, parses attributes with known
32238   * types, and returns an object with attribute names mapped to values.
32239   *
32240   * @param {Node} el
32241   *        The node to parse attributes from
32242   * @return {Object}
32243   *         Object with all attributes of el parsed
32244   */
32245
32246  var parseAttributes$1 = function parseAttributes(el) {
32247    if (!(el && el.attributes)) {
32248      return {};
32249    }
32250
32251    return from(el.attributes).reduce(function (a, e) {
32252      var parseFn = parsers[e.name] || parsers.DEFAULT;
32253      a[e.name] = parseFn(e.value);
32254      return a;
32255    }, {});
32256  };
32257
32258  function decodeB64ToUint8Array$1(b64Text) {
32259    var decodedString = window$1.atob(b64Text);
32260    var array = new Uint8Array(decodedString.length);
32261
32262    for (var i = 0; i < decodedString.length; i++) {
32263      array[i] = decodedString.charCodeAt(i);
32264    }
32265
32266    return array;
32267  }
32268
32269  var keySystemsMap = {
32270    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
32271    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
32272    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
32273    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
32274  };
32275  /**
32276   * Builds a list of urls that is the product of the reference urls and BaseURL values
32277   *
32278   * @param {string[]} referenceUrls
32279   *        List of reference urls to resolve to
32280   * @param {Node[]} baseUrlElements
32281   *        List of BaseURL nodes from the mpd
32282   * @return {string[]}
32283   *         List of resolved urls
32284   */
32285
32286  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
32287    if (!baseUrlElements.length) {
32288      return referenceUrls;
32289    }
32290
32291    return flatten(referenceUrls.map(function (reference) {
32292      return baseUrlElements.map(function (baseUrlElement) {
32293        return resolveUrl(reference, getContent(baseUrlElement));
32294      });
32295    }));
32296  };
32297  /**
32298   * Contains all Segment information for its containing AdaptationSet
32299   *
32300   * @typedef {Object} SegmentInformation
32301   * @property {Object|undefined} template
32302   *           Contains the attributes for the SegmentTemplate node
32303   * @property {Object[]|undefined} timeline
32304   *           Contains a list of atrributes for each S node within the SegmentTimeline node
32305   * @property {Object|undefined} list
32306   *           Contains the attributes for the SegmentList node
32307   * @property {Object|undefined} base
32308   *           Contains the attributes for the SegmentBase node
32309   */
32310
32311  /**
32312   * Returns all available Segment information contained within the AdaptationSet node
32313   *
32314   * @param {Node} adaptationSet
32315   *        The AdaptationSet node to get Segment information from
32316   * @return {SegmentInformation}
32317   *         The Segment information contained within the provided AdaptationSet
32318   */
32319
32320
32321  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
32322    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
32323    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
32324    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
32325      return merge({
32326        tag: 'SegmentURL'
32327      }, parseAttributes$1(s));
32328    });
32329    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
32330    var segmentTimelineParentNode = segmentList || segmentTemplate;
32331    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
32332    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
32333    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
32334    // @initialization and an <Initialization> node.  @initialization can be templated,
32335    // while the node can have a url and range specified.  If the <SegmentTemplate> has
32336    // both @initialization and an <Initialization> subelement we opt to override with
32337    // the node, as this interaction is not defined in the spec.
32338
32339    var template = segmentTemplate && parseAttributes$1(segmentTemplate);
32340
32341    if (template && segmentInitialization) {
32342      template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
32343    } else if (template && template.initialization) {
32344      // If it is @initialization we convert it to an object since this is the format that
32345      // later functions will rely on for the initialization segment.  This is only valid
32346      // for <SegmentTemplate>
32347      template.initialization = {
32348        sourceURL: template.initialization
32349      };
32350    }
32351
32352    var segmentInfo = {
32353      template: template,
32354      timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
32355        return parseAttributes$1(s);
32356      }),
32357      list: segmentList && merge(parseAttributes$1(segmentList), {
32358        segmentUrls: segmentUrls,
32359        initialization: parseAttributes$1(segmentInitialization)
32360      }),
32361      base: segmentBase && merge(parseAttributes$1(segmentBase), {
32362        initialization: parseAttributes$1(segmentInitialization)
32363      })
32364    };
32365    Object.keys(segmentInfo).forEach(function (key) {
32366      if (!segmentInfo[key]) {
32367        delete segmentInfo[key];
32368      }
32369    });
32370    return segmentInfo;
32371  };
32372  /**
32373   * Contains Segment information and attributes needed to construct a Playlist object
32374   * from a Representation
32375   *
32376   * @typedef {Object} RepresentationInformation
32377   * @property {SegmentInformation} segmentInfo
32378   *           Segment information for this Representation
32379   * @property {Object} attributes
32380   *           Inherited attributes for this Representation
32381   */
32382
32383  /**
32384   * Maps a Representation node to an object containing Segment information and attributes
32385   *
32386   * @name inheritBaseUrlsCallback
32387   * @function
32388   * @param {Node} representation
32389   *        Representation node from the mpd
32390   * @return {RepresentationInformation}
32391   *         Representation information needed to construct a Playlist object
32392   */
32393
32394  /**
32395   * Returns a callback for Array.prototype.map for mapping Representation nodes to
32396   * Segment information and attributes using inherited BaseURL nodes.
32397   *
32398   * @param {Object} adaptationSetAttributes
32399   *        Contains attributes inherited by the AdaptationSet
32400   * @param {string[]} adaptationSetBaseUrls
32401   *        Contains list of resolved base urls inherited by the AdaptationSet
32402   * @param {SegmentInformation} adaptationSetSegmentInfo
32403   *        Contains Segment information for the AdaptationSet
32404   * @return {inheritBaseUrlsCallback}
32405   *         Callback map function
32406   */
32407
32408
32409  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
32410    return function (representation) {
32411      var repBaseUrlElements = findChildren(representation, 'BaseURL');
32412      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
32413      var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
32414      var representationSegmentInfo = getSegmentInformation(representation);
32415      return repBaseUrls.map(function (baseUrl) {
32416        return {
32417          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
32418          attributes: merge(attributes, {
32419            baseUrl: baseUrl
32420          })
32421        };
32422      });
32423    };
32424  };
32425  /**
32426   * Tranforms a series of content protection nodes to
vendor: 13,158 bytes, lines 32427-32817
32427   * an object containing pssh data by key system
32428   *
32429   * @param {Node[]} contentProtectionNodes
32430   *        Content protection nodes
32431   * @return {Object}
32432   *        Object containing pssh data by key system
32433   */
32434
32435
32436  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
32437    return contentProtectionNodes.reduce(function (acc, node) {
32438      var attributes = parseAttributes$1(node);
32439      var keySystem = keySystemsMap[attributes.schemeIdUri];
32440
32441      if (keySystem) {
32442        acc[keySystem] = {
32443          attributes: attributes
32444        };
32445        var psshNode = findChildren(node, 'cenc:pssh')[0];
32446
32447        if (psshNode) {
32448          var pssh = getContent(psshNode);
32449          var psshBuffer = pssh && decodeB64ToUint8Array$1(pssh);
32450          acc[keySystem].pssh = psshBuffer;
32451        }
32452      }
32453
32454      return acc;
32455    }, {});
32456  };
32457  /**
32458   * Maps an AdaptationSet node to a list of Representation information objects
32459   *
32460   * @name toRepresentationsCallback
32461   * @function
32462   * @param {Node} adaptationSet
32463   *        AdaptationSet node from the mpd
32464   * @return {RepresentationInformation[]}
32465   *         List of objects containing Representaion information
32466   */
32467
32468  /**
32469   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
32470   * Representation information objects
32471   *
32472   * @param {Object} periodAttributes
32473   *        Contains attributes inherited by the Period
32474   * @param {string[]} periodBaseUrls
32475   *        Contains list of resolved base urls inherited by the Period
32476   * @param {string[]} periodSegmentInfo
32477   *        Contains Segment Information at the period level
32478   * @return {toRepresentationsCallback}
32479   *         Callback map function
32480   */
32481
32482
32483  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
32484    return function (adaptationSet) {
32485      var adaptationSetAttributes = parseAttributes$1(adaptationSet);
32486      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
32487      var role = findChildren(adaptationSet, 'Role')[0];
32488      var roleAttributes = {
32489        role: parseAttributes$1(role)
32490      };
32491      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
32492      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
32493
32494      if (Object.keys(contentProtection).length) {
32495        attrs = merge(attrs, {
32496          contentProtection: contentProtection
32497        });
32498      }
32499
32500      var segmentInfo = getSegmentInformation(adaptationSet);
32501      var representations = findChildren(adaptationSet, 'Representation');
32502      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
32503      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
32504    };
32505  };
32506  /**
32507   * Maps an Period node to a list of Representation inforamtion objects for all
32508   * AdaptationSet nodes contained within the Period
32509   *
32510   * @name toAdaptationSetsCallback
32511   * @function
32512   * @param {Node} period
32513   *        Period node from the mpd
32514   * @param {number} periodIndex
32515   *        Index of the Period within the mpd
32516   * @return {RepresentationInformation[]}
32517   *         List of objects containing Representaion information
32518   */
32519
32520  /**
32521   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
32522   * Representation information objects
32523   *
32524   * @param {Object} mpdAttributes
32525   *        Contains attributes inherited by the mpd
32526   * @param {string[]} mpdBaseUrls
32527   *        Contains list of resolved base urls inherited by the mpd
32528   * @return {toAdaptationSetsCallback}
32529   *         Callback map function
32530   */
32531
32532
32533  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
32534    return function (period, index) {
32535      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
32536      var periodAtt = parseAttributes$1(period);
32537      var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
32538
32539      var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
32540      var periodAttributes = merge(mpdAttributes, {
32541        periodIndex: periodIndex
32542      });
32543      var adaptationSets = findChildren(period, 'AdaptationSet');
32544      var periodSegmentInfo = getSegmentInformation(period);
32545      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
32546    };
32547  };
32548  /**
32549   * Traverses the mpd xml tree to generate a list of Representation information objects
32550   * that have inherited attributes from parent nodes
32551   *
32552   * @param {Node} mpd
32553   *        The root node of the mpd
32554   * @param {Object} options
32555   *        Available options for inheritAttributes
32556   * @param {string} options.manifestUri
32557   *        The uri source of the mpd
32558   * @param {number} options.NOW
32559   *        Current time per DASH IOP.  Default is current time in ms since epoch
32560   * @param {number} options.clientOffset
32561   *        Client time difference from NOW (in milliseconds)
32562   * @return {RepresentationInformation[]}
32563   *         List of objects containing Representation information
32564   */
32565
32566
32567  var inheritAttributes = function inheritAttributes(mpd, options) {
32568    if (options === void 0) {
32569      options = {};
32570    }
32571
32572    var _options = options,
32573        _options$manifestUri = _options.manifestUri,
32574        manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
32575        _options$NOW = _options.NOW,
32576        NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
32577        _options$clientOffset = _options.clientOffset,
32578        clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
32579    var periods = findChildren(mpd, 'Period');
32580
32581    if (!periods.length) {
32582      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
32583    }
32584
32585    var mpdAttributes = parseAttributes$1(mpd);
32586    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
32587    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
32588    mpdAttributes.NOW = NOW;
32589    mpdAttributes.clientOffset = clientOffset;
32590    return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
32591  };
32592
32593  var stringToMpdXml = function stringToMpdXml(manifestString) {
32594    if (manifestString === '') {
32595      throw new Error(errors.DASH_EMPTY_MANIFEST);
32596    }
32597
32598    var parser = new window$1.DOMParser();
32599    var xml = parser.parseFromString(manifestString, 'application/xml');
32600    var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
32601
32602    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
32603      throw new Error(errors.DASH_INVALID_XML);
32604    }
32605
32606    return mpd;
32607  };
32608  /**
32609   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
32610   *
32611   * @param {string} mpd
32612   *        XML string of the MPD manifest
32613   * @return {Object|null}
32614   *         Attributes of UTCTiming node specified in the manifest. Null if none found
32615   */
32616
32617
32618  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
32619    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
32620
32621    if (!UTCTimingNode) {
32622      return null;
32623    }
32624
32625    var attributes = parseAttributes$1(UTCTimingNode);
32626
32627    switch (attributes.schemeIdUri) {
32628      case 'urn:mpeg:dash:utc:http-head:2014':
32629      case 'urn:mpeg:dash:utc:http-head:2012':
32630        attributes.method = 'HEAD';
32631        break;
32632
32633      case 'urn:mpeg:dash:utc:http-xsdate:2014':
32634      case 'urn:mpeg:dash:utc:http-iso:2014':
32635      case 'urn:mpeg:dash:utc:http-xsdate:2012':
32636      case 'urn:mpeg:dash:utc:http-iso:2012':
32637        attributes.method = 'GET';
32638        break;
32639
32640      case 'urn:mpeg:dash:utc:direct:2014':
32641      case 'urn:mpeg:dash:utc:direct:2012':
32642        attributes.method = 'DIRECT';
32643        attributes.value = Date.parse(attributes.value);
32644        break;
32645
32646      case 'urn:mpeg:dash:utc:http-ntp:2014':
32647      case 'urn:mpeg:dash:utc:ntp:2014':
32648      case 'urn:mpeg:dash:utc:sntp:2014':
32649      default:
32650        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
32651    }
32652
32653    return attributes;
32654  };
32655
32656  var parse = function parse(manifestString, options) {
32657    if (options === void 0) {
32658      options = {};
32659    }
32660
32661    return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping);
32662  };
32663  /**
32664   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
32665   *
32666   * @param {string} manifestString
32667   *        XML string of the MPD manifest
32668   * @return {Object|null}
32669   *         Attributes of UTCTiming node specified in the manifest. Null if none found
32670   */
32671
32672
32673  var parseUTCTiming = function parseUTCTiming(manifestString) {
32674    return parseUTCTimingScheme(stringToMpdXml(manifestString));
32675  };
32676
32677  /**
32678   * mux.js
32679   *
32680   * Copyright (c) Brightcove
32681   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
32682   */
32683  var toUnsigned = function toUnsigned(value) {
32684    return value >>> 0;
32685  };
32686
32687  var toHexString = function toHexString(value) {
32688    return ('00' + value.toString(16)).slice(-2);
32689  };
32690
32691  var bin = {
32692    toUnsigned: toUnsigned,
32693    toHexString: toHexString
32694  };
32695
32696  var inspectMp4,
32697      _textifyMp,
32698      toUnsigned$1 = bin.toUnsigned,
32699      parseMp4Date = function parseMp4Date(seconds) {
32700    return new Date(seconds * 1000 - 2082844800000);
32701  },
32702      parseSampleFlags = function parseSampleFlags(flags) {
32703    return {
32704      isLeading: (flags[0] & 0x0c) >>> 2,
32705      dependsOn: flags[0] & 0x03,
32706      isDependedOn: (flags[1] & 0xc0) >>> 6,
32707      hasRedundancy: (flags[1] & 0x30) >>> 4,
32708      paddingValue: (flags[1] & 0x0e) >>> 1,
32709      isNonSyncSample: flags[1] & 0x01,
32710      degradationPriority: flags[2] << 8 | flags[3]
32711    };
32712  },
32713
32714  /**
32715   * Returns the string representation of an ASCII encoded four byte buffer.
32716   * @param buffer {Uint8Array} a four-byte buffer to translate
32717   * @return {string} the corresponding string
32718   */
32719  parseType = function parseType(buffer) {
32720    var result = '';
32721    result += String.fromCharCode(buffer[0]);
32722    result += String.fromCharCode(buffer[1]);
32723    result += String.fromCharCode(buffer[2]);
32724    result += String.fromCharCode(buffer[3]);
32725    return result;
32726  },
32727      // Find the data for a box specified by its path
32728  findBox = function findBox(data, path) {
32729    var results = [],
32730        i,
32731        size,
32732        type,
32733        end,
32734        subresults;
32735
32736    if (!path.length) {
32737      // short-circuit the search for empty paths
32738      return null;
32739    }
32740
32741    for (i = 0; i < data.byteLength;) {
32742      size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
32743      type = parseType(data.subarray(i + 4, i + 8));
32744      end = size > 1 ? i + size : data.byteLength;
32745
32746      if (type === path[0]) {
32747        if (path.length === 1) {
32748          // this is the end of the path and we've found the box we were
32749          // looking for
32750          results.push(data.subarray(i + 8, end));
32751        } else {
32752          // recursively search for the next box along the path
32753          subresults = findBox(data.subarray(i + 8, end), path.slice(1));
32754
32755          if (subresults.length) {
32756            results = results.concat(subresults);
32757          }
32758        }
32759      }
32760
32761      i = end;
32762    } // we've finished searching all of data
32763
32764
32765    return results;
32766  },
32767      nalParse = function nalParse(avcStream) {
32768    var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
32769        result = [],
32770        i,
32771        length;
32772
32773    for (i = 0; i + 4 < avcStream.length; i += length) {
32774      length = avcView.getUint32(i);
32775      i += 4; // bail if this doesn't appear to be an H264 stream
32776
32777      if (length <= 0) {
32778        result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
32779        continue;
32780      }
32781
32782      switch (avcStream[i] & 0x1F) {
32783        case 0x01:
32784          result.push('slice_layer_without_partitioning_rbsp');
32785          break;
32786
32787        case 0x05:
32788          result.push('slice_layer_without_partitioning_rbsp_idr');
32789          break;
32790
32791        case 0x06:
32792          result.push('sei_rbsp');
32793          break;
32794
32795        case 0x07:
32796          result.push('seq_parameter_set_rbsp');
32797          break;
32798
32799        case 0x08:
32800          result.push('pic_parameter_set_rbsp');
32801          break;
32802
32803        case 0x09:
32804          result.push('access_unit_delimiter_rbsp');
32805          break;
32806
32807        default:
32808          result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
32809          break;
32810      }
32811    }
32812
32813    return result;
32814  },
32815      // registry of handlers for individual mp4 box types
32816  parse$1 = {
32817    // codingname, not a first-class box type. stsd entries share the
vendor: 6,697 bytes, lines 32818-33018
32818    // same format as real boxes so the parsing infrastructure can be
32819    // shared
32820    avc1: function avc1(data) {
32821      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
32822      return {
32823        dataReferenceIndex: view.getUint16(6),
32824        width: view.getUint16(24),
32825        height: view.getUint16(26),
32826        horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
32827        vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
32828        frameCount: view.getUint16(40),
32829        depth: view.getUint16(74),
32830        config: inspectMp4(data.subarray(78, data.byteLength))
32831      };
32832    },
32833    avcC: function avcC(data) {
32834      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
32835          result = {
32836        configurationVersion: data[0],
32837        avcProfileIndication: data[1],
32838        profileCompatibility: data[2],
32839        avcLevelIndication: data[3],
32840        lengthSizeMinusOne: data[4] & 0x03,
32841        sps: [],
32842        pps: []
32843      },
32844          numOfSequenceParameterSets = data[5] & 0x1f,
32845          numOfPictureParameterSets,
32846          nalSize,
32847          offset,
32848          i; // iterate past any SPSs
32849
32850      offset = 6;
32851
32852      for (i = 0; i < numOfSequenceParameterSets; i++) {
32853        nalSize = view.getUint16(offset);
32854        offset += 2;
32855        result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
32856        offset += nalSize;
32857      } // iterate past any PPSs
32858
32859
32860      numOfPictureParameterSets = data[offset];
32861      offset++;
32862
32863      for (i = 0; i < numOfPictureParameterSets; i++) {
32864        nalSize = view.getUint16(offset);
32865        offset += 2;
32866        result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
32867        offset += nalSize;
32868      }
32869
32870      return result;
32871    },
32872    btrt: function btrt(data) {
32873      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
32874      return {
32875        bufferSizeDB: view.getUint32(0),
32876        maxBitrate: view.getUint32(4),
32877        avgBitrate: view.getUint32(8)
32878      };
32879    },
32880    esds: function esds(data) {
32881      return {
32882        version: data[0],
32883        flags: new Uint8Array(data.subarray(1, 4)),
32884        esId: data[6] << 8 | data[7],
32885        streamPriority: data[8] & 0x1f,
32886        decoderConfig: {
32887          objectProfileIndication: data[11],
32888          streamType: data[12] >>> 2 & 0x3f,
32889          bufferSize: data[13] << 16 | data[14] << 8 | data[15],
32890          maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
32891          avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
32892          decoderConfigDescriptor: {
32893            tag: data[24],
32894            length: data[25],
32895            audioObjectType: data[26] >>> 3 & 0x1f,
32896            samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
32897            channelConfiguration: data[27] >>> 3 & 0x0f
32898          }
32899        }
32900      };
32901    },
32902    ftyp: function ftyp(data) {
32903      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
32904          result = {
32905        majorBrand: parseType(data.subarray(0, 4)),
32906        minorVersion: view.getUint32(4),
32907        compatibleBrands: []
32908      },
32909          i = 8;
32910
32911      while (i < data.byteLength) {
32912        result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
32913        i += 4;
32914      }
32915
32916      return result;
32917    },
32918    dinf: function dinf(data) {
32919      return {
32920        boxes: inspectMp4(data)
32921      };
32922    },
32923    dref: function dref(data) {
32924      return {
32925        version: data[0],
32926        flags: new Uint8Array(data.subarray(1, 4)),
32927        dataReferences: inspectMp4(data.subarray(8))
32928      };
32929    },
32930    hdlr: function hdlr(data) {
32931      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
32932          result = {
32933        version: view.getUint8(0),
32934        flags: new Uint8Array(data.subarray(1, 4)),
32935        handlerType: parseType(data.subarray(8, 12)),
32936        name: ''
32937      },
32938          i = 8; // parse out the name field
32939
32940      for (i = 24; i < data.byteLength; i++) {
32941        if (data[i] === 0x00) {
32942          // the name field is null-terminated
32943          i++;
32944          break;
32945        }
32946
32947        result.name += String.fromCharCode(data[i]);
32948      } // decode UTF-8 to javascript's internal representation
32949      // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
32950
32951
32952      result.name = decodeURIComponent(escape(result.name));
32953      return result;
32954    },
32955    mdat: function mdat(data) {
32956      return {
32957        byteLength: data.byteLength,
32958        nals: nalParse(data)
32959      };
32960    },
32961    mdhd: function mdhd(data) {
32962      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
32963          i = 4,
32964          language,
32965          result = {
32966        version: view.getUint8(0),
32967        flags: new Uint8Array(data.subarray(1, 4)),
32968        language: ''
32969      };
32970
32971      if (result.version === 1) {
32972        i += 4;
32973        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
32974
32975        i += 8;
32976        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
32977
32978        i += 4;
32979        result.timescale = view.getUint32(i);
32980        i += 8;
32981        result.duration = view.getUint32(i); // truncating top 4 bytes
32982      } else {
32983        result.creationTime = parseMp4Date(view.getUint32(i));
32984        i += 4;
32985        result.modificationTime = parseMp4Date(view.getUint32(i));
32986        i += 4;
32987        result.timescale = view.getUint32(i);
32988        i += 4;
32989        result.duration = view.getUint32(i);
32990      }
32991
32992      i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
32993      // each field is the packed difference between its ASCII value and 0x60
32994
32995      language = view.getUint16(i);
32996      result.language += String.fromCharCode((language >> 10) + 0x60);
32997      result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
32998      result.language += String.fromCharCode((language & 0x1f) + 0x60);
32999      return result;
33000    },
33001    mdia: function mdia(data) {
33002      return {
33003        boxes: inspectMp4(data)
33004      };
33005    },
33006    mfhd: function mfhd(data) {
33007      return {
33008        version: data[0],
33009        flags: new Uint8Array(data.subarray(1, 4)),
33010        sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
33011      };
33012    },
33013    minf: function minf(data) {
33014      return {
33015        boxes: inspectMp4(data)
33016      };
33017    },
33018    // codingname, not a first-class box type. stsd entries share the
vendor: 25,421 bytes, lines 33019-33778
33019    // same format as real boxes so the parsing infrastructure can be
33020    // shared
33021    mp4a: function mp4a(data) {
33022      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33023          result = {
33024        // 6 bytes reserved
33025        dataReferenceIndex: view.getUint16(6),
33026        // 4 + 4 bytes reserved
33027        channelcount: view.getUint16(16),
33028        samplesize: view.getUint16(18),
33029        // 2 bytes pre_defined
33030        // 2 bytes reserved
33031        samplerate: view.getUint16(24) + view.getUint16(26) / 65536
33032      }; // if there are more bytes to process, assume this is an ISO/IEC
33033      // 14496-14 MP4AudioSampleEntry and parse the ESDBox
33034
33035      if (data.byteLength > 28) {
33036        result.streamDescriptor = inspectMp4(data.subarray(28))[0];
33037      }
33038
33039      return result;
33040    },
33041    moof: function moof(data) {
33042      return {
33043        boxes: inspectMp4(data)
33044      };
33045    },
33046    moov: function moov(data) {
33047      return {
33048        boxes: inspectMp4(data)
33049      };
33050    },
33051    mvex: function mvex(data) {
33052      return {
33053        boxes: inspectMp4(data)
33054      };
33055    },
33056    mvhd: function mvhd(data) {
33057      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33058          i = 4,
33059          result = {
33060        version: view.getUint8(0),
33061        flags: new Uint8Array(data.subarray(1, 4))
33062      };
33063
33064      if (result.version === 1) {
33065        i += 4;
33066        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
33067
33068        i += 8;
33069        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
33070
33071        i += 4;
33072        result.timescale = view.getUint32(i);
33073        i += 8;
33074        result.duration = view.getUint32(i); // truncating top 4 bytes
33075      } else {
33076        result.creationTime = parseMp4Date(view.getUint32(i));
33077        i += 4;
33078        result.modificationTime = parseMp4Date(view.getUint32(i));
33079        i += 4;
33080        result.timescale = view.getUint32(i);
33081        i += 4;
33082        result.duration = view.getUint32(i);
33083      }
33084
33085      i += 4; // convert fixed-point, base 16 back to a number
33086
33087      result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
33088      i += 4;
33089      result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
33090      i += 2;
33091      i += 2;
33092      i += 2 * 4;
33093      result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
33094      i += 9 * 4;
33095      i += 6 * 4;
33096      result.nextTrackId = view.getUint32(i);
33097      return result;
33098    },
33099    pdin: function pdin(data) {
33100      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
33101      return {
33102        version: view.getUint8(0),
33103        flags: new Uint8Array(data.subarray(1, 4)),
33104        rate: view.getUint32(4),
33105        initialDelay: view.getUint32(8)
33106      };
33107    },
33108    sdtp: function sdtp(data) {
33109      var result = {
33110        version: data[0],
33111        flags: new Uint8Array(data.subarray(1, 4)),
33112        samples: []
33113      },
33114          i;
33115
33116      for (i = 4; i < data.byteLength; i++) {
33117        result.samples.push({
33118          dependsOn: (data[i] & 0x30) >> 4,
33119          isDependedOn: (data[i] & 0x0c) >> 2,
33120          hasRedundancy: data[i] & 0x03
33121        });
33122      }
33123
33124      return result;
33125    },
33126    sidx: function sidx(data) {
33127      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33128          result = {
33129        version: data[0],
33130        flags: new Uint8Array(data.subarray(1, 4)),
33131        references: [],
33132        referenceId: view.getUint32(4),
33133        timescale: view.getUint32(8),
33134        earliestPresentationTime: view.getUint32(12),
33135        firstOffset: view.getUint32(16)
33136      },
33137          referenceCount = view.getUint16(22),
33138          i;
33139
33140      for (i = 24; referenceCount; i += 12, referenceCount--) {
33141        result.references.push({
33142          referenceType: (data[i] & 0x80) >>> 7,
33143          referencedSize: view.getUint32(i) & 0x7FFFFFFF,
33144          subsegmentDuration: view.getUint32(i + 4),
33145          startsWithSap: !!(data[i + 8] & 0x80),
33146          sapType: (data[i + 8] & 0x70) >>> 4,
33147          sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
33148        });
33149      }
33150
33151      return result;
33152    },
33153    smhd: function smhd(data) {
33154      return {
33155        version: data[0],
33156        flags: new Uint8Array(data.subarray(1, 4)),
33157        balance: data[4] + data[5] / 256
33158      };
33159    },
33160    stbl: function stbl(data) {
33161      return {
33162        boxes: inspectMp4(data)
33163      };
33164    },
33165    stco: function stco(data) {
33166      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33167          result = {
33168        version: data[0],
33169        flags: new Uint8Array(data.subarray(1, 4)),
33170        chunkOffsets: []
33171      },
33172          entryCount = view.getUint32(4),
33173          i;
33174
33175      for (i = 8; entryCount; i += 4, entryCount--) {
33176        result.chunkOffsets.push(view.getUint32(i));
33177      }
33178
33179      return result;
33180    },
33181    stsc: function stsc(data) {
33182      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33183          entryCount = view.getUint32(4),
33184          result = {
33185        version: data[0],
33186        flags: new Uint8Array(data.subarray(1, 4)),
33187        sampleToChunks: []
33188      },
33189          i;
33190
33191      for (i = 8; entryCount; i += 12, entryCount--) {
33192        result.sampleToChunks.push({
33193          firstChunk: view.getUint32(i),
33194          samplesPerChunk: view.getUint32(i + 4),
33195          sampleDescriptionIndex: view.getUint32(i + 8)
33196        });
33197      }
33198
33199      return result;
33200    },
33201    stsd: function stsd(data) {
33202      return {
33203        version: data[0],
33204        flags: new Uint8Array(data.subarray(1, 4)),
33205        sampleDescriptions: inspectMp4(data.subarray(8))
33206      };
33207    },
33208    stsz: function stsz(data) {
33209      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33210          result = {
33211        version: data[0],
33212        flags: new Uint8Array(data.subarray(1, 4)),
33213        sampleSize: view.getUint32(4),
33214        entries: []
33215      },
33216          i;
33217
33218      for (i = 12; i < data.byteLength; i += 4) {
33219        result.entries.push(view.getUint32(i));
33220      }
33221
33222      return result;
33223    },
33224    stts: function stts(data) {
33225      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33226          result = {
33227        version: data[0],
33228        flags: new Uint8Array(data.subarray(1, 4)),
33229        timeToSamples: []
33230      },
33231          entryCount = view.getUint32(4),
33232          i;
33233
33234      for (i = 8; entryCount; i += 8, entryCount--) {
33235        result.timeToSamples.push({
33236          sampleCount: view.getUint32(i),
33237          sampleDelta: view.getUint32(i + 4)
33238        });
33239      }
33240
33241      return result;
33242    },
33243    styp: function styp(data) {
33244      return parse$1.ftyp(data);
33245    },
33246    tfdt: function tfdt(data) {
33247      var result = {
33248        version: data[0],
33249        flags: new Uint8Array(data.subarray(1, 4)),
33250        baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
33251      };
33252
33253      if (result.version === 1) {
33254        result.baseMediaDecodeTime *= Math.pow(2, 32);
33255        result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
33256      }
33257
33258      return result;
33259    },
33260    tfhd: function tfhd(data) {
33261      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33262          result = {
33263        version: data[0],
33264        flags: new Uint8Array(data.subarray(1, 4)),
33265        trackId: view.getUint32(4)
33266      },
33267          baseDataOffsetPresent = result.flags[2] & 0x01,
33268          sampleDescriptionIndexPresent = result.flags[2] & 0x02,
33269          defaultSampleDurationPresent = result.flags[2] & 0x08,
33270          defaultSampleSizePresent = result.flags[2] & 0x10,
33271          defaultSampleFlagsPresent = result.flags[2] & 0x20,
33272          durationIsEmpty = result.flags[0] & 0x010000,
33273          defaultBaseIsMoof = result.flags[0] & 0x020000,
33274          i;
33275      i = 8;
33276
33277      if (baseDataOffsetPresent) {
33278        i += 4; // truncate top 4 bytes
33279        // FIXME: should we read the full 64 bits?
33280
33281        result.baseDataOffset = view.getUint32(12);
33282        i += 4;
33283      }
33284
33285      if (sampleDescriptionIndexPresent) {
33286        result.sampleDescriptionIndex = view.getUint32(i);
33287        i += 4;
33288      }
33289
33290      if (defaultSampleDurationPresent) {
33291        result.defaultSampleDuration = view.getUint32(i);
33292        i += 4;
33293      }
33294
33295      if (defaultSampleSizePresent) {
33296        result.defaultSampleSize = view.getUint32(i);
33297        i += 4;
33298      }
33299
33300      if (defaultSampleFlagsPresent) {
33301        result.defaultSampleFlags = view.getUint32(i);
33302      }
33303
33304      if (durationIsEmpty) {
33305        result.durationIsEmpty = true;
33306      }
33307
33308      if (!baseDataOffsetPresent && defaultBaseIsMoof) {
33309        result.baseDataOffsetIsMoof = true;
33310      }
33311
33312      return result;
33313    },
33314    tkhd: function tkhd(data) {
33315      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33316          i = 4,
33317          result = {
33318        version: view.getUint8(0),
33319        flags: new Uint8Array(data.subarray(1, 4))
33320      };
33321
33322      if (result.version === 1) {
33323        i += 4;
33324        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
33325
33326        i += 8;
33327        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
33328
33329        i += 4;
33330        result.trackId = view.getUint32(i);
33331        i += 4;
33332        i += 8;
33333        result.duration = view.getUint32(i); // truncating top 4 bytes
33334      } else {
33335        result.creationTime = parseMp4Date(view.getUint32(i));
33336        i += 4;
33337        result.modificationTime = parseMp4Date(view.getUint32(i));
33338        i += 4;
33339        result.trackId = view.getUint32(i);
33340        i += 4;
33341        i += 4;
33342        result.duration = view.getUint32(i);
33343      }
33344
33345      i += 4;
33346      i += 2 * 4;
33347      result.layer = view.getUint16(i);
33348      i += 2;
33349      result.alternateGroup = view.getUint16(i);
33350      i += 2; // convert fixed-point, base 16 back to a number
33351
33352      result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
33353      i += 2;
33354      i += 2;
33355      result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
33356      i += 9 * 4;
33357      result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
33358      i += 4;
33359      result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
33360      return result;
33361    },
33362    traf: function traf(data) {
33363      return {
33364        boxes: inspectMp4(data)
33365      };
33366    },
33367    trak: function trak(data) {
33368      return {
33369        boxes: inspectMp4(data)
33370      };
33371    },
33372    trex: function trex(data) {
33373      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
33374      return {
33375        version: data[0],
33376        flags: new Uint8Array(data.subarray(1, 4)),
33377        trackId: view.getUint32(4),
33378        defaultSampleDescriptionIndex: view.getUint32(8),
33379        defaultSampleDuration: view.getUint32(12),
33380        defaultSampleSize: view.getUint32(16),
33381        sampleDependsOn: data[20] & 0x03,
33382        sampleIsDependedOn: (data[21] & 0xc0) >> 6,
33383        sampleHasRedundancy: (data[21] & 0x30) >> 4,
33384        samplePaddingValue: (data[21] & 0x0e) >> 1,
33385        sampleIsDifferenceSample: !!(data[21] & 0x01),
33386        sampleDegradationPriority: view.getUint16(22)
33387      };
33388    },
33389    trun: function trun(data) {
33390      var result = {
33391        version: data[0],
33392        flags: new Uint8Array(data.subarray(1, 4)),
33393        samples: []
33394      },
33395          view = new DataView(data.buffer, data.byteOffset, data.byteLength),
33396          // Flag interpretation
33397      dataOffsetPresent = result.flags[2] & 0x01,
33398          // compare with 2nd byte of 0x1
33399      firstSampleFlagsPresent = result.flags[2] & 0x04,
33400          // compare with 2nd byte of 0x4
33401      sampleDurationPresent = result.flags[1] & 0x01,
33402          // compare with 2nd byte of 0x100
33403      sampleSizePresent = result.flags[1] & 0x02,
33404          // compare with 2nd byte of 0x200
33405      sampleFlagsPresent = result.flags[1] & 0x04,
33406          // compare with 2nd byte of 0x400
33407      sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
33408          // compare with 2nd byte of 0x800
33409      sampleCount = view.getUint32(4),
33410          offset = 8,
33411          sample;
33412
33413      if (dataOffsetPresent) {
33414        // 32 bit signed integer
33415        result.dataOffset = view.getInt32(offset);
33416        offset += 4;
33417      } // Overrides the flags for the first sample only. The order of
33418      // optional values will be: duration, size, compositionTimeOffset
33419
33420
33421      if (firstSampleFlagsPresent && sampleCount) {
33422        sample = {
33423          flags: parseSampleFlags(data.subarray(offset, offset + 4))
33424        };
33425        offset += 4;
33426
33427        if (sampleDurationPresent) {
33428          sample.duration = view.getUint32(offset);
33429          offset += 4;
33430        }
33431
33432        if (sampleSizePresent) {
33433          sample.size = view.getUint32(offset);
33434          offset += 4;
33435        }
33436
33437        if (sampleCompositionTimeOffsetPresent) {
33438          // Note: this should be a signed int if version is 1
33439          sample.compositionTimeOffset = view.getUint32(offset);
33440          offset += 4;
33441        }
33442
33443        result.samples.push(sample);
33444        sampleCount--;
33445      }
33446
33447      while (sampleCount--) {
33448        sample = {};
33449
33450        if (sampleDurationPresent) {
33451          sample.duration = view.getUint32(offset);
33452          offset += 4;
33453        }
33454
33455        if (sampleSizePresent) {
33456          sample.size = view.getUint32(offset);
33457          offset += 4;
33458        }
33459
33460        if (sampleFlagsPresent) {
33461          sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
33462          offset += 4;
33463        }
33464
33465        if (sampleCompositionTimeOffsetPresent) {
33466          // Note: this should be a signed int if version is 1
33467          sample.compositionTimeOffset = view.getUint32(offset);
33468          offset += 4;
33469        }
33470
33471        result.samples.push(sample);
33472      }
33473
33474      return result;
33475    },
33476    'url ': function url(data) {
33477      return {
33478        version: data[0],
33479        flags: new Uint8Array(data.subarray(1, 4))
33480      };
33481    },
33482    vmhd: function vmhd(data) {
33483      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
33484      return {
33485        version: data[0],
33486        flags: new Uint8Array(data.subarray(1, 4)),
33487        graphicsmode: view.getUint16(4),
33488        opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
33489      };
33490    }
33491  };
33492  /**
33493   * Return a javascript array of box objects parsed from an ISO base
33494   * media file.
33495   * @param data {Uint8Array} the binary data of the media to be inspected
33496   * @return {array} a javascript array of potentially nested box objects
33497   */
33498
33499
33500  inspectMp4 = function inspectMp4(data) {
33501    var i = 0,
33502        result = [],
33503        view,
33504        size,
33505        type,
33506        end,
33507        box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
33508
33509    var ab = new ArrayBuffer(data.length);
33510    var v = new Uint8Array(ab);
33511
33512    for (var z = 0; z < data.length; ++z) {
33513      v[z] = data[z];
33514    }
33515
33516    view = new DataView(ab);
33517
33518    while (i < data.byteLength) {
33519      // parse box data
33520      size = view.getUint32(i);
33521      type = parseType(data.subarray(i + 4, i + 8));
33522      end = size > 1 ? i + size : data.byteLength; // parse type-specific data
33523
33524      box = (parse$1[type] || function (data) {
33525        return {
33526          data: data
33527        };
33528      })(data.subarray(i + 8, end));
33529
33530      box.size = size;
33531      box.type = type; // store this box and move to the next
33532
33533      result.push(box);
33534      i = end;
33535    }
33536
33537    return result;
33538  };
33539  /**
33540   * Returns a textual representation of the javascript represtentation
33541   * of an MP4 file. You can use it as an alternative to
33542   * JSON.stringify() to compare inspected MP4s.
33543   * @param inspectedMp4 {array} the parsed array of boxes in an MP4
33544   * file
33545   * @param depth {number} (optional) the number of ancestor boxes of
33546   * the elements of inspectedMp4. Assumed to be zero if unspecified.
33547   * @return {string} a text representation of the parsed MP4
33548   */
33549
33550
33551  _textifyMp = function textifyMp4(inspectedMp4, depth) {
33552    var indent;
33553    depth = depth || 0;
33554    indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
33555
33556    return inspectedMp4.map(function (box, index) {
33557      // list the box type first at the current indentation level
33558      return indent + box.type + '\n' + // the type is already included and handle child boxes separately
33559      Object.keys(box).filter(function (key) {
33560        return key !== 'type' && key !== 'boxes'; // output all the box properties
33561      }).map(function (key) {
33562        var prefix = indent + '  ' + key + ': ',
33563            value = box[key]; // print out raw bytes as hexademical
33564
33565        if (value instanceof Uint8Array || value instanceof Uint32Array) {
33566          var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) {
33567            return ' ' + ('00' + _byte.toString(16)).slice(-2);
33568          }).join('').match(/.{1,24}/g);
33569
33570          if (!bytes) {
33571            return prefix + '<>';
33572          }
33573
33574          if (bytes.length === 1) {
33575            return prefix + '<' + bytes.join('').slice(1) + '>';
33576          }
33577
33578          return prefix + '<\n' + bytes.map(function (line) {
33579            return indent + '  ' + line;
33580          }).join('\n') + '\n' + indent + '  >';
33581        } // stringify generic objects
33582
33583
33584        return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
33585          if (index === 0) {
33586            return line;
33587          }
33588
33589          return indent + '  ' + line;
33590        }).join('\n');
33591      }).join('\n') + ( // recursively textify the child boxes
33592      box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
33593    }).join('\n');
33594  };
33595
33596  var mp4Inspector = {
33597    inspect: inspectMp4,
33598    textify: _textifyMp,
33599    parseType: parseType,
33600    findBox: findBox,
33601    parseTraf: parse$1.traf,
33602    parseTfdt: parse$1.tfdt,
33603    parseHdlr: parse$1.hdlr,
33604    parseTfhd: parse$1.tfhd,
33605    parseTrun: parse$1.trun,
33606    parseSidx: parse$1.sidx
33607  };
33608
33609  var toUnsigned$2 = bin.toUnsigned;
33610  var toHexString$1 = bin.toHexString;
33611  var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks;
33612  /**
33613   * Parses an MP4 initialization segment and extracts the timescale
33614   * values for any declared tracks. Timescale values indicate the
33615   * number of clock ticks per second to assume for time-based values
33616   * elsewhere in the MP4.
33617   *
33618   * To determine the start time of an MP4, you need two pieces of
33619   * information: the timescale unit and the earliest base media decode
33620   * time. Multiple timescales can be specified within an MP4 but the
33621   * base media decode time is always expressed in the timescale from
33622   * the media header box for the track:
33623   * ```
33624   * moov > trak > mdia > mdhd.timescale
33625   * ```
33626   * @param init {Uint8Array} the bytes of the init segment
33627   * @return {object} a hash of track ids to timescale values or null if
33628   * the init segment is malformed.
33629   */
33630
33631  timescale = function timescale(init) {
33632    var result = {},
33633        traks = mp4Inspector.findBox(init, ['moov', 'trak']); // mdhd timescale
33634
33635    return traks.reduce(function (result, trak) {
33636      var tkhd, version, index, id, mdhd;
33637      tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0];
33638
33639      if (!tkhd) {
33640        return null;
33641      }
33642
33643      version = tkhd[0];
33644      index = version === 0 ? 12 : 20;
33645      id = toUnsigned$2(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
33646      mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0];
33647
33648      if (!mdhd) {
33649        return null;
33650      }
33651
33652      version = mdhd[0];
33653      index = version === 0 ? 12 : 20;
33654      result[id] = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
33655      return result;
33656    }, result);
33657  };
33658  /**
33659   * Determine the base media decode start time, in seconds, for an MP4
33660   * fragment. If multiple fragments are specified, the earliest time is
33661   * returned.
33662   *
33663   * The base media decode time can be parsed from track fragment
33664   * metadata:
33665   * ```
33666   * moof > traf > tfdt.baseMediaDecodeTime
33667   * ```
33668   * It requires the timescale value from the mdhd to interpret.
33669   *
33670   * @param timescale {object} a hash of track ids to timescale values.
33671   * @return {number} the earliest base media decode start time for the
33672   * fragment, in seconds
33673   */
33674
33675
33676  startTime = function startTime(timescale, fragment) {
33677    var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
33678
33679    trafs = mp4Inspector.findBox(fragment, ['moof', 'traf']); // determine the start times for each track
33680
33681    baseTimes = [].concat.apply([], trafs.map(function (traf) {
33682      return mp4Inspector.findBox(traf, ['tfhd']).map(function (tfhd) {
33683        var id, scale, baseTime; // get the track id from the tfhd
33684
33685        id = toUnsigned$2(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
33686
33687        scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
33688
33689        baseTime = mp4Inspector.findBox(traf, ['tfdt']).map(function (tfdt) {
33690          var version, result;
33691          version = tfdt[0];
33692          result = toUnsigned$2(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
33693
33694          if (version === 1) {
33695            result *= Math.pow(2, 32);
33696            result += toUnsigned$2(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
33697          }
33698
33699          return result;
33700        })[0];
33701        baseTime = baseTime || Infinity; // convert base time to seconds
33702
33703        return baseTime / scale;
33704      });
33705    })); // return the minimum
33706
33707    result = Math.min.apply(null, baseTimes);
33708    return isFinite(result) ? result : 0;
33709  };
33710  /**
33711   * Determine the composition start, in seconds, for an MP4
33712   * fragment.
33713   *
33714   * The composition start time of a fragment can be calculated using the base
33715   * media decode time, composition time offset, and timescale, as follows:
33716   *
33717   * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
33718   *
33719   * All of the aforementioned information is contained within a media fragment's
33720   * `traf` box, except for timescale info, which comes from the initialization
33721   * segment, so a track id (also contained within a `traf`) is also necessary to
33722   * associate it with a timescale
33723   *
33724   *
33725   * @param timescales {object} - a hash of track ids to timescale values.
33726   * @param fragment {Unit8Array} - the bytes of a media segment
33727   * @return {number} the composition start time for the fragment, in seconds
33728   **/
33729
33730
33731  compositionStartTime = function compositionStartTime(timescales, fragment) {
33732    var trafBoxes = mp4Inspector.findBox(fragment, ['moof', 'traf']);
33733    var baseMediaDecodeTime = 0;
33734    var compositionTimeOffset = 0;
33735    var trackId;
33736
33737    if (trafBoxes && trafBoxes.length) {
33738      // The spec states that track run samples contained within a `traf` box are contiguous, but
33739      // it does not explicitly state whether the `traf` boxes themselves are contiguous.
33740      // We will assume that they are, so we only need the first to calculate start time.
33741      var parsedTraf = mp4Inspector.parseTraf(trafBoxes[0]);
33742
33743      for (var i = 0; i < parsedTraf.boxes.length; i++) {
33744        if (parsedTraf.boxes[i].type === 'tfhd') {
33745          trackId = parsedTraf.boxes[i].trackId;
33746        } else if (parsedTraf.boxes[i].type === 'tfdt') {
33747          baseMediaDecodeTime = parsedTraf.boxes[i].baseMediaDecodeTime;
33748        } else if (parsedTraf.boxes[i].type === 'trun' && parsedTraf.boxes[i].samples.length) {
33749          compositionTimeOffset = parsedTraf.boxes[i].samples[0].compositionTimeOffset || 0;
33750        }
33751      }
33752    } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
33753    // specified.
33754
33755
33756    var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
33757
33758    return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
33759  };
33760  /**
33761    * Find the trackIds of the video tracks in this source.
33762    * Found by parsing the Handler Reference and Track Header Boxes:
33763    *   moov > trak > mdia > hdlr
33764    *   moov > trak > tkhd
33765    *
33766    * @param {Uint8Array} init - The bytes of the init segment for this source
33767    * @return {Number[]} A list of trackIds
33768    *
33769    * @see ISO-BMFF-12/2015, Section 8.4.3
33770   **/
33771
33772
33773  getVideoTrackIds = function getVideoTrackIds(init) {
33774    var traks = mp4Inspector.findBox(init, ['moov', 'trak']);
33775    var videoTrackIds = [];
33776    traks.forEach(function (trak) {
33777      var hdlrs = mp4Inspector.findBox(trak, ['mdia', 'hdlr']);
33778      var tkhds = mp4Inspector.findBox(trak, ['tkhd']);
vendor: 13,289 bytes, lines 33779-34197
33779      hdlrs.forEach(function (hdlr, index) {
33780        var handlerType = mp4Inspector.parseType(hdlr.subarray(8, 12));
33781        var tkhd = tkhds[index];
33782        var view;
33783        var version;
33784        var trackId;
33785
33786        if (handlerType === 'vide') {
33787          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
33788          version = view.getUint8(0);
33789          trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
33790          videoTrackIds.push(trackId);
33791        }
33792      });
33793    });
33794    return videoTrackIds;
33795  };
33796  /**
33797   * Get all the video, audio, and hint tracks from a non fragmented
33798   * mp4 segment
33799   */
33800
33801
33802  getTracks = function getTracks(init) {
33803    var traks = mp4Inspector.findBox(init, ['moov', 'trak']);
33804    var tracks = [];
33805    traks.forEach(function (trak) {
33806      var track = {};
33807      var tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0];
33808      var view, version; // id
33809
33810      if (tkhd) {
33811        view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
33812        version = view.getUint8(0);
33813        track.id = version === 0 ? view.getUint32(12) : view.getUint32(20);
33814      }
33815
33816      var hdlr = mp4Inspector.findBox(trak, ['mdia', 'hdlr'])[0]; // type
33817
33818      if (hdlr) {
33819        var type = mp4Inspector.parseType(hdlr.subarray(8, 12));
33820
33821        if (type === 'vide') {
33822          track.type = 'video';
33823        } else if (type === 'soun') {
33824          track.type = 'audio';
33825        } else {
33826          track.type = type;
33827        }
33828      } // codec
33829
33830
33831      var stsd = mp4Inspector.findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
33832
33833      if (stsd) {
33834        var sampleDescriptions = stsd.subarray(8); // gives the codec type string
33835
33836        track.codec = mp4Inspector.parseType(sampleDescriptions.subarray(4, 8));
33837        var codecBox = mp4Inspector.findBox(sampleDescriptions, [track.codec])[0];
33838        var codecConfig, codecConfigType;
33839
33840        if (codecBox) {
33841          // https://tools.ietf.org/html/rfc6381#section-3.3
33842          if (/^[a-z]vc[1-9]$/i.test(track.codec)) {
33843            // we don't need anything but the "config" parameter of the
33844            // avc1 codecBox
33845            codecConfig = codecBox.subarray(78);
33846            codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8));
33847
33848            if (codecConfigType === 'avcC' && codecConfig.length > 11) {
33849              track.codec += '.'; // left padded with zeroes for single digit hex
33850              // profile idc
33851
33852              track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags
33853
33854              track.codec += toHexString$1(codecConfig[10]); // level idc
33855
33856              track.codec += toHexString$1(codecConfig[11]);
33857            } else {
33858              // TODO: show a warning that we couldn't parse the codec
33859              // and are using the default
33860              track.codec = 'avc1.4d400d';
33861            }
33862          } else if (/^mp4[a,v]$/i.test(track.codec)) {
33863            // we do not need anything but the streamDescriptor of the mp4a codecBox
33864            codecConfig = codecBox.subarray(28);
33865            codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8));
33866
33867            if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
33868              track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit
33869
33870              track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
33871            } else {
33872              // TODO: show a warning that we couldn't parse the codec
33873              // and are using the default
33874              track.codec = 'mp4a.40.2';
33875            }
33876          }
33877        }
33878      }
33879
33880      var mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0];
33881
33882      if (mdhd && tkhd) {
33883        var index = version === 0 ? 12 : 20;
33884        track.timescale = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
33885      }
33886
33887      tracks.push(track);
33888    });
33889    return tracks;
33890  };
33891
33892  var probe = {
33893    // export mp4 inspector's findBox and parseType for backwards compatibility
33894    findBox: mp4Inspector.findBox,
33895    parseType: mp4Inspector.parseType,
33896    timescale: timescale,
33897    startTime: startTime,
33898    compositionStartTime: compositionStartTime,
33899    videoTrackIds: getVideoTrackIds,
33900    tracks: getTracks
33901  };
33902
33903  /**
33904   * mux.js
33905   *
33906   * Copyright (c) Brightcove
33907   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
33908   *
33909   * Reads in-band caption information from a video elementary
33910   * stream. Captions must follow the CEA-708 standard for injection
33911   * into an MPEG-2 transport streams.
33912   * @see https://en.wikipedia.org/wiki/CEA-708
33913   * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
33914   */
33915  // payload type field to indicate how they are to be
33916  // interpreted. CEAS-708 caption content is always transmitted with
33917  // payload type 0x04.
33918
33919  var USER_DATA_REGISTERED_ITU_T_T35 = 4,
33920      RBSP_TRAILING_BITS = 128;
33921  /**
33922    * Parse a supplemental enhancement information (SEI) NAL unit.
33923    * Stops parsing once a message of type ITU T T35 has been found.
33924    *
33925    * @param bytes {Uint8Array} the bytes of a SEI NAL unit
33926    * @return {object} the parsed SEI payload
33927    * @see Rec. ITU-T H.264, 7.3.2.3.1
33928    */
33929
33930  var parseSei = function parseSei(bytes) {
33931    var i = 0,
33932        result = {
33933      payloadType: -1,
33934      payloadSize: 0
33935    },
33936        payloadType = 0,
33937        payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
33938
33939    while (i < bytes.byteLength) {
33940      // stop once we have hit the end of the sei_rbsp
33941      if (bytes[i] === RBSP_TRAILING_BITS) {
33942        break;
33943      } // Parse payload type
33944
33945
33946      while (bytes[i] === 0xFF) {
33947        payloadType += 255;
33948        i++;
33949      }
33950
33951      payloadType += bytes[i++]; // Parse payload size
33952
33953      while (bytes[i] === 0xFF) {
33954        payloadSize += 255;
33955        i++;
33956      }
33957
33958      payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
33959      // there can only ever be one caption message in a frame's sei
33960
33961      if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
33962        result.payloadType = payloadType;
33963        result.payloadSize = payloadSize;
33964        result.payload = bytes.subarray(i, i + payloadSize);
33965        break;
33966      } // skip the payload and parse the next message
33967
33968
33969      i += payloadSize;
33970      payloadType = 0;
33971      payloadSize = 0;
33972    }
33973
33974    return result;
33975  }; // see ANSI/SCTE 128-1 (2013), section 8.1
33976
33977
33978  var parseUserData = function parseUserData(sei) {
33979    // itu_t_t35_contry_code must be 181 (United States) for
33980    // captions
33981    if (sei.payload[0] !== 181) {
33982      return null;
33983    } // itu_t_t35_provider_code should be 49 (ATSC) for captions
33984
33985
33986    if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
33987      return null;
33988    } // the user_identifier should be "GA94" to indicate ATSC1 data
33989
33990
33991    if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
33992      return null;
33993    } // finally, user_data_type_code should be 0x03 for caption data
33994
33995
33996    if (sei.payload[7] !== 0x03) {
33997      return null;
33998    } // return the user_data_type_structure and strip the trailing
33999    // marker bits
34000
34001
34002    return sei.payload.subarray(8, sei.payload.length - 1);
34003  }; // see CEA-708-D, section 4.4
34004
34005
34006  var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
34007    var results = [],
34008        i,
34009        count,
34010        offset,
34011        data; // if this is just filler, return immediately
34012
34013    if (!(userData[0] & 0x40)) {
34014      return results;
34015    } // parse out the cc_data_1 and cc_data_2 fields
34016
34017
34018    count = userData[0] & 0x1f;
34019
34020    for (i = 0; i < count; i++) {
34021      offset = i * 3;
34022      data = {
34023        type: userData[offset + 2] & 0x03,
34024        pts: pts
34025      }; // capture cc data when cc_valid is 1
34026
34027      if (userData[offset + 2] & 0x04) {
34028        data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
34029        results.push(data);
34030      }
34031    }
34032
34033    return results;
34034  };
34035
34036  var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
34037    var length = data.byteLength,
34038        emulationPreventionBytesPositions = [],
34039        i = 1,
34040        newLength,
34041        newData; // Find all `Emulation Prevention Bytes`
34042
34043    while (i < length - 2) {
34044      if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
34045        emulationPreventionBytesPositions.push(i + 2);
34046        i += 2;
34047      } else {
34048        i++;
34049      }
34050    } // If no Emulation Prevention Bytes were found just return the original
34051    // array
34052
34053
34054    if (emulationPreventionBytesPositions.length === 0) {
34055      return data;
34056    } // Create a new array to hold the NAL unit data
34057
34058
34059    newLength = length - emulationPreventionBytesPositions.length;
34060    newData = new Uint8Array(newLength);
34061    var sourceIndex = 0;
34062
34063    for (i = 0; i < newLength; sourceIndex++, i++) {
34064      if (sourceIndex === emulationPreventionBytesPositions[0]) {
34065        // Skip this byte
34066        sourceIndex++; // Remove this position index
34067
34068        emulationPreventionBytesPositions.shift();
34069      }
34070
34071      newData[i] = data[sourceIndex];
34072    }
34073
34074    return newData;
34075  }; // exports
34076
34077
34078  var captionPacketParser = {
34079    parseSei: parseSei,
34080    parseUserData: parseUserData,
34081    parseCaptionPackets: parseCaptionPackets,
34082    discardEmulationPreventionBytes: discardEmulationPreventionBytes,
34083    USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
34084  };
34085
34086  /**
34087   * mux.js
34088   *
34089   * Copyright (c) Brightcove
34090   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
34091   *
34092   * A lightweight readable stream implemention that handles event dispatching.
34093   * Objects that inherit from streams should call init in their constructors.
34094   */
34095
34096  var Stream$1 = function Stream() {
34097    this.init = function () {
34098      var listeners = {};
34099      /**
34100       * Add a listener for a specified event type.
34101       * @param type {string} the event name
34102       * @param listener {function} the callback to be invoked when an event of
34103       * the specified type occurs
34104       */
34105
34106      this.on = function (type, listener) {
34107        if (!listeners[type]) {
34108          listeners[type] = [];
34109        }
34110
34111        listeners[type] = listeners[type].concat(listener);
34112      };
34113      /**
34114       * Remove a listener for a specified event type.
34115       * @param type {string} the event name
34116       * @param listener {function} a function previously registered for this
34117       * type of event through `on`
34118       */
34119
34120
34121      this.off = function (type, listener) {
34122        var index;
34123
34124        if (!listeners[type]) {
34125          return false;
34126        }
34127
34128        index = listeners[type].indexOf(listener);
34129        listeners[type] = listeners[type].slice();
34130        listeners[type].splice(index, 1);
34131        return index > -1;
34132      };
34133      /**
34134       * Trigger an event of the specified type on this stream. Any additional
34135       * arguments to this function are passed as parameters to event listeners.
34136       * @param type {string} the event name
34137       */
34138
34139
34140      this.trigger = function (type) {
34141        var callbacks, i, length, args;
34142        callbacks = listeners[type];
34143
34144        if (!callbacks) {
34145          return;
34146        } // Slicing the arguments on every invocation of this method
34147        // can add a significant amount of overhead. Avoid the
34148        // intermediate object creation for the common case of a
34149        // single callback argument
34150
34151
34152        if (arguments.length === 2) {
34153          length = callbacks.length;
34154
34155          for (i = 0; i < length; ++i) {
34156            callbacks[i].call(this, arguments[1]);
34157          }
34158        } else {
34159          args = [];
34160          i = arguments.length;
34161
34162          for (i = 1; i < arguments.length; ++i) {
34163            args.push(arguments[i]);
34164          }
34165
34166          length = callbacks.length;
34167
34168          for (i = 0; i < length; ++i) {
34169            callbacks[i].apply(this, args);
34170          }
34171        }
34172      };
34173      /**
34174       * Destroys the stream and cleans up.
34175       */
34176
34177
34178      this.dispose = function () {
34179        listeners = {};
34180      };
34181    };
34182  };
34183  /**
34184   * Forwards all `data` events on this stream to the destination stream. The
34185   * destination stream should provide a method `push` to receive the data
34186   * events as they arrive.
34187   * @param destination {stream} the stream that will receive all `data` events
34188   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
34189   *                            when the current stream emits a 'done' event
34190   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
34191   */
34192
34193
34194  Stream$1.prototype.pipe = function (destination) {
34195    this.on('data', function (data) {
34196      destination.push(data);
34197    });
vendor: 4,187 bytes, lines 34198-34317
34198    this.on('done', function (flushSource) {
34199      destination.flush(flushSource);
34200    });
34201    this.on('partialdone', function (flushSource) {
34202      destination.partialFlush(flushSource);
34203    });
34204    this.on('endedtimeline', function (flushSource) {
34205      destination.endTimeline(flushSource);
34206    });
34207    this.on('reset', function (flushSource) {
34208      destination.reset(flushSource);
34209    });
34210    return destination;
34211  }; // Default stream functions that are expected to be overridden to perform
34212  // actual work. These are provided by the prototype as a sort of no-op
34213  // implementation so that we don't have to check for their existence in the
34214  // `pipe` function above.
34215
34216
34217  Stream$1.prototype.push = function (data) {
34218    this.trigger('data', data);
34219  };
34220
34221  Stream$1.prototype.flush = function (flushSource) {
34222    this.trigger('done', flushSource);
34223  };
34224
34225  Stream$1.prototype.partialFlush = function (flushSource) {
34226    this.trigger('partialdone', flushSource);
34227  };
34228
34229  Stream$1.prototype.endTimeline = function (flushSource) {
34230    this.trigger('endedtimeline', flushSource);
34231  };
34232
34233  Stream$1.prototype.reset = function (flushSource) {
34234    this.trigger('reset', flushSource);
34235  };
34236
34237  var stream = Stream$1;
34238
34239  // Link To Transport
34240  // -----------------
34241
34242
34243  var CaptionStream = function CaptionStream() {
34244    CaptionStream.prototype.init.call(this);
34245    this.captionPackets_ = [];
34246    this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
34247    new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
34248    new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
34249    new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
34250    ];
34251    this.reset(); // forward data and done events from CCs to this CaptionStream
34252
34253    this.ccStreams_.forEach(function (cc) {
34254      cc.on('data', this.trigger.bind(this, 'data'));
34255      cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
34256      cc.on('done', this.trigger.bind(this, 'done'));
34257    }, this);
34258  };
34259
34260  CaptionStream.prototype = new stream();
34261
34262  CaptionStream.prototype.push = function (event) {
34263    var sei, userData, newCaptionPackets; // only examine SEI NALs
34264
34265    if (event.nalUnitType !== 'sei_rbsp') {
34266      return;
34267    } // parse the sei
34268
34269
34270    sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
34271
34272    if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
34273      return;
34274    } // parse out the user data payload
34275
34276
34277    userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
34278
34279    if (!userData) {
34280      return;
34281    } // Sometimes, the same segment # will be downloaded twice. To stop the
34282    // caption data from being processed twice, we track the latest dts we've
34283    // received and ignore everything with a dts before that. However, since
34284    // data for a specific dts can be split across packets on either side of
34285    // a segment boundary, we need to make sure we *don't* ignore the packets
34286    // from the *next* segment that have dts === this.latestDts_. By constantly
34287    // tracking the number of packets received with dts === this.latestDts_, we
34288    // know how many should be ignored once we start receiving duplicates.
34289
34290
34291    if (event.dts < this.latestDts_) {
34292      // We've started getting older data, so set the flag.
34293      this.ignoreNextEqualDts_ = true;
34294      return;
34295    } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
34296      this.numSameDts_--;
34297
34298      if (!this.numSameDts_) {
34299        // We've received the last duplicate packet, time to start processing again
34300        this.ignoreNextEqualDts_ = false;
34301      }
34302
34303      return;
34304    } // parse out CC data packets and save them for later
34305
34306
34307    newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
34308    this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
34309
34310    if (this.latestDts_ !== event.dts) {
34311      this.numSameDts_ = 0;
34312    }
34313
34314    this.numSameDts_++;
34315    this.latestDts_ = event.dts;
34316  };
34317
vendor: 4,788 bytes, lines 34318-34451
34318  CaptionStream.prototype.flushCCStreams = function (flushType) {
34319    this.ccStreams_.forEach(function (cc) {
34320      return flushType === 'flush' ? cc.flush() : cc.partialFlush();
34321    }, this);
34322  };
34323
34324  CaptionStream.prototype.flushStream = function (flushType) {
34325    // make sure we actually parsed captions before proceeding
34326    if (!this.captionPackets_.length) {
34327      this.flushCCStreams(flushType);
34328      return;
34329    } // In Chrome, the Array#sort function is not stable so add a
34330    // presortIndex that we can use to ensure we get a stable-sort
34331
34332
34333    this.captionPackets_.forEach(function (elem, idx) {
34334      elem.presortIndex = idx;
34335    }); // sort caption byte-pairs based on their PTS values
34336
34337    this.captionPackets_.sort(function (a, b) {
34338      if (a.pts === b.pts) {
34339        return a.presortIndex - b.presortIndex;
34340      }
34341
34342      return a.pts - b.pts;
34343    });
34344    this.captionPackets_.forEach(function (packet) {
34345      if (packet.type < 2) {
34346        // Dispatch packet to the right Cea608Stream
34347        this.dispatchCea608Packet(packet);
34348      } // this is where an 'else' would go for a dispatching packets
34349      // to a theoretical Cea708Stream that handles SERVICEn data
34350
34351    }, this);
34352    this.captionPackets_.length = 0;
34353    this.flushCCStreams(flushType);
34354  };
34355
34356  CaptionStream.prototype.flush = function () {
34357    return this.flushStream('flush');
34358  }; // Only called if handling partial data
34359
34360
34361  CaptionStream.prototype.partialFlush = function () {
34362    return this.flushStream('partialFlush');
34363  };
34364
34365  CaptionStream.prototype.reset = function () {
34366    this.latestDts_ = null;
34367    this.ignoreNextEqualDts_ = false;
34368    this.numSameDts_ = 0;
34369    this.activeCea608Channel_ = [null, null];
34370    this.ccStreams_.forEach(function (ccStream) {
34371      ccStream.reset();
34372    });
34373  }; // From the CEA-608 spec:
34374
34375  /*
34376   * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
34377   * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
34378   * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
34379   * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
34380   * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
34381   * to switch to captioning or Text.
34382  */
34383  // With that in mind, we ignore any data between an XDS control code and a
34384  // subsequent closed-captioning control code.
34385
34386
34387  CaptionStream.prototype.dispatchCea608Packet = function (packet) {
34388    // NOTE: packet.type is the CEA608 field
34389    if (this.setsTextOrXDSActive(packet)) {
34390      this.activeCea608Channel_[packet.type] = null;
34391    } else if (this.setsChannel1Active(packet)) {
34392      this.activeCea608Channel_[packet.type] = 0;
34393    } else if (this.setsChannel2Active(packet)) {
34394      this.activeCea608Channel_[packet.type] = 1;
34395    }
34396
34397    if (this.activeCea608Channel_[packet.type] === null) {
34398      // If we haven't received anything to set the active channel, or the
34399      // packets are Text/XDS data, discard the data; we don't want jumbled
34400      // captions
34401      return;
34402    }
34403
34404    this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
34405  };
34406
34407  CaptionStream.prototype.setsChannel1Active = function (packet) {
34408    return (packet.ccData & 0x7800) === 0x1000;
34409  };
34410
34411  CaptionStream.prototype.setsChannel2Active = function (packet) {
34412    return (packet.ccData & 0x7800) === 0x1800;
34413  };
34414
34415  CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
34416    return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
34417  }; // ----------------------
34418  // Session to Application
34419  // ----------------------
34420  // This hash maps non-ASCII, special, and extended character codes to their
34421  // proper Unicode equivalent. The first keys that are only a single byte
34422  // are the non-standard ASCII characters, which simply map the CEA608 byte
34423  // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
34424  // character codes, but have their MSB bitmasked with 0x03 so that a lookup
34425  // can be performed regardless of the field and data channel on which the
34426  // character code was received.
34427
34428
34429  var CHARACTER_TRANSLATION = {
34430    0x2a: 0xe1,
34431    // á
34432    0x5c: 0xe9,
34433    // é
34434    0x5e: 0xed,
34435    // í
34436    0x5f: 0xf3,
34437    // ó
34438    0x60: 0xfa,
34439    // ú
34440    0x7b: 0xe7,
34441    // ç
34442    0x7c: 0xf7,
34443    // ÷
34444    0x7d: 0xd1,
34445    // Ñ
34446    0x7e: 0xf1,
34447    // ñ
34448    0x7f: 0x2588,
34449    // █
34450    0x0130: 0xae,
34451    // ®
34452    0x0131: 0xb0,
34453    // °
34454    0x0132: 0xbd,
34455    // ½
34456    0x0133: 0xbf,
34457    // ¿
34458    0x0134: 0x2122,
34459    // ™
34460    0x0135: 0xa2,
34461    // ¢
34462    0x0136: 0xa3,
34463    // £
34464    0x0137: 0x266a,
34465    // ♪
34466    0x0138: 0xe0,
34467    // à
34468    0x0139: 0xa0,
34469    //
34470    0x013a: 0xe8,
34471    // è
34472    0x013b: 0xe2,
34473    // â
34474    0x013c: 0xea,
34475    // ê
34476    0x013d: 0xee,
34477    // î
34478    0x013e: 0xf4,
34479    // ô
34480    0x013f: 0xfb,
34481    // û
34482    0x0220: 0xc1,
34483    // Á
34484    0x0221: 0xc9,
34485    // É
34486    0x0222: 0xd3,
34487    // Ó
34488    0x0223: 0xda,
34489    // Ú
34490    0x0224: 0xdc,
34491    // Ü
34492    0x0225: 0xfc,
34493    // ü
34494    0x0226: 0x2018,
34495    // ‘
34496    0x0227: 0xa1,
34497    // ¡
34498    0x0228: 0x2a,
34499    // *
34500    0x0229: 0x27,
34501    // '
34502    0x022a: 0x2014,
34503    // —
34504    0x022b: 0xa9,
34505    // ©
34506    0x022c: 0x2120,
34507    // ℠
34508    0x022d: 0x2022,
34509    // •
34510    0x022e: 0x201c,
34511    // “
34512    0x022f: 0x201d,
34513    // ”
34514    0x0230: 0xc0,
34515    // À
34516    0x0231: 0xc2,
34517    // Â
34518    0x0232: 0xc7,
34519    // Ç
34520    0x0233: 0xc8,
34521    // È
34522    0x0234: 0xca,
34523    // Ê
34524    0x0235: 0xcb,
34525    // Ë
34526    0x0236: 0xeb,
34527    // ë
34528    0x0237: 0xce,
34529    // Î
34530    0x0238: 0xcf,
34531    // Ï
34532    0x0239: 0xef,
34533    // ï
34534    0x023a: 0xd4,
34535    // Ô
34536    0x023b: 0xd9,
34537    // Ù
34538    0x023c: 0xf9,
34539    // ù
34540    0x023d: 0xdb,
34541    // Û
34542    0x023e: 0xab,
34543    // «
34544    0x023f: 0xbb,
34545    // »
34546    0x0320: 0xc3,
34547    // Ã
34548    0x0321: 0xe3,
34549    // ã
34550    0x0322: 0xcd,
34551    // Í
34552    0x0323: 0xcc,
34553    // Ì
34554    0x0324: 0xec,
34555    // ì
34556    0x0325: 0xd2,
34557    // Ò
34558    0x0326: 0xf2,
34559    // ò
34560    0x0327: 0xd5,
34561    // Õ
34562    0x0328: 0xf5,
34563    // õ
34564    0x0329: 0x7b,
34565    // {
34566    0x032a: 0x7d,
34567    // }
34568    0x032b: 0x5c,
34569    // \
34570    0x032c: 0x5e,
34571    // ^
34572    0x032d: 0x5f,
34573    // _
34574    0x032e: 0x7c,
34575    // |
34576    0x032f: 0x7e,
34577    // ~
34578    0x0330: 0xc4,
34579    // Ä
34580    0x0331: 0xe4,
vendor: 18,474 bytes, lines 34581-35080
34581    // ä
34582    0x0332: 0xd6,
34583    // Ö
34584    0x0333: 0xf6,
34585    // ö
34586    0x0334: 0xdf,
34587    // ß
34588    0x0335: 0xa5,
34589    // ¥
34590    0x0336: 0xa4,
34591    // ¤
34592    0x0337: 0x2502,
34593    // │
34594    0x0338: 0xc5,
34595    // Å
34596    0x0339: 0xe5,
34597    // å
34598    0x033a: 0xd8,
34599    // Ø
34600    0x033b: 0xf8,
34601    // ø
34602    0x033c: 0x250c,
34603    // ┌
34604    0x033d: 0x2510,
34605    // ┐
34606    0x033e: 0x2514,
34607    // └
34608    0x033f: 0x2518 // ┘
34609
34610  };
34611
34612  var getCharFromCode = function getCharFromCode(code) {
34613    if (code === null) {
34614      return '';
34615    }
34616
34617    code = CHARACTER_TRANSLATION[code] || code;
34618    return String.fromCharCode(code);
34619  }; // the index of the last row in a CEA-608 display buffer
34620
34621
34622  var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
34623  // getting it through bit logic.
34624
34625  var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
34626  // cells. The "bottom" row is the last element in the outer array.
34627
34628  var createDisplayBuffer = function createDisplayBuffer() {
34629    var result = [],
34630        i = BOTTOM_ROW + 1;
34631
34632    while (i--) {
34633      result.push('');
34634    }
34635
34636    return result;
34637  };
34638
34639  var Cea608Stream = function Cea608Stream(field, dataChannel) {
34640    Cea608Stream.prototype.init.call(this);
34641    this.field_ = field || 0;
34642    this.dataChannel_ = dataChannel || 0;
34643    this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
34644    this.setConstants();
34645    this.reset();
34646
34647    this.push = function (packet) {
34648      var data, swap, char0, char1, text; // remove the parity bits
34649
34650      data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
34651
34652      if (data === this.lastControlCode_) {
34653        this.lastControlCode_ = null;
34654        return;
34655      } // Store control codes
34656
34657
34658      if ((data & 0xf000) === 0x1000) {
34659        this.lastControlCode_ = data;
34660      } else if (data !== this.PADDING_) {
34661        this.lastControlCode_ = null;
34662      }
34663
34664      char0 = data >>> 8;
34665      char1 = data & 0xff;
34666
34667      if (data === this.PADDING_) {
34668        return;
34669      } else if (data === this.RESUME_CAPTION_LOADING_) {
34670        this.mode_ = 'popOn';
34671      } else if (data === this.END_OF_CAPTION_) {
34672        // If an EOC is received while in paint-on mode, the displayed caption
34673        // text should be swapped to non-displayed memory as if it was a pop-on
34674        // caption. Because of that, we should explicitly switch back to pop-on
34675        // mode
34676        this.mode_ = 'popOn';
34677        this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
34678
34679        this.flushDisplayed(packet.pts); // flip memory
34680
34681        swap = this.displayed_;
34682        this.displayed_ = this.nonDisplayed_;
34683        this.nonDisplayed_ = swap; // start measuring the time to display the caption
34684
34685        this.startPts_ = packet.pts;
34686      } else if (data === this.ROLL_UP_2_ROWS_) {
34687        this.rollUpRows_ = 2;
34688        this.setRollUp(packet.pts);
34689      } else if (data === this.ROLL_UP_3_ROWS_) {
34690        this.rollUpRows_ = 3;
34691        this.setRollUp(packet.pts);
34692      } else if (data === this.ROLL_UP_4_ROWS_) {
34693        this.rollUpRows_ = 4;
34694        this.setRollUp(packet.pts);
34695      } else if (data === this.CARRIAGE_RETURN_) {
34696        this.clearFormatting(packet.pts);
34697        this.flushDisplayed(packet.pts);
34698        this.shiftRowsUp_();
34699        this.startPts_ = packet.pts;
34700      } else if (data === this.BACKSPACE_) {
34701        if (this.mode_ === 'popOn') {
34702          this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
34703        } else {
34704          this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
34705        }
34706      } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
34707        this.flushDisplayed(packet.pts);
34708        this.displayed_ = createDisplayBuffer();
34709      } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
34710        this.nonDisplayed_ = createDisplayBuffer();
34711      } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
34712        if (this.mode_ !== 'paintOn') {
34713          // NOTE: This should be removed when proper caption positioning is
34714          // implemented
34715          this.flushDisplayed(packet.pts);
34716          this.displayed_ = createDisplayBuffer();
34717        }
34718
34719        this.mode_ = 'paintOn';
34720        this.startPts_ = packet.pts; // Append special characters to caption text
34721      } else if (this.isSpecialCharacter(char0, char1)) {
34722        // Bitmask char0 so that we can apply character transformations
34723        // regardless of field and data channel.
34724        // Then byte-shift to the left and OR with char1 so we can pass the
34725        // entire character code to `getCharFromCode`.
34726        char0 = (char0 & 0x03) << 8;
34727        text = getCharFromCode(char0 | char1);
34728        this[this.mode_](packet.pts, text);
34729        this.column_++; // Append extended characters to caption text
34730      } else if (this.isExtCharacter(char0, char1)) {
34731        // Extended characters always follow their "non-extended" equivalents.
34732        // IE if a "è" is desired, you'll always receive "eè"; non-compliant
34733        // decoders are supposed to drop the "è", while compliant decoders
34734        // backspace the "e" and insert "è".
34735        // Delete the previous character
34736        if (this.mode_ === 'popOn') {
34737          this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
34738        } else {
34739          this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
34740        } // Bitmask char0 so that we can apply character transformations
34741        // regardless of field and data channel.
34742        // Then byte-shift to the left and OR with char1 so we can pass the
34743        // entire character code to `getCharFromCode`.
34744
34745
34746        char0 = (char0 & 0x03) << 8;
34747        text = getCharFromCode(char0 | char1);
34748        this[this.mode_](packet.pts, text);
34749        this.column_++; // Process mid-row codes
34750      } else if (this.isMidRowCode(char0, char1)) {
34751        // Attributes are not additive, so clear all formatting
34752        this.clearFormatting(packet.pts); // According to the standard, mid-row codes
34753        // should be replaced with spaces, so add one now
34754
34755        this[this.mode_](packet.pts, ' ');
34756        this.column_++;
34757
34758        if ((char1 & 0xe) === 0xe) {
34759          this.addFormatting(packet.pts, ['i']);
34760        }
34761
34762        if ((char1 & 0x1) === 0x1) {
34763          this.addFormatting(packet.pts, ['u']);
34764        } // Detect offset control codes and adjust cursor
34765
34766      } else if (this.isOffsetControlCode(char0, char1)) {
34767        // Cursor position is set by indent PAC (see below) in 4-column
34768        // increments, with an additional offset code of 1-3 to reach any
34769        // of the 32 columns specified by CEA-608. So all we need to do
34770        // here is increment the column cursor by the given offset.
34771        this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
34772      } else if (this.isPAC(char0, char1)) {
34773        // There's no logic for PAC -> row mapping, so we have to just
34774        // find the row code in an array and use its index :(
34775        var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
34776
34777        if (this.mode_ === 'rollUp') {
34778          // This implies that the base row is incorrectly set.
34779          // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
34780          // of roll-up rows set.
34781          if (row - this.rollUpRows_ + 1 < 0) {
34782            row = this.rollUpRows_ - 1;
34783          }
34784
34785          this.setRollUp(packet.pts, row);
34786        }
34787
34788        if (row !== this.row_) {
34789          // formatting is only persistent for current row
34790          this.clearFormatting(packet.pts);
34791          this.row_ = row;
34792        } // All PACs can apply underline, so detect and apply
34793        // (All odd-numbered second bytes set underline)
34794
34795
34796        if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
34797          this.addFormatting(packet.pts, ['u']);
34798        }
34799
34800        if ((data & 0x10) === 0x10) {
34801          // We've got an indent level code. Each successive even number
34802          // increments the column cursor by 4, so we can get the desired
34803          // column position by bit-shifting to the right (to get n/2)
34804          // and multiplying by 4.
34805          this.column_ = ((data & 0xe) >> 1) * 4;
34806        }
34807
34808        if (this.isColorPAC(char1)) {
34809          // it's a color code, though we only support white, which
34810          // can be either normal or italicized. white italics can be
34811          // either 0x4e or 0x6e depending on the row, so we just
34812          // bitwise-and with 0xe to see if italics should be turned on
34813          if ((char1 & 0xe) === 0xe) {
34814            this.addFormatting(packet.pts, ['i']);
34815          }
34816        } // We have a normal character in char0, and possibly one in char1
34817
34818      } else if (this.isNormalChar(char0)) {
34819        if (char1 === 0x00) {
34820          char1 = null;
34821        }
34822
34823        text = getCharFromCode(char0);
34824        text += getCharFromCode(char1);
34825        this[this.mode_](packet.pts, text);
34826        this.column_ += text.length;
34827      } // finish data processing
34828
34829    };
34830  };
34831
34832  Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
34833  // display buffer
34834
34835  Cea608Stream.prototype.flushDisplayed = function (pts) {
34836    var content = this.displayed_ // remove spaces from the start and end of the string
34837    .map(function (row) {
34838      try {
34839        return row.trim();
34840      } catch (e) {
34841        // Ordinarily, this shouldn't happen. However, caption
34842        // parsing errors should not throw exceptions and
34843        // break playback.
34844        // eslint-disable-next-line no-console
34845        console.error('Skipping malformed caption.');
34846        return '';
34847      }
34848    }) // combine all text rows to display in one cue
34849    .join('\n') // and remove blank rows from the start and end, but not the middle
34850    .replace(/^\n+|\n+$/g, '');
34851
34852    if (content.length) {
34853      this.trigger('data', {
34854        startPts: this.startPts_,
34855        endPts: pts,
34856        text: content,
34857        stream: this.name_
34858      });
34859    }
34860  };
34861  /**
34862   * Zero out the data, used for startup and on seek
34863   */
34864
34865
34866  Cea608Stream.prototype.reset = function () {
34867    this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
34868    // actually display captions. If a caption is shifted to a row
34869    // with a lower index than this, it is cleared from the display
34870    // buffer
34871
34872    this.topRow_ = 0;
34873    this.startPts_ = 0;
34874    this.displayed_ = createDisplayBuffer();
34875    this.nonDisplayed_ = createDisplayBuffer();
34876    this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
34877
34878    this.column_ = 0;
34879    this.row_ = BOTTOM_ROW;
34880    this.rollUpRows_ = 2; // This variable holds currently-applied formatting
34881
34882    this.formatting_ = [];
34883  };
34884  /**
34885   * Sets up control code and related constants for this instance
34886   */
34887
34888
34889  Cea608Stream.prototype.setConstants = function () {
34890    // The following attributes have these uses:
34891    // ext_ :    char0 for mid-row codes, and the base for extended
34892    //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
34893    //           extended codes)
34894    // control_: char0 for control codes, except byte-shifted to the
34895    //           left so that we can do this.control_ | CONTROL_CODE
34896    // offset_:  char0 for tab offset codes
34897    //
34898    // It's also worth noting that control codes, and _only_ control codes,
34899    // differ between field 1 and field2. Field 2 control codes are always
34900    // their field 1 value plus 1. That's why there's the "| field" on the
34901    // control value.
34902    if (this.dataChannel_ === 0) {
34903      this.BASE_ = 0x10;
34904      this.EXT_ = 0x11;
34905      this.CONTROL_ = (0x14 | this.field_) << 8;
34906      this.OFFSET_ = 0x17;
34907    } else if (this.dataChannel_ === 1) {
34908      this.BASE_ = 0x18;
34909      this.EXT_ = 0x19;
34910      this.CONTROL_ = (0x1c | this.field_) << 8;
34911      this.OFFSET_ = 0x1f;
34912    } // Constants for the LSByte command codes recognized by Cea608Stream. This
34913    // list is not exhaustive. For a more comprehensive listing and semantics see
34914    // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
34915    // Padding
34916
34917
34918    this.PADDING_ = 0x0000; // Pop-on Mode
34919
34920    this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
34921    this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
34922
34923    this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
34924    this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
34925    this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
34926    this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
34927
34928    this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
34929
34930    this.BACKSPACE_ = this.CONTROL_ | 0x21;
34931    this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
34932    this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
34933  };
34934  /**
34935   * Detects if the 2-byte packet data is a special character
34936   *
34937   * Special characters have a second byte in the range 0x30 to 0x3f,
34938   * with the first byte being 0x11 (for data channel 1) or 0x19 (for
34939   * data channel 2).
34940   *
34941   * @param  {Integer} char0 The first byte
34942   * @param  {Integer} char1 The second byte
34943   * @return {Boolean}       Whether the 2 bytes are an special character
34944   */
34945
34946
34947  Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
34948    return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
34949  };
34950  /**
34951   * Detects if the 2-byte packet data is an extended character
34952   *
34953   * Extended characters have a second byte in the range 0x20 to 0x3f,
34954   * with the first byte being 0x12 or 0x13 (for data channel 1) or
34955   * 0x1a or 0x1b (for data channel 2).
34956   *
34957   * @param  {Integer} char0 The first byte
34958   * @param  {Integer} char1 The second byte
34959   * @return {Boolean}       Whether the 2 bytes are an extended character
34960   */
34961
34962
34963  Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
34964    return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
34965  };
34966  /**
34967   * Detects if the 2-byte packet is a mid-row code
34968   *
34969   * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
34970   * the first byte being 0x11 (for data channel 1) or 0x19 (for data
34971   * channel 2).
34972   *
34973   * @param  {Integer} char0 The first byte
34974   * @param  {Integer} char1 The second byte
34975   * @return {Boolean}       Whether the 2 bytes are a mid-row code
34976   */
34977
34978
34979  Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
34980    return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
34981  };
34982  /**
34983   * Detects if the 2-byte packet is an offset control code
34984   *
34985   * Offset control codes have a second byte in the range 0x21 to 0x23,
34986   * with the first byte being 0x17 (for data channel 1) or 0x1f (for
34987   * data channel 2).
34988   *
34989   * @param  {Integer} char0 The first byte
34990   * @param  {Integer} char1 The second byte
34991   * @return {Boolean}       Whether the 2 bytes are an offset control code
34992   */
34993
34994
34995  Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
34996    return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
34997  };
34998  /**
34999   * Detects if the 2-byte packet is a Preamble Address Code
35000   *
35001   * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
35002   * or 0x18 to 0x1f (for data channel 2), with the second byte in the
35003   * range 0x40 to 0x7f.
35004   *
35005   * @param  {Integer} char0 The first byte
35006   * @param  {Integer} char1 The second byte
35007   * @return {Boolean}       Whether the 2 bytes are a PAC
35008   */
35009
35010
35011  Cea608Stream.prototype.isPAC = function (char0, char1) {
35012    return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
35013  };
35014  /**
35015   * Detects if a packet's second byte is in the range of a PAC color code
35016   *
35017   * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
35018   * 0x60 to 0x6f.
35019   *
35020   * @param  {Integer} char1 The second byte
35021   * @return {Boolean}       Whether the byte is a color PAC
35022   */
35023
35024
35025  Cea608Stream.prototype.isColorPAC = function (char1) {
35026    return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
35027  };
35028  /**
35029   * Detects if a single byte is in the range of a normal character
35030   *
35031   * Normal text bytes are in the range 0x20 to 0x7f.
35032   *
35033   * @param  {Integer} char  The byte
35034   * @return {Boolean}       Whether the byte is a normal character
35035   */
35036
35037
35038  Cea608Stream.prototype.isNormalChar = function (_char) {
35039    return _char >= 0x20 && _char <= 0x7f;
35040  };
35041  /**
35042   * Configures roll-up
35043   *
35044   * @param  {Integer} pts         Current PTS
35045   * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
35046   *                               a new position
35047   */
35048
35049
35050  Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
35051    // Reset the base row to the bottom row when switching modes
35052    if (this.mode_ !== 'rollUp') {
35053      this.row_ = BOTTOM_ROW;
35054      this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
35055
35056      this.flushDisplayed(pts);
35057      this.nonDisplayed_ = createDisplayBuffer();
35058      this.displayed_ = createDisplayBuffer();
35059    }
35060
35061    if (newBaseRow !== undefined && newBaseRow !== this.row_) {
35062      // move currently displayed captions (up or down) to the new base row
35063      for (var i = 0; i < this.rollUpRows_; i++) {
35064        this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
35065        this.displayed_[this.row_ - i] = '';
35066      }
35067    }
35068
35069    if (newBaseRow === undefined) {
35070      newBaseRow = this.row_;
35071    }
35072
35073    this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
35074  }; // Adds the opening HTML tag for the passed character to the caption text,
35075  // and keeps track of it for later closing
35076
35077
35078  Cea608Stream.prototype.addFormatting = function (pts, format) {
35079    this.formatting_ = this.formatting_.concat(format);
35080    var text = format.reduce(function (text, format) {
vendor: 2,997 bytes, lines 35081-35181
35081      return text + '<' + format + '>';
35082    }, '');
35083    this[this.mode_](pts, text);
35084  }; // Adds HTML closing tags for current formatting to caption text and
35085  // clears remembered formatting
35086
35087
35088  Cea608Stream.prototype.clearFormatting = function (pts) {
35089    if (!this.formatting_.length) {
35090      return;
35091    }
35092
35093    var text = this.formatting_.reverse().reduce(function (text, format) {
35094      return text + '</' + format + '>';
35095    }, '');
35096    this.formatting_ = [];
35097    this[this.mode_](pts, text);
35098  }; // Mode Implementations
35099
35100
35101  Cea608Stream.prototype.popOn = function (pts, text) {
35102    var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
35103
35104    baseRow += text;
35105    this.nonDisplayed_[this.row_] = baseRow;
35106  };
35107
35108  Cea608Stream.prototype.rollUp = function (pts, text) {
35109    var baseRow = this.displayed_[this.row_];
35110    baseRow += text;
35111    this.displayed_[this.row_] = baseRow;
35112  };
35113
35114  Cea608Stream.prototype.shiftRowsUp_ = function () {
35115    var i; // clear out inactive rows
35116
35117    for (i = 0; i < this.topRow_; i++) {
35118      this.displayed_[i] = '';
35119    }
35120
35121    for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
35122      this.displayed_[i] = '';
35123    } // shift displayed rows up
35124
35125
35126    for (i = this.topRow_; i < this.row_; i++) {
35127      this.displayed_[i] = this.displayed_[i + 1];
35128    } // clear out the bottom row
35129
35130
35131    this.displayed_[this.row_] = '';
35132  };
35133
35134  Cea608Stream.prototype.paintOn = function (pts, text) {
35135    var baseRow = this.displayed_[this.row_];
35136    baseRow += text;
35137    this.displayed_[this.row_] = baseRow;
35138  }; // exports
35139
35140
35141  var captionStream = {
35142    CaptionStream: CaptionStream,
35143    Cea608Stream: Cea608Stream
35144  };
35145
35146  var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
35147  var CaptionStream$1 = captionStream.CaptionStream;
35148  /**
35149    * Maps an offset in the mdat to a sample based on the the size of the samples.
35150    * Assumes that `parseSamples` has been called first.
35151    *
35152    * @param {Number} offset - The offset into the mdat
35153    * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
35154    * @return {?Object} The matching sample, or null if no match was found.
35155    *
35156    * @see ISO-BMFF-12/2015, Section 8.8.8
35157   **/
35158
35159  var mapToSample = function mapToSample(offset, samples) {
35160    var approximateOffset = offset;
35161
35162    for (var i = 0; i < samples.length; i++) {
35163      var sample = samples[i];
35164
35165      if (approximateOffset < sample.size) {
35166        return sample;
35167      }
35168
35169      approximateOffset -= sample.size;
35170    }
35171
35172    return null;
35173  };
35174  /**
35175    * Finds SEI nal units contained in a Media Data Box.
35176    * Assumes that `parseSamples` has been called first.
35177    *
35178    * @param {Uint8Array} avcStream - The bytes of the mdat
35179    * @param {Object[]} samples - The samples parsed out by `parseSamples`
35180    * @param {Number} trackId - The trackId of this video track
35181    * @return {Object[]}
vendor: 8,070 bytes, lines 35181-35409
35181 seiNals - the parsed SEI NALUs found.
35182    *   The contents of the seiNal should match what is expected by
35183    *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
35184    *
35185    * @see ISO-BMFF-12/2015, Section 8.1.1
35186    * @see Rec. ITU-T H.264, 7.3.2.3.1
35187   **/
35188
35189
35190  var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
35191    var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
35192        result = [],
35193        seiNal,
35194        i,
35195        length,
35196        lastMatchedSample;
35197
35198    for (i = 0; i + 4 < avcStream.length; i += length) {
35199      length = avcView.getUint32(i);
35200      i += 4; // Bail if this doesn't appear to be an H264 stream
35201
35202      if (length <= 0) {
35203        continue;
35204      }
35205
35206      switch (avcStream[i] & 0x1F) {
35207        case 0x06:
35208          var data = avcStream.subarray(i + 1, i + 1 + length);
35209          var matchingSample = mapToSample(i, samples);
35210          seiNal = {
35211            nalUnitType: 'sei_rbsp',
35212            size: length,
35213            data: data,
35214            escapedRBSP: discardEmulationPreventionBytes$1(data),
35215            trackId: trackId
35216          };
35217
35218          if (matchingSample) {
35219            seiNal.pts = matchingSample.pts;
35220            seiNal.dts = matchingSample.dts;
35221            lastMatchedSample = matchingSample;
35222          } else {
35223            // If a matching sample cannot be found, use the last
35224            // sample's values as they should be as close as possible
35225            seiNal.pts = lastMatchedSample.pts;
35226            seiNal.dts = lastMatchedSample.dts;
35227          }
35228
35229          result.push(seiNal);
35230          break;
35231
35232        default:
35233          break;
35234      }
35235    }
35236
35237    return result;
35238  };
35239  /**
35240    * Parses sample information out of Track Run Boxes and calculates
35241    * the absolute presentation and decode timestamps of each sample.
35242    *
35243    * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
35244    * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
35245        @see ISO-BMFF-12/2015, Section 8.8.12
35246    * @param {Object} tfhd - The parsed Track Fragment Header
35247    *   @see inspect.parseTfhd
35248    * @return {Object[]} the parsed samples
35249    *
35250    * @see ISO-BMFF-12/2015, Section 8.8.8
35251   **/
35252
35253
35254  var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
35255    var currentDts = baseMediaDecodeTime;
35256    var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
35257    var defaultSampleSize = tfhd.defaultSampleSize || 0;
35258    var trackId = tfhd.trackId;
35259    var allSamples = [];
35260    truns.forEach(function (trun) {
35261      // Note: We currently do not parse the sample table as well
35262      // as the trun. It's possible some sources will require this.
35263      // moov > trak > mdia > minf > stbl
35264      var trackRun = mp4Inspector.parseTrun(trun);
35265      var samples = trackRun.samples;
35266      samples.forEach(function (sample) {
35267        if (sample.duration === undefined) {
35268          sample.duration = defaultSampleDuration;
35269        }
35270
35271        if (sample.size === undefined) {
35272          sample.size = defaultSampleSize;
35273        }
35274
35275        sample.trackId = trackId;
35276        sample.dts = currentDts;
35277
35278        if (sample.compositionTimeOffset === undefined) {
35279          sample.compositionTimeOffset = 0;
35280        }
35281
35282        sample.pts = currentDts + sample.compositionTimeOffset;
35283        currentDts += sample.duration;
35284      });
35285      allSamples = allSamples.concat(samples);
35286    });
35287    return allSamples;
35288  };
35289  /**
35290    * Parses out caption nals from an FMP4 segment's video tracks.
35291    *
35292    * @param {Uint8Array} segment - The bytes of a single segment
35293    * @param {Number} videoTrackId - The trackId of a video track in the segment
35294    * @return {Object.<Number, Object[]>} A mapping of video trackId to
35295    *   a list of seiNals found in that track
35296   **/
35297
35298
35299  var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
35300    // To get the samples
35301    var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
35302
35303    var mdats = probe.findBox(segment, ['mdat']);
35304    var captionNals = {};
35305    var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
35306
35307    mdats.forEach(function (mdat, index) {
35308      var matchingTraf = trafs[index];
35309      mdatTrafPairs.push({
35310        mdat: mdat,
35311        traf: matchingTraf
35312      });
35313    });
35314    mdatTrafPairs.forEach(function (pair) {
35315      var mdat = pair.mdat;
35316      var traf = pair.traf;
35317      var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
35318
35319      var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
35320      var trackId = headerInfo.trackId;
35321      var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
35322
35323      var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
35324      var truns = probe.findBox(traf, ['trun']);
35325      var samples;
35326      var seiNals; // Only parse video data for the chosen video track
35327
35328      if (videoTrackId === trackId && truns.length > 0) {
35329        samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
35330        seiNals = findSeiNals(mdat, samples, trackId);
35331
35332        if (!captionNals[trackId]) {
35333          captionNals[trackId] = [];
35334        }
35335
35336        captionNals[trackId] = captionNals[trackId].concat(seiNals);
35337      }
35338    });
35339    return captionNals;
35340  };
35341  /**
35342    * Parses out inband captions from an MP4 container and returns
35343    * caption objects that can be used by WebVTT and the TextTrack API.
35344    * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
35345    * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
35346    * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
35347    *
35348    * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
35349    * @param {Number} trackId - The id of the video track to parse
35350    * @param {Number} timescale - The timescale for the video track from the init segment
35351    *
35352    * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
35353    * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
35354    * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
35355    * @return {String} parsedCaptions[].text - The visible content of the caption
35356   **/
35357
35358
35359  var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
35360    var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
35361
35362    if (trackId === null) {
35363      return null;
35364    }
35365
35366    seiNals = parseCaptionNals(segment, trackId);
35367    return {
35368      seiNals: seiNals[trackId],
35369      timescale: timescale
35370    };
35371  };
35372  /**
35373    * Converts SEI NALUs into captions that can be used by video.js
35374   **/
35375
35376
35377  var CaptionParser = function CaptionParser() {
35378    var isInitialized = false;
35379    var captionStream; // Stores segments seen before trackId and timescale are set
35380
35381    var segmentCache; // Stores video track ID of the track being parsed
35382
35383    var trackId; // Stores the timescale of the track being parsed
35384
35385    var timescale; // Stores captions parsed so far
35386
35387    var parsedCaptions; // Stores whether we are receiving partial data or not
35388
35389    var parsingPartial;
35390    /**
35391      * A method to indicate whether a CaptionParser has been initalized
35392      * @returns {Boolean}
35393     **/
35394
35395    this.isInitialized = function () {
35396      return isInitialized;
35397    };
35398    /**
35399      * Initializes the underlying CaptionStream, SEI NAL parsing
35400      * and management, and caption collection
35401     **/
35402
35403
35404    this.init = function (options) {
35405      captionStream = new CaptionStream$1();
35406      isInitialized = true;
35407      parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
35408
35409      captionStream.on('data', function (event) {
vendor: 4,550 bytes, lines 35410-35559
35410        // Convert to seconds in the source's timescale
35411        event.startTime = event.startPts / timescale;
35412        event.endTime = event.endPts / timescale;
35413        parsedCaptions.captions.push(event);
35414        parsedCaptions.captionStreams[event.stream] = true;
35415      });
35416    };
35417    /**
35418      * Determines if a new video track will be selected
35419      * or if the timescale changed
35420      * @return {Boolean}
35421     **/
35422
35423
35424    this.isNewInit = function (videoTrackIds, timescales) {
35425      if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
35426        return false;
35427      }
35428
35429      return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
35430    };
35431    /**
35432      * Parses out SEI captions and interacts with underlying
35433      * CaptionStream to return dispatched captions
35434      *
35435      * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
35436      * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
35437      * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
35438      * @see parseEmbeddedCaptions
35439      * @see m2ts/caption-stream.js
35440     **/
35441
35442
35443    this.parse = function (segment, videoTrackIds, timescales) {
35444      var parsedData;
35445
35446      if (!this.isInitialized()) {
35447        return null; // This is not likely to be a video segment
35448      } else if (!videoTrackIds || !timescales) {
35449        return null;
35450      } else if (this.isNewInit(videoTrackIds, timescales)) {
35451        // Use the first video track only as there is no
35452        // mechanism to switch to other video tracks
35453        trackId = videoTrackIds[0];
35454        timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
35455        // data until we have one.
35456        // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
35457      } else if (trackId === null || !timescale) {
35458        segmentCache.push(segment);
35459        return null;
35460      } // Now that a timescale and trackId is set, parse cached segments
35461
35462
35463      while (segmentCache.length > 0) {
35464        var cachedSegment = segmentCache.shift();
35465        this.parse(cachedSegment, videoTrackIds, timescales);
35466      }
35467
35468      parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
35469
35470      if (parsedData === null || !parsedData.seiNals) {
35471        return null;
35472      }
35473
35474      this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
35475
35476      this.flushStream();
35477      return parsedCaptions;
35478    };
35479    /**
35480      * Pushes SEI NALUs onto CaptionStream
35481      * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
35482      * Assumes that `parseCaptionNals` has been called first
35483      * @see m2ts/caption-stream.js
35484      **/
35485
35486
35487    this.pushNals = function (nals) {
35488      if (!this.isInitialized() || !nals || nals.length === 0) {
35489        return null;
35490      }
35491
35492      nals.forEach(function (nal) {
35493        captionStream.push(nal);
35494      });
35495    };
35496    /**
35497      * Flushes underlying CaptionStream to dispatch processed, displayable captions
35498      * @see m2ts/caption-stream.js
35499     **/
35500
35501
35502    this.flushStream = function () {
35503      if (!this.isInitialized()) {
35504        return null;
35505      }
35506
35507      if (!parsingPartial) {
35508        captionStream.flush();
35509      } else {
35510        captionStream.partialFlush();
35511      }
35512    };
35513    /**
35514      * Reset caption buckets for new data
35515     **/
35516
35517
35518    this.clearParsedCaptions = function () {
35519      parsedCaptions.captions = [];
35520      parsedCaptions.captionStreams = {};
35521    };
35522    /**
35523      * Resets underlying CaptionStream
35524      * @see m2ts/caption-stream.js
35525     **/
35526
35527
35528    this.resetCaptionStream = function () {
35529      if (!this.isInitialized()) {
35530        return null;
35531      }
35532
35533      captionStream.reset();
35534    };
35535    /**
35536      * Convenience method to clear all captions flushed from the
35537      * CaptionStream and still being parsed
35538      * @see m2ts/caption-stream.js
35539     **/
35540
35541
35542    this.clearAllCaptions = function () {
35543      this.clearParsedCaptions();
35544      this.resetCaptionStream();
35545    };
35546    /**
35547      * Reset caption parser
35548     **/
35549
35550
35551    this.reset = function () {
35552      segmentCache = [];
35553      trackId = null;
35554      timescale = null;
35555
35556      if (!parsedCaptions) {
35557        parsedCaptions = {
35558          captions: [],
35559          // CC1, CC2, CC3, CC4
vendor: 28,883 bytes, lines 35560-36460
35560          captionStreams: {}
35561        };
35562      } else {
35563        this.clearParsedCaptions();
35564      }
35565
35566      this.resetCaptionStream();
35567    };
35568
35569    this.reset();
35570  };
35571
35572  var captionParser = CaptionParser;
35573
35574  /**
35575   * mux.js
35576   *
35577   * Copyright (c) Brightcove
35578   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35579   */
35580
35581  var streamTypes = {
35582    H264_STREAM_TYPE: 0x1B,
35583    ADTS_STREAM_TYPE: 0x0F,
35584    METADATA_STREAM_TYPE: 0x15
35585  };
35586
35587  var MAX_TS = 8589934592;
35588  var RO_THRESH = 4294967296;
35589  var TYPE_SHARED = 'shared';
35590
35591  var handleRollover = function handleRollover(value, reference) {
35592    var direction = 1;
35593
35594    if (value > reference) {
35595      // If the current timestamp value is greater than our reference timestamp and we detect a
35596      // timestamp rollover, this means the roll over is happening in the opposite direction.
35597      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
35598      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
35599      // rollover point. In loading this segment, the timestamp values will be very large,
35600      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
35601      // the time stamp to be `value - 2^33`.
35602      direction = -1;
35603    } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
35604    // cause an incorrect adjustment.
35605
35606
35607    while (Math.abs(reference - value) > RO_THRESH) {
35608      value += direction * MAX_TS;
35609    }
35610
35611    return value;
35612  };
35613
35614  var TimestampRolloverStream = function TimestampRolloverStream(type) {
35615    var lastDTS, referenceDTS;
35616    TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
35617    // video and audio. We could use `undefined` here, but having a string
35618    // makes debugging a little clearer.
35619
35620    this.type_ = type || TYPE_SHARED;
35621
35622    this.push = function (data) {
35623      // Any "shared" rollover streams will accept _all_ data. Otherwise,
35624      // streams will only accept data that matches their type.
35625      if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
35626        return;
35627      }
35628
35629      if (referenceDTS === undefined) {
35630        referenceDTS = data.dts;
35631      }
35632
35633      data.dts = handleRollover(data.dts, referenceDTS);
35634      data.pts = handleRollover(data.pts, referenceDTS);
35635      lastDTS = data.dts;
35636      this.trigger('data', data);
35637    };
35638
35639    this.flush = function () {
35640      referenceDTS = lastDTS;
35641      this.trigger('done');
35642    };
35643
35644    this.endTimeline = function () {
35645      this.flush();
35646      this.trigger('endedtimeline');
35647    };
35648
35649    this.discontinuity = function () {
35650      referenceDTS = void 0;
35651      lastDTS = void 0;
35652    };
35653
35654    this.reset = function () {
35655      this.discontinuity();
35656      this.trigger('reset');
35657    };
35658  };
35659
35660  TimestampRolloverStream.prototype = new stream();
35661  var timestampRolloverStream = {
35662    TimestampRolloverStream: TimestampRolloverStream,
35663    handleRollover: handleRollover
35664  };
35665
35666  var parsePid = function parsePid(packet) {
35667    var pid = packet[1] & 0x1f;
35668    pid <<= 8;
35669    pid |= packet[2];
35670    return pid;
35671  };
35672
35673  var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
35674    return !!(packet[1] & 0x40);
35675  };
35676
35677  var parseAdaptionField = function parseAdaptionField(packet) {
35678    var offset = 0; // if an adaption field is present, its length is specified by the
35679    // fifth byte of the TS packet header. The adaptation field is
35680    // used to add stuffing to PES packets that don't fill a complete
35681    // TS packet, and to specify some forms of timing and control data
35682    // that we do not currently use.
35683
35684    if ((packet[3] & 0x30) >>> 4 > 0x01) {
35685      offset += packet[4] + 1;
35686    }
35687
35688    return offset;
35689  };
35690
35691  var parseType$1 = function parseType(packet, pmtPid) {
35692    var pid = parsePid(packet);
35693
35694    if (pid === 0) {
35695      return 'pat';
35696    } else if (pid === pmtPid) {
35697      return 'pmt';
35698    } else if (pmtPid) {
35699      return 'pes';
35700    }
35701
35702    return null;
35703  };
35704
35705  var parsePat = function parsePat(packet) {
35706    var pusi = parsePayloadUnitStartIndicator(packet);
35707    var offset = 4 + parseAdaptionField(packet);
35708
35709    if (pusi) {
35710      offset += packet[offset] + 1;
35711    }
35712
35713    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
35714  };
35715
35716  var parsePmt = function parsePmt(packet) {
35717    var programMapTable = {};
35718    var pusi = parsePayloadUnitStartIndicator(packet);
35719    var payloadOffset = 4 + parseAdaptionField(packet);
35720
35721    if (pusi) {
35722      payloadOffset += packet[payloadOffset] + 1;
35723    } // PMTs can be sent ahead of the time when they should actually
35724    // take effect. We don't believe this should ever be the case
35725    // for HLS but we'll ignore "forward" PMT declarations if we see
35726    // them. Future PMT declarations have the current_next_indicator
35727    // set to zero.
35728
35729
35730    if (!(packet[payloadOffset + 5] & 0x01)) {
35731      return;
35732    }
35733
35734    var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
35735
35736    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
35737    tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
35738    // long the program info descriptors are
35739
35740    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
35741
35742    var offset = 12 + programInfoLength;
35743
35744    while (offset < tableEnd) {
35745      var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
35746
35747      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
35748      // skip past the elementary stream descriptors, if present
35749
35750      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
35751    }
35752
35753    return programMapTable;
35754  };
35755
35756  var parsePesType = function parsePesType(packet, programMapTable) {
35757    var pid = parsePid(packet);
35758    var type = programMapTable[pid];
35759
35760    switch (type) {
35761      case streamTypes.H264_STREAM_TYPE:
35762        return 'video';
35763
35764      case streamTypes.ADTS_STREAM_TYPE:
35765        return 'audio';
35766
35767      case streamTypes.METADATA_STREAM_TYPE:
35768        return 'timed-metadata';
35769
35770      default:
35771        return null;
35772    }
35773  };
35774
35775  var parsePesTime = function parsePesTime(packet) {
35776    var pusi = parsePayloadUnitStartIndicator(packet);
35777
35778    if (!pusi) {
35779      return null;
35780    }
35781
35782    var offset = 4 + parseAdaptionField(packet);
35783
35784    if (offset >= packet.byteLength) {
35785      // From the H 222.0 MPEG-TS spec
35786      // "For transport stream packets carrying PES packets, stuffing is needed when there
35787      //  is insufficient PES packet data to completely fill the transport stream packet
35788      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
35789      //  the sum of the lengths of the data elements in it, so that the payload bytes
35790      //  remaining after the adaptation field exactly accommodates the available PES packet
35791      //  data."
35792      //
35793      // If the offset is >= the length of the packet, then the packet contains no data
35794      // and instead is just adaption field stuffing bytes
35795      return null;
35796    }
35797
35798    var pes = null;
35799    var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
35800    // and a DTS value. Determine what combination of values is
35801    // available to work with.
35802
35803    ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
35804    // performs all bitwise operations on 32-bit integers but javascript
35805    // supports a much greater range (52-bits) of integer using standard
35806    // mathematical operations.
35807    // We construct a 31-bit value using bitwise operators over the 31
35808    // most significant bits and then multiply by 4 (equal to a left-shift
35809    // of 2) before we add the final 2 least significant bits of the
35810    // timestamp (equal to an OR.)
35811
35812    if (ptsDtsFlags & 0xC0) {
35813      pes = {}; // the PTS and DTS are not written out directly. For information
35814      // on how they are encoded, see
35815      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
35816
35817      pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
35818      pes.pts *= 4; // Left shift by 2
35819
35820      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
35821
35822      pes.dts = pes.pts;
35823
35824      if (ptsDtsFlags & 0x40) {
35825        pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
35826        pes.dts *= 4; // Left shift by 2
35827
35828        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
35829      }
35830    }
35831
35832    return pes;
35833  };
35834
35835  var parseNalUnitType = function parseNalUnitType(type) {
35836    switch (type) {
35837      case 0x05:
35838        return 'slice_layer_without_partitioning_rbsp_idr';
35839
35840      case 0x06:
35841        return 'sei_rbsp';
35842
35843      case 0x07:
35844        return 'seq_parameter_set_rbsp';
35845
35846      case 0x08:
35847        return 'pic_parameter_set_rbsp';
35848
35849      case 0x09:
35850        return 'access_unit_delimiter_rbsp';
35851
35852      default:
35853        return null;
35854    }
35855  };
35856
35857  var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
35858    var offset = 4 + parseAdaptionField(packet);
35859    var frameBuffer = packet.subarray(offset);
35860    var frameI = 0;
35861    var frameSyncPoint = 0;
35862    var foundKeyFrame = false;
35863    var nalType; // advance the sync point to a NAL start, if necessary
35864
35865    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
35866      if (frameBuffer[frameSyncPoint + 2] === 1) {
35867        // the sync point is properly aligned
35868        frameI = frameSyncPoint + 5;
35869        break;
35870      }
35871    }
35872
35873    while (frameI < frameBuffer.byteLength) {
35874      // look at the current byte to determine if we've hit the end of
35875      // a NAL unit boundary
35876      switch (frameBuffer[frameI]) {
35877        case 0:
35878          // skip past non-sync sequences
35879          if (frameBuffer[frameI - 1] !== 0) {
35880            frameI += 2;
35881            break;
35882          } else if (frameBuffer[frameI - 2] !== 0) {
35883            frameI++;
35884            break;
35885          }
35886
35887          if (frameSyncPoint + 3 !== frameI - 2) {
35888            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
35889
35890            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
35891              foundKeyFrame = true;
35892            }
35893          } // drop trailing zeroes
35894
35895
35896          do {
35897            frameI++;
35898          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
35899
35900          frameSyncPoint = frameI - 2;
35901          frameI += 3;
35902          break;
35903
35904        case 1:
35905          // skip past non-sync sequences
35906          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
35907            frameI += 3;
35908            break;
35909          }
35910
35911          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
35912
35913          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
35914            foundKeyFrame = true;
35915          }
35916
35917          frameSyncPoint = frameI - 2;
35918          frameI += 3;
35919          break;
35920
35921        default:
35922          // the current byte isn't a one or zero, so it cannot be part
35923          // of a sync sequence
35924          frameI += 3;
35925          break;
35926      }
35927    }
35928
35929    frameBuffer = frameBuffer.subarray(frameSyncPoint);
35930    frameI -= frameSyncPoint;
35931    frameSyncPoint = 0; // parse the final nal
35932
35933    if (frameBuffer && frameBuffer.byteLength > 3) {
35934      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
35935
35936      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
35937        foundKeyFrame = true;
35938      }
35939    }
35940
35941    return foundKeyFrame;
35942  };
35943
35944  var probe$1 = {
35945    parseType: parseType$1,
35946    parsePat: parsePat,
35947    parsePmt: parsePmt,
35948    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
35949    parsePesType: parsePesType,
35950    parsePesTime: parsePesTime,
35951    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
35952  };
35953
35954  /**
35955   * mux.js
35956   *
35957   * Copyright (c) Brightcove
35958   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
35959   *
35960   * Utilities to detect basic properties and metadata about Aac data.
35961   */
35962
35963  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
35964
35965  var isLikelyAacData = function isLikelyAacData(data) {
35966    if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
35967      return true;
35968    }
35969
35970    return false;
35971  };
35972
35973  var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
35974    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
35975  }; // return a percent-encoded representation of the specified byte range
35976  // @see http://en.wikipedia.org/wiki/Percent-encoding
35977
35978
35979  var percentEncode = function percentEncode(bytes, start, end) {
35980    var i,
35981        result = '';
35982
35983    for (i = start; i < end; i++) {
35984      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
35985    }
35986
35987    return result;
35988  }; // return the string representation of the specified byte range,
35989  // interpreted as ISO-8859-1.
35990
35991
35992  var parseIso88591 = function parseIso88591(bytes, start, end) {
35993    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
35994  };
35995
35996  var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
35997    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
35998        flags = header[byteIndex + 5],
35999        footerPresent = (flags & 16) >> 4;
36000
36001    if (footerPresent) {
36002      return returnSize + 20;
36003    }
36004
36005    return returnSize + 10;
36006  };
36007
36008  var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
36009    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
36010        middle = header[byteIndex + 4] << 3,
36011        highTwo = header[byteIndex + 3] & 0x3 << 11;
36012    return highTwo | middle | lowThree;
36013  };
36014
36015  var parseType$2 = function parseType(header, byteIndex) {
36016    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
36017      return 'timed-metadata';
36018    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
36019      return 'audio';
36020    }
36021
36022    return null;
36023  };
36024
36025  var parseSampleRate = function parseSampleRate(packet) {
36026    var i = 0;
36027
36028    while (i + 5 < packet.length) {
36029      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
36030        // If a valid header was not found,  jump one forward and attempt to
36031        // find a valid ADTS header starting at the next byte
36032        i++;
36033        continue;
36034      }
36035
36036      return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
36037    }
36038
36039    return null;
36040  };
36041
36042  var parseAacTimestamp = function parseAacTimestamp(packet) {
36043    var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
36044
36045    frameStart = 10;
36046
36047    if (packet[5] & 0x40) {
36048      // advance the frame start past the extended header
36049      frameStart += 4; // header size field
36050
36051      frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
36052    } // parse one or more ID3 frames
36053    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
36054
36055
36056    do {
36057      // determine the number of bytes in this frame
36058      frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
36059
36060      if (frameSize < 1) {
36061        return null;
36062      }
36063
36064      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
36065
36066      if (frameHeader === 'PRIV') {
36067        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
36068
36069        for (var i = 0; i < frame.byteLength; i++) {
36070          if (frame[i] === 0) {
36071            var owner = parseIso88591(frame, 0, i);
36072
36073            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
36074              var d = frame.subarray(i + 1);
36075              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
36076              size *= 4;
36077              size += d[7] & 0x03;
36078              return size;
36079            }
36080
36081            break;
36082          }
36083        }
36084      }
36085
36086      frameStart += 10; // advance past the frame header
36087
36088      frameStart += frameSize; // advance past the frame body
36089    } while (frameStart < packet.byteLength);
36090
36091    return null;
36092  };
36093
36094  var utils = {
36095    isLikelyAacData: isLikelyAacData,
36096    parseId3TagSize: parseId3TagSize,
36097    parseAdtsSize: parseAdtsSize,
36098    parseType: parseType$2,
36099    parseSampleRate: parseSampleRate,
36100    parseAacTimestamp: parseAacTimestamp
36101  };
36102
36103  /**
36104   * mux.js
36105   *
36106   * Copyright (c) Brightcove
36107   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
36108   */
36109  var ONE_SECOND_IN_TS = 90000,
36110      // 90kHz clock
36111  secondsToVideoTs,
36112      secondsToAudioTs,
36113      videoTsToSeconds,
36114      audioTsToSeconds,
36115      audioTsToVideoTs,
36116      videoTsToAudioTs,
36117      metadataTsToSeconds;
36118
36119  secondsToVideoTs = function secondsToVideoTs(seconds) {
36120    return seconds * ONE_SECOND_IN_TS;
36121  };
36122
36123  secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
36124    return seconds * sampleRate;
36125  };
36126
36127  videoTsToSeconds = function videoTsToSeconds(timestamp) {
36128    return timestamp / ONE_SECOND_IN_TS;
36129  };
36130
36131  audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
36132    return timestamp / sampleRate;
36133  };
36134
36135  audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
36136    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
36137  };
36138
36139  videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
36140    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
36141  };
36142  /**
36143   * Adjust ID3 tag or caption timing information by the timeline pts values
36144   * (if keepOriginalTimestamps is false) and convert to seconds
36145   */
36146
36147
36148  metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
36149    return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
36150  };
36151
36152  var clock = {
36153    ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
36154    secondsToVideoTs: secondsToVideoTs,
36155    secondsToAudioTs: secondsToAudioTs,
36156    videoTsToSeconds: videoTsToSeconds,
36157    audioTsToSeconds: audioTsToSeconds,
36158    audioTsToVideoTs: audioTsToVideoTs,
36159    videoTsToAudioTs: videoTsToAudioTs,
36160    metadataTsToSeconds: metadataTsToSeconds
36161  };
36162
36163  var handleRollover$1 = timestampRolloverStream.handleRollover;
36164  var probe$2 = {};
36165  probe$2.ts = probe$1;
36166  probe$2.aac = utils;
36167  var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
36168  var MP2T_PACKET_LENGTH = 188,
36169      // bytes
36170  SYNC_BYTE = 0x47;
36171  /**
36172   * walks through segment data looking for pat and pmt packets to parse out
36173   * program map table information
36174   */
36175
36176  var parsePsi_ = function parsePsi_(bytes, pmt) {
36177    var startIndex = 0,
36178        endIndex = MP2T_PACKET_LENGTH,
36179        packet,
36180        type;
36181
36182    while (endIndex < bytes.byteLength) {
36183      // Look for a pair of start and end sync bytes in the data..
36184      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36185        // We found a packet
36186        packet = bytes.subarray(startIndex, endIndex);
36187        type = probe$2.ts.parseType(packet, pmt.pid);
36188
36189        switch (type) {
36190          case 'pat':
36191            if (!pmt.pid) {
36192              pmt.pid = probe$2.ts.parsePat(packet);
36193            }
36194
36195            break;
36196
36197          case 'pmt':
36198            if (!pmt.table) {
36199              pmt.table = probe$2.ts.parsePmt(packet);
36200            }
36201
36202            break;
36203
36204          default:
36205            break;
36206        } // Found the pat and pmt, we can stop walking the segment
36207
36208
36209        if (pmt.pid && pmt.table) {
36210          return;
36211        }
36212
36213        startIndex += MP2T_PACKET_LENGTH;
36214        endIndex += MP2T_PACKET_LENGTH;
36215        continue;
36216      } // If we get here, we have somehow become de-synchronized and we need to step
36217      // forward one byte at a time until we find a pair of sync bytes that denote
36218      // a packet
36219
36220
36221      startIndex++;
36222      endIndex++;
36223    }
36224  };
36225  /**
36226   * walks through the segment data from the start and end to get timing information
36227   * for the first and last audio pes packets
36228   */
36229
36230
36231  var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
36232    var startIndex = 0,
36233        endIndex = MP2T_PACKET_LENGTH,
36234        packet,
36235        type,
36236        pesType,
36237        pusi,
36238        parsed;
36239    var endLoop = false; // Start walking from start of segment to get first audio packet
36240
36241    while (endIndex <= bytes.byteLength) {
36242      // Look for a pair of start and end sync bytes in the data..
36243      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
36244        // We found a packet
36245        packet = bytes.subarray(startIndex, endIndex);
36246        type = probe$2.ts.parseType(packet, pmt.pid);
36247
36248        switch (type) {
36249          case 'pes':
36250            pesType = probe$2.ts.parsePesType(packet, pmt.table);
36251            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
36252
36253            if (pesType === 'audio' && pusi) {
36254              parsed = probe$2.ts.parsePesTime(packet);
36255
36256              if (parsed) {
36257                parsed.type = 'audio';
36258                result.audio.push(parsed);
36259                endLoop = true;
36260              }
36261            }
36262
36263            break;
36264
36265          default:
36266            break;
36267        }
36268
36269        if (endLoop) {
36270          break;
36271        }
36272
36273        startIndex += MP2T_PACKET_LENGTH;
36274        endIndex += MP2T_PACKET_LENGTH;
36275        continue;
36276      } // If we get here, we have somehow become de-synchronized and we need to step
36277      // forward one byte at a time until we find a pair of sync bytes that denote
36278      // a packet
36279
36280
36281      startIndex++;
36282      endIndex++;
36283    } // Start walking from end of segment to get last audio packet
36284
36285
36286    endIndex = bytes.byteLength;
36287    startIndex = endIndex - MP2T_PACKET_LENGTH;
36288    endLoop = false;
36289
36290    while (startIndex >= 0) {
36291      // Look for a pair of start and end sync bytes in the data..
36292      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
36293        // We found a packet
36294        packet = bytes.subarray(startIndex, endIndex);
36295        type = probe$2.ts.parseType(packet, pmt.pid);
36296
36297        switch (type) {
36298          case 'pes':
36299            pesType = probe$2.ts.parsePesType(packet, pmt.table);
36300            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
36301
36302            if (pesType === 'audio' && pusi) {
36303              parsed = probe$2.ts.parsePesTime(packet);
36304
36305              if (parsed) {
36306                parsed.type = 'audio';
36307                result.audio.push(parsed);
36308                endLoop = true;
36309              }
36310            }
36311
36312            break;
36313
36314          default:
36315            break;
36316        }
36317
36318        if (endLoop) {
36319          break;
36320        }
36321
36322        startIndex -= MP2T_PACKET_LENGTH;
36323        endIndex -= MP2T_PACKET_LENGTH;
36324        continue;
36325      } // If we get here, we have somehow become de-synchronized and we need to step
36326      // forward one byte at a time until we find a pair of sync bytes that denote
36327      // a packet
36328
36329
36330      startIndex--;
36331      endIndex--;
36332    }
36333  };
36334  /**
36335   * walks through the segment data from the start and end to get timing information
36336   * for the first and last video pes packets as well as timing information for the first
36337   * key frame.
36338   */
36339
36340
36341  var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
36342    var startIndex = 0,
36343        endIndex = MP2T_PACKET_LENGTH,
36344        packet,
36345        type,
36346        pesType,
36347        pusi,
36348        parsed,
36349        frame,
36350        i,
36351        pes;
36352    var endLoop = false;
36353    var currentFrame = {
36354      data: [],
36355      size: 0
36356    }; // Start walking from start of segment to get first video packet
36357
36358    while (endIndex < bytes.byteLength) {
36359      // Look for a pair of start and end sync bytes in the data..
36360      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36361        // We found a packet
36362        packet = bytes.subarray(startIndex, endIndex);
36363        type = probe$2.ts.parseType(packet, pmt.pid);
36364
36365        switch (type) {
36366          case 'pes':
36367            pesType = probe$2.ts.parsePesType(packet, pmt.table);
36368            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
36369
36370            if (pesType === 'video') {
36371              if (pusi && !endLoop) {
36372                parsed = probe$2.ts.parsePesTime(packet);
36373
36374                if (parsed) {
36375                  parsed.type = 'video';
36376                  result.video.push(parsed);
36377                  endLoop = true;
36378                }
36379              }
36380
36381              if (!result.firstKeyFrame) {
36382                if (pusi) {
36383                  if (currentFrame.size !== 0) {
36384                    frame = new Uint8Array(currentFrame.size);
36385                    i = 0;
36386
36387                    while (currentFrame.data.length) {
36388                      pes = currentFrame.data.shift();
36389                      frame.set(pes, i);
36390                      i += pes.byteLength;
36391                    }
36392
36393                    if (probe$2.ts.videoPacketContainsKeyFrame(frame)) {
36394                      var firstKeyFrame = probe$2.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
36395                      // the keyframe seems to work fine with HLS playback
36396                      // and definitely preferable to a crash with TypeError...
36397
36398                      if (firstKeyFrame) {
36399                        result.firstKeyFrame = firstKeyFrame;
36400                        result.firstKeyFrame.type = 'video';
36401                      } else {
36402                        // eslint-disable-next-line
36403                        console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
36404                      }
36405                    }
36406
36407                    currentFrame.size = 0;
36408                  }
36409                }
36410
36411                currentFrame.data.push(packet);
36412                currentFrame.size += packet.byteLength;
36413              }
36414            }
36415
36416            break;
36417
36418          default:
36419            break;
36420        }
36421
36422        if (endLoop && result.firstKeyFrame) {
36423          break;
36424        }
36425
36426        startIndex += MP2T_PACKET_LENGTH;
36427        endIndex += MP2T_PACKET_LENGTH;
36428        continue;
36429      } // If we get here, we have somehow become de-synchronized and we need to step
36430      // forward one byte at a time until we find a pair of sync bytes that denote
36431      // a packet
36432
36433
36434      startIndex++;
36435      endIndex++;
36436    } // Start walking from end of segment to get last video packet
36437
36438
36439    endIndex = bytes.byteLength;
36440    startIndex = endIndex - MP2T_PACKET_LENGTH;
36441    endLoop = false;
36442
36443    while (startIndex >= 0) {
36444      // Look for a pair of start and end sync bytes in the data..
36445      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
36446        // We found a packet
36447        packet = bytes.subarray(startIndex, endIndex);
36448        type = probe$2.ts.parseType(packet, pmt.pid);
36449
36450        switch (type) {
36451          case 'pes':
36452            pesType = probe$2.ts.parsePesType(packet, pmt.table);
36453            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
36454
36455            if (pesType === 'video' && pusi) {
36456              parsed = probe$2.ts.parsePesTime(packet);
36457
36458              if (parsed) {
36459                parsed.type = 'video';
36460                result.video.push(parsed);
vendor: 76,827 bytes, lines 36461-38860
36461                endLoop = true;
36462              }
36463            }
36464
36465            break;
36466
36467          default:
36468            break;
36469        }
36470
36471        if (endLoop) {
36472          break;
36473        }
36474
36475        startIndex -= MP2T_PACKET_LENGTH;
36476        endIndex -= MP2T_PACKET_LENGTH;
36477        continue;
36478      } // If we get here, we have somehow become de-synchronized and we need to step
36479      // forward one byte at a time until we find a pair of sync bytes that denote
36480      // a packet
36481
36482
36483      startIndex--;
36484      endIndex--;
36485    }
36486  };
36487  /**
36488   * Adjusts the timestamp information for the segment to account for
36489   * rollover and convert to seconds based on pes packet timescale (90khz clock)
36490   */
36491
36492
36493  var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
36494    if (segmentInfo.audio && segmentInfo.audio.length) {
36495      var audioBaseTimestamp = baseTimestamp;
36496
36497      if (typeof audioBaseTimestamp === 'undefined') {
36498        audioBaseTimestamp = segmentInfo.audio[0].dts;
36499      }
36500
36501      segmentInfo.audio.forEach(function (info) {
36502        info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
36503        info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
36504
36505        info.dtsTime = info.dts / ONE_SECOND_IN_TS$1;
36506        info.ptsTime = info.pts / ONE_SECOND_IN_TS$1;
36507      });
36508    }
36509
36510    if (segmentInfo.video && segmentInfo.video.length) {
36511      var videoBaseTimestamp = baseTimestamp;
36512
36513      if (typeof videoBaseTimestamp === 'undefined') {
36514        videoBaseTimestamp = segmentInfo.video[0].dts;
36515      }
36516
36517      segmentInfo.video.forEach(function (info) {
36518        info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
36519        info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
36520
36521        info.dtsTime = info.dts / ONE_SECOND_IN_TS$1;
36522        info.ptsTime = info.pts / ONE_SECOND_IN_TS$1;
36523      });
36524
36525      if (segmentInfo.firstKeyFrame) {
36526        var frame = segmentInfo.firstKeyFrame;
36527        frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
36528        frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
36529
36530        frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1;
36531        frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1;
36532      }
36533    }
36534  };
36535  /**
36536   * inspects the aac data stream for start and end time information
36537   */
36538
36539
36540  var inspectAac_ = function inspectAac_(bytes) {
36541    var endLoop = false,
36542        audioCount = 0,
36543        sampleRate = null,
36544        timestamp = null,
36545        frameSize = 0,
36546        byteIndex = 0,
36547        packet;
36548
36549    while (bytes.length - byteIndex >= 3) {
36550      var type = probe$2.aac.parseType(bytes, byteIndex);
36551
36552      switch (type) {
36553        case 'timed-metadata':
36554          // Exit early because we don't have enough to parse
36555          // the ID3 tag header
36556          if (bytes.length - byteIndex < 10) {
36557            endLoop = true;
36558            break;
36559          }
36560
36561          frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
36562          // to emit a full packet
36563
36564          if (frameSize > bytes.length) {
36565            endLoop = true;
36566            break;
36567          }
36568
36569          if (timestamp === null) {
36570            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
36571            timestamp = probe$2.aac.parseAacTimestamp(packet);
36572          }
36573
36574          byteIndex += frameSize;
36575          break;
36576
36577        case 'audio':
36578          // Exit early because we don't have enough to parse
36579          // the ADTS frame header
36580          if (bytes.length - byteIndex < 7) {
36581            endLoop = true;
36582            break;
36583          }
36584
36585          frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
36586          // to emit a full packet
36587
36588          if (frameSize > bytes.length) {
36589            endLoop = true;
36590            break;
36591          }
36592
36593          if (sampleRate === null) {
36594            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
36595            sampleRate = probe$2.aac.parseSampleRate(packet);
36596          }
36597
36598          audioCount++;
36599          byteIndex += frameSize;
36600          break;
36601
36602        default:
36603          byteIndex++;
36604          break;
36605      }
36606
36607      if (endLoop) {
36608        return null;
36609      }
36610    }
36611
36612    if (sampleRate === null || timestamp === null) {
36613      return null;
36614    }
36615
36616    var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate;
36617    var result = {
36618      audio: [{
36619        type: 'audio',
36620        dts: timestamp,
36621        pts: timestamp
36622      }, {
36623        type: 'audio',
36624        dts: timestamp + audioCount * 1024 * audioTimescale,
36625        pts: timestamp + audioCount * 1024 * audioTimescale
36626      }]
36627    };
36628    return result;
36629  };
36630  /**
36631   * inspects the transport stream segment data for start and end time information
36632   * of the audio and video tracks (when present) as well as the first key frame's
36633   * start time.
36634   */
36635
36636
36637  var inspectTs_ = function inspectTs_(bytes) {
36638    var pmt = {
36639      pid: null,
36640      table: null
36641    };
36642    var result = {};
36643    parsePsi_(bytes, pmt);
36644
36645    for (var pid in pmt.table) {
36646      if (pmt.table.hasOwnProperty(pid)) {
36647        var type = pmt.table[pid];
36648
36649        switch (type) {
36650          case streamTypes.H264_STREAM_TYPE:
36651            result.video = [];
36652            parseVideoPes_(bytes, pmt, result);
36653
36654            if (result.video.length === 0) {
36655              delete result.video;
36656            }
36657
36658            break;
36659
36660          case streamTypes.ADTS_STREAM_TYPE:
36661            result.audio = [];
36662            parseAudioPes_(bytes, pmt, result);
36663
36664            if (result.audio.length === 0) {
36665              delete result.audio;
36666            }
36667
36668            break;
36669
36670          default:
36671            break;
36672        }
36673      }
36674    }
36675
36676    return result;
36677  };
36678  /**
36679   * Inspects segment byte data and returns an object with start and end timing information
36680   *
36681   * @param {Uint8Array} bytes The segment byte data
36682   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
36683   *  timestamps for rollover. This value must be in 90khz clock.
36684   * @return {Object} Object containing start and end frame timing info of segment.
36685   */
36686
36687
36688  var inspect = function inspect(bytes, baseTimestamp) {
36689    var isAacData = probe$2.aac.isLikelyAacData(bytes);
36690    var result;
36691
36692    if (isAacData) {
36693      result = inspectAac_(bytes);
36694    } else {
36695      result = inspectTs_(bytes);
36696    }
36697
36698    if (!result || !result.audio && !result.video) {
36699      return null;
36700    }
36701
36702    adjustTimestamp_(result, baseTimestamp);
36703    return result;
36704  };
36705
36706  var tsInspector = {
36707    inspect: inspect,
36708    parseAudioPes_: parseAudioPes_
36709  };
36710
36711  /*
36712   * pkcs7.pad
36713   * https://github.com/brightcove/pkcs7
36714   *
36715   * Copyright (c) 2014 Brightcove
36716   * Licensed under the apache2 license.
36717   */
36718  /**
36719   * Returns the subarray of a Uint8Array without PKCS#7 padding.
36720   * @param padded {Uint8Array} unencrypted bytes that have been padded
36721   * @return {Uint8Array} the unpadded bytes
36722   * @see http://tools.ietf.org/html/rfc5652
36723   */
36724
36725  function unpad(padded) {
36726    return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
36727  }
36728
36729  var classCallCheck = function classCallCheck(instance, Constructor) {
36730    if (!(instance instanceof Constructor)) {
36731      throw new TypeError("Cannot call a class as a function");
36732    }
36733  };
36734
36735  var createClass = function () {
36736    function defineProperties(target, props) {
36737      for (var i = 0; i < props.length; i++) {
36738        var descriptor = props[i];
36739        descriptor.enumerable = descriptor.enumerable || false;
36740        descriptor.configurable = true;
36741        if ("value" in descriptor) descriptor.writable = true;
36742        Object.defineProperty(target, descriptor.key, descriptor);
36743      }
36744    }
36745
36746    return function (Constructor, protoProps, staticProps) {
36747      if (protoProps) defineProperties(Constructor.prototype, protoProps);
36748      if (staticProps) defineProperties(Constructor, staticProps);
36749      return Constructor;
36750    };
36751  }();
36752
36753  var inherits$1 = function inherits(subClass, superClass) {
36754    if (typeof superClass !== "function" && superClass !== null) {
36755      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
36756    }
36757
36758    subClass.prototype = Object.create(superClass && superClass.prototype, {
36759      constructor: {
36760        value: subClass,
36761        enumerable: false,
36762        writable: true,
36763        configurable: true
36764      }
36765    });
36766    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
36767  };
36768
36769  var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
36770    if (!self) {
36771      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
36772    }
36773
36774    return call && (typeof call === "object" || typeof call === "function") ? call : self;
36775  };
36776  /**
36777   * @file aes.js
36778   *
36779   * This file contains an adaptation of the AES decryption algorithm
36780   * from the Standford Javascript Cryptography Library. That work is
36781   * covered by the following copyright and permissions notice:
36782   *
36783   * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
36784   * All rights reserved.
36785   *
36786   * Redistribution and use in source and binary forms, with or without
36787   * modification, are permitted provided that the following conditions are
36788   * met:
36789   *
36790   * 1. Redistributions of source code must retain the above copyright
36791   *    notice, this list of conditions and the following disclaimer.
36792   *
36793   * 2. Redistributions in binary form must reproduce the above
36794   *    copyright notice, this list of conditions and the following
36795   *    disclaimer in the documentation and/or other materials provided
36796   *    with the distribution.
36797   *
36798   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
36799   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
36800   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36801   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
36802   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
36803   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36804   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
36805   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
36806   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
36807   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
36808   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36809   *
36810   * The views and conclusions contained in the software and documentation
36811   * are those of the authors and should not be interpreted as representing
36812   * official policies, either expressed or implied, of the authors.
36813   */
36814
36815  /**
36816   * Expand the S-box tables.
36817   *
36818   * @private
36819   */
36820
36821
36822  var precompute = function precompute() {
36823    var tables = [[[], [], [], [], []], [[], [], [], [], []]];
36824    var encTable = tables[0];
36825    var decTable = tables[1];
36826    var sbox = encTable[4];
36827    var sboxInv = decTable[4];
36828    var i = void 0;
36829    var x = void 0;
36830    var xInv = void 0;
36831    var d = [];
36832    var th = [];
36833    var x2 = void 0;
36834    var x4 = void 0;
36835    var x8 = void 0;
36836    var s = void 0;
36837    var tEnc = void 0;
36838    var tDec = void 0; // Compute double and third tables
36839
36840    for (i = 0; i < 256; i++) {
36841      th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
36842    }
36843
36844    for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
36845      // Compute sbox
36846      s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
36847      s = s >> 8 ^ s & 255 ^ 99;
36848      sbox[x] = s;
36849      sboxInv[s] = x; // Compute MixColumns
36850
36851      x8 = d[x4 = d[x2 = d[x]]];
36852      tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
36853      tEnc = d[s] * 0x101 ^ s * 0x1010100;
36854
36855      for (i = 0; i < 4; i++) {
36856        encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
36857        decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
36858      }
36859    } // Compactify. Considerable speedup on Firefox.
36860
36861
36862    for (i = 0; i < 5; i++) {
36863      encTable[i] = encTable[i].slice(0);
36864      decTable[i] = decTable[i].slice(0);
36865    }
36866
36867    return tables;
36868  };
36869
36870  var aesTables = null;
36871  /**
36872   * Schedule out an AES key for both encryption and decryption. This
36873   * is a low-level class. Use a cipher mode to do bulk encryption.
36874   *
36875   * @class AES
36876   * @param key {Array} The key as an array of 4, 6 or 8 words.
36877   */
36878
36879  var AES = function () {
36880    function AES(key) {
36881      classCallCheck(this, AES);
36882      /**
36883       * The expanded S-box and inverse S-box tables. These will be computed
36884       * on the client so that we don't have to send them down the wire.
36885       *
36886       * There are two tables, _tables[0] is for encryption and
36887       * _tables[1] is for decryption.
36888       *
36889       * The first 4 sub-tables are the expanded S-box with MixColumns. The
36890       * last (_tables[01][4]) is the S-box itself.
36891       *
36892       * @private
36893       */
36894      // if we have yet to precompute the S-box tables
36895      // do so now
36896
36897      if (!aesTables) {
36898        aesTables = precompute();
36899      } // then make a copy of that object for use
36900
36901
36902      this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
36903      var i = void 0;
36904      var j = void 0;
36905      var tmp = void 0;
36906      var encKey = void 0;
36907      var decKey = void 0;
36908      var sbox = this._tables[0][4];
36909      var decTable = this._tables[1];
36910      var keyLen = key.length;
36911      var rcon = 1;
36912
36913      if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
36914        throw new Error('Invalid aes key size');
36915      }
36916
36917      encKey = key.slice(0);
36918      decKey = [];
36919      this._key = [encKey, decKey]; // schedule encryption keys
36920
36921      for (i = keyLen; i < 4 * keyLen + 28; i++) {
36922        tmp = encKey[i - 1]; // apply sbox
36923
36924        if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
36925          tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
36926
36927          if (i % keyLen === 0) {
36928            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
36929            rcon = rcon << 1 ^ (rcon >> 7) * 283;
36930          }
36931        }
36932
36933        encKey[i] = encKey[i - keyLen] ^ tmp;
36934      } // schedule decryption keys
36935
36936
36937      for (j = 0; i; j++, i--) {
36938        tmp = encKey[j & 3 ? i : i - 4];
36939
36940        if (i <= 4 || j < 4) {
36941          decKey[j] = tmp;
36942        } else {
36943          decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
36944        }
36945      }
36946    }
36947    /**
36948     * Decrypt 16 bytes, specified as four 32-bit words.
36949     *
36950     * @param {Number} encrypted0 the first word to decrypt
36951     * @param {Number} encrypted1 the second word to decrypt
36952     * @param {Number} encrypted2 the third word to decrypt
36953     * @param {Number} encrypted3 the fourth word to decrypt
36954     * @param {Int32Array} out the array to write the decrypted words
36955     * into
36956     * @param {Number} offset the offset into the output array to start
36957     * writing results
36958     * @return {Array} The plaintext.
36959     */
36960
36961
36962    AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
36963      var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
36964
36965      var a = encrypted0 ^ key[0];
36966      var b = encrypted3 ^ key[1];
36967      var c = encrypted2 ^ key[2];
36968      var d = encrypted1 ^ key[3];
36969      var a2 = void 0;
36970      var b2 = void 0;
36971      var c2 = void 0; // key.length === 2 ?
36972
36973      var nInnerRounds = key.length / 4 - 2;
36974      var i = void 0;
36975      var kIndex = 4;
36976      var table = this._tables[1]; // load up the tables
36977
36978      var table0 = table[0];
36979      var table1 = table[1];
36980      var table2 = table[2];
36981      var table3 = table[3];
36982      var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
36983
36984      for (i = 0; i < nInnerRounds; i++) {
36985        a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
36986        b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
36987        c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
36988        d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
36989        kIndex += 4;
36990        a = a2;
36991        b = b2;
36992        c = c2;
36993      } // Last round.
36994
36995
36996      for (i = 0; i < 4; i++) {
36997        out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
36998        a2 = a;
36999        a = b;
37000        b = c;
37001        c = d;
37002        d = a2;
37003      }
37004    };
37005
37006    return AES;
37007  }();
37008  /**
37009   * @file stream.js
37010   */
37011
37012  /**
37013   * A lightweight readable stream implemention that handles event dispatching.
37014   *
37015   * @class Stream
37016   */
37017
37018
37019  var Stream$2 = function () {
37020    function Stream() {
37021      classCallCheck(this, Stream);
37022      this.listeners = {};
37023    }
37024    /**
37025     * Add a listener for a specified event type.
37026     *
37027     * @param {String} type the event name
37028     * @param {Function} listener the callback to be invoked when an event of
37029     * the specified type occurs
37030     */
37031
37032
37033    Stream.prototype.on = function on(type, listener) {
37034      if (!this.listeners[type]) {
37035        this.listeners[type] = [];
37036      }
37037
37038      this.listeners[type].push(listener);
37039    };
37040    /**
37041     * Remove a listener for a specified event type.
37042     *
37043     * @param {String} type the event name
37044     * @param {Function} listener  a function previously registered for this
37045     * type of event through `on`
37046     * @return {Boolean} if we could turn it off or not
37047     */
37048
37049
37050    Stream.prototype.off = function off(type, listener) {
37051      if (!this.listeners[type]) {
37052        return false;
37053      }
37054
37055      var index = this.listeners[type].indexOf(listener);
37056      this.listeners[type].splice(index, 1);
37057      return index > -1;
37058    };
37059    /**
37060     * Trigger an event of the specified type on this stream. Any additional
37061     * arguments to this function are passed as parameters to event listeners.
37062     *
37063     * @param {String} type the event name
37064     */
37065
37066
37067    Stream.prototype.trigger = function trigger(type) {
37068      var callbacks = this.listeners[type];
37069
37070      if (!callbacks) {
37071        return;
37072      } // Slicing the arguments on every invocation of this method
37073      // can add a significant amount of overhead. Avoid the
37074      // intermediate object creation for the common case of a
37075      // single callback argument
37076
37077
37078      if (arguments.length === 2) {
37079        var length = callbacks.length;
37080
37081        for (var i = 0; i < length; ++i) {
37082          callbacks[i].call(this, arguments[1]);
37083        }
37084      } else {
37085        var args = Array.prototype.slice.call(arguments, 1);
37086        var _length = callbacks.length;
37087
37088        for (var _i = 0; _i < _length; ++_i) {
37089          callbacks[_i].apply(this, args);
37090        }
37091      }
37092    };
37093    /**
37094     * Destroys the stream and cleans up.
37095     */
37096
37097
37098    Stream.prototype.dispose = function dispose() {
37099      this.listeners = {};
37100    };
37101    /**
37102     * Forwards all `data` events on this stream to the destination stream. The
37103     * destination stream should provide a method `push` to receive the data
37104     * events as they arrive.
37105     *
37106     * @param {Stream} destination the stream that will receive all `data` events
37107     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
37108     */
37109
37110
37111    Stream.prototype.pipe = function pipe(destination) {
37112      this.on('data', function (data) {
37113        destination.push(data);
37114      });
37115    };
37116
37117    return Stream;
37118  }();
37119  /**
37120   * @file async-stream.js
37121   */
37122
37123  /**
37124   * A wrapper around the Stream class to use setTiemout
37125   * and run stream "jobs" Asynchronously
37126   *
37127   * @class AsyncStream
37128   * @extends Stream
37129   */
37130
37131
37132  var AsyncStream = function (_Stream) {
37133    inherits$1(AsyncStream, _Stream);
37134
37135    function AsyncStream() {
37136      classCallCheck(this, AsyncStream);
37137
37138      var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2));
37139
37140      _this.jobs = [];
37141      _this.delay = 1;
37142      _this.timeout_ = null;
37143      return _this;
37144    }
37145    /**
37146     * process an async job
37147     *
37148     * @private
37149     */
37150
37151
37152    AsyncStream.prototype.processJob_ = function processJob_() {
37153      this.jobs.shift()();
37154
37155      if (this.jobs.length) {
37156        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
37157      } else {
37158        this.timeout_ = null;
37159      }
37160    };
37161    /**
37162     * push a job into the stream
37163     *
37164     * @param {Function} job the job to push into the stream
37165     */
37166
37167
37168    AsyncStream.prototype.push = function push(job) {
37169      this.jobs.push(job);
37170
37171      if (!this.timeout_) {
37172        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
37173      }
37174    };
37175
37176    return AsyncStream;
37177  }(Stream$2);
37178  /**
37179   * @file decrypter.js
37180   *
37181   * An asynchronous implementation of AES-128 CBC decryption with
37182   * PKCS#7 padding.
37183   */
37184
37185  /**
37186   * Convert network-order (big-endian) bytes into their little-endian
37187   * representation.
37188   */
37189
37190
37191  var ntoh = function ntoh(word) {
37192    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
37193  };
37194  /**
37195   * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
37196   *
37197   * @param {Uint8Array} encrypted the encrypted bytes
37198   * @param {Uint32Array} key the bytes of the decryption key
37199   * @param {Uint32Array} initVector the initialization vector (IV) to
37200   * use for the first round of CBC.
37201   * @return {Uint8Array} the decrypted bytes
37202   *
37203   * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
37204   * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
37205   * @see https://tools.ietf.org/html/rfc2315
37206   */
37207
37208
37209  var decrypt = function decrypt(encrypted, key, initVector) {
37210    // word-level access to the encrypted bytes
37211    var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
37212    var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
37213
37214    var decrypted = new Uint8Array(encrypted.byteLength);
37215    var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
37216    // decrypted data
37217
37218    var init0 = void 0;
37219    var init1 = void 0;
37220    var init2 = void 0;
37221    var init3 = void 0;
37222    var encrypted0 = void 0;
37223    var encrypted1 = void 0;
37224    var encrypted2 = void 0;
37225    var encrypted3 = void 0; // iteration variable
37226
37227    var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
37228    // passed-in reference and easier access
37229
37230    init0 = initVector[0];
37231    init1 = initVector[1];
37232    init2 = initVector[2];
37233    init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
37234    // to each decrypted block
37235
37236    for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
37237      // convert big-endian (network order) words into little-endian
37238      // (javascript order)
37239      encrypted0 = ntoh(encrypted32[wordIx]);
37240      encrypted1 = ntoh(encrypted32[wordIx + 1]);
37241      encrypted2 = ntoh(encrypted32[wordIx + 2]);
37242      encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
37243
37244      decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
37245      // plaintext
37246
37247      decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
37248      decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
37249      decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
37250      decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
37251
37252      init0 = encrypted0;
37253      init1 = encrypted1;
37254      init2 = encrypted2;
37255      init3 = encrypted3;
37256    }
37257
37258    return decrypted;
37259  };
37260  /**
37261   * The `Decrypter` class that manages decryption of AES
37262   * data through `AsyncStream` objects and the `decrypt`
37263   * function
37264   *
37265   * @param {Uint8Array} encrypted the encrypted bytes
37266   * @param {Uint32Array} key the bytes of the decryption key
37267   * @param {Uint32Array} initVector the initialization vector (IV) to
37268   * @param {Function} done the function to run when done
37269   * @class Decrypter
37270   */
37271
37272
37273  var Decrypter = function () {
37274    function Decrypter(encrypted, key, initVector, done) {
37275      classCallCheck(this, Decrypter);
37276      var step = Decrypter.STEP;
37277      var encrypted32 = new Int32Array(encrypted.buffer);
37278      var decrypted = new Uint8Array(encrypted.byteLength);
37279      var i = 0;
37280      this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
37281
37282      this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
37283
37284      for (i = step; i < encrypted32.length; i += step) {
37285        initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
37286        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
37287      } // invoke the done() callback when everything is finished
37288
37289
37290      this.asyncStream_.push(function () {
37291        // remove pkcs#7 padding from the decrypted bytes
37292        done(null, unpad(decrypted));
37293      });
37294    }
37295    /**
37296     * a getter for step the maximum number of bytes to process at one time
37297     *
37298     * @return {Number} the value of step 32000
37299     */
37300
37301    /**
37302     * @private
37303     */
37304
37305
37306    Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
37307      return function () {
37308        var bytes = decrypt(encrypted, key, initVector);
37309        decrypted.set(bytes, encrypted.byteOffset);
37310      };
37311    };
37312
37313    createClass(Decrypter, null, [{
37314      key: 'STEP',
37315      get: function get$$1() {
37316        // 4 * 8000;
37317        return 32000;
37318      }
37319    }]);
37320    return Decrypter;
37321  }();
37322
37323  /**
37324   * @videojs/http-streaming
37325   * @version 1.11.2
37326   * @copyright 2019 Brightcove, Inc
37327   * @license Apache-2.0
37328   */
37329  /**
37330   * @file resolve-url.js - Handling how URLs are resolved and manipulated
37331   */
37332
37333  var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
37334    // return early if we don't need to resolve
37335    if (/^[a-z]+:/i.test(relativeURL)) {
37336      return relativeURL;
37337    } // if the base URL is relative then combine with the current location
37338
37339
37340    if (!/\/\//i.test(baseURL)) {
37341      baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
37342    }
37343
37344    return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
37345  };
37346  /**
37347   * Checks whether xhr request was redirected and returns correct url depending
37348   * on `handleManifestRedirects` option
37349   *
37350   * @api private
37351   *
37352   * @param  {String} url - an url being requested
37353   * @param  {XMLHttpRequest} req - xhr request result
37354   *
37355   * @return {String}
37356   */
37357
37358
37359  var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
37360    // To understand how the responseURL below is set and generated:
37361    // - https://fetch.spec.whatwg.org/#concept-response-url
37362    // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
37363    if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
37364      return req.responseURL;
37365    }
37366
37367    return url;
37368  };
37369
37370  var classCallCheck$1 = function classCallCheck(instance, Constructor) {
37371    if (!(instance instanceof Constructor)) {
37372      throw new TypeError("Cannot call a class as a function");
37373    }
37374  };
37375
37376  var createClass$1 = function () {
37377    function defineProperties(target, props) {
37378      for (var i = 0; i < props.length; i++) {
37379        var descriptor = props[i];
37380        descriptor.enumerable = descriptor.enumerable || false;
37381        descriptor.configurable = true;
37382        if ("value" in descriptor) descriptor.writable = true;
37383        Object.defineProperty(target, descriptor.key, descriptor);
37384      }
37385    }
37386
37387    return function (Constructor, protoProps, staticProps) {
37388      if (protoProps) defineProperties(Constructor.prototype, protoProps);
37389      if (staticProps) defineProperties(Constructor, staticProps);
37390      return Constructor;
37391    };
37392  }();
37393
37394  var get$1 = function get(object, property, receiver) {
37395    if (object === null) object = Function.prototype;
37396    var desc = Object.getOwnPropertyDescriptor(object, property);
37397
37398    if (desc === undefined) {
37399      var parent = Object.getPrototypeOf(object);
37400
37401      if (parent === null) {
37402        return undefined;
37403      } else {
37404        return get(parent, property, receiver);
37405      }
37406    } else if ("value" in desc) {
37407      return desc.value;
37408    } else {
37409      var getter = desc.get;
37410
37411      if (getter === undefined) {
37412        return undefined;
37413      }
37414
37415      return getter.call(receiver);
37416    }
37417  };
37418
37419  var inherits$2 = function inherits(subClass, superClass) {
37420    if (typeof superClass !== "function" && superClass !== null) {
37421      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
37422    }
37423
37424    subClass.prototype = Object.create(superClass && superClass.prototype, {
37425      constructor: {
37426        value: subClass,
37427        enumerable: false,
37428        writable: true,
37429        configurable: true
37430      }
37431    });
37432    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
37433  };
37434
37435  var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) {
37436    if (!self) {
37437      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
37438    }
37439
37440    return call && (typeof call === "object" || typeof call === "function") ? call : self;
37441  };
37442
37443  var slicedToArray = function () {
37444    function sliceIterator(arr, i) {
37445      var _arr = [];
37446      var _n = true;
37447      var _d = false;
37448      var _e = undefined;
37449
37450      try {
37451        for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
37452          _arr.push(_s.value);
37453
37454          if (i && _arr.length === i) break;
37455        }
37456      } catch (err) {
37457        _d = true;
37458        _e = err;
37459      } finally {
37460        try {
37461          if (!_n && _i["return"]) _i["return"]();
37462        } finally {
37463          if (_d) throw _e;
37464        }
37465      }
37466
37467      return _arr;
37468    }
37469
37470    return function (arr, i) {
37471      if (Array.isArray(arr)) {
37472        return arr;
37473      } else if (Symbol.iterator in Object(arr)) {
37474        return sliceIterator(arr, i);
37475      } else {
37476        throw new TypeError("Invalid attempt to destructure non-iterable instance");
37477      }
37478    };
37479  }();
37480  /**
37481   * @file playlist-loader.js
37482   *
37483   * A state machine that manages the loading, caching, and updating of
37484   * M3U8 playlists.
37485   *
37486   */
37487
37488
37489  var mergeOptions$1 = videojs$1.mergeOptions,
37490      EventTarget$1 = videojs$1.EventTarget,
37491      log$1 = videojs$1.log;
37492  /**
37493   * Loops through all supported media groups in master and calls the provided
37494   * callback for each group
37495   *
37496   * @param {Object} master
37497   *        The parsed master manifest object
37498   * @param {Function} callback
37499   *        Callback to call for each media group
37500   */
37501
37502  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
37503    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
37504      for (var groupKey in master.mediaGroups[mediaType]) {
37505        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
37506          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
37507          callback(mediaProperties, mediaType, groupKey, labelKey);
37508        }
37509      }
37510    });
37511  };
37512  /**
37513    * Returns a new array of segments that is the result of merging
37514    * properties from an older list of segments onto an updated
37515    * list. No properties on the updated playlist will be overridden.
37516    *
37517    * @param {Array} original the outdated list of segments
37518    * @param {Array} update the updated list of segments
37519    * @param {Number=} offset the index of the first update
37520    * segment in the original segment list. For non-live playlists,
37521    * this should always be zero and does not need to be
37522    * specified. For live playlists, it should be the difference
37523    * between the media sequence numbers in the original and updated
37524    * playlists.
37525    * @return a list of merged segment objects
37526    */
37527
37528
37529  var updateSegments = function updateSegments(original, update, offset) {
37530    var result = update.slice();
37531    offset = offset || 0;
37532    var length = Math.min(original.length, update.length + offset);
37533
37534    for (var i = offset; i < length; i++) {
37535      result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
37536    }
37537
37538    return result;
37539  };
37540
37541  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
37542    if (!segment.resolvedUri) {
37543      segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
37544    }
37545
37546    if (segment.key && !segment.key.resolvedUri) {
37547      segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
37548    }
37549
37550    if (segment.map && !segment.map.resolvedUri) {
37551      segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
37552    }
37553  };
37554  /**
37555    * Returns a new master playlist that is the result of merging an
37556    * updated media playlist into the original version. If the
37557    * updated media playlist does not match any of the playlist
37558    * entries in the original master playlist, null is returned.
37559    *
37560    * @param {Object} master a parsed master M3U8 object
37561    * @param {Object} media a parsed media M3U8 object
37562    * @return {Object} a new object that represents the original
37563    * master playlist with the updated media playlist merged in, or
37564    * null if the merge produced no change.
37565    */
37566
37567
37568  var updateMaster = function updateMaster(master, media) {
37569    var result = mergeOptions$1(master, {});
37570    var playlist = result.playlists[media.uri];
37571
37572    if (!playlist) {
37573      return null;
37574    } // consider the playlist unchanged if the number of segments is equal, the media
37575    // sequence number is unchanged, and this playlist hasn't become the end of the playlist
37576
37577
37578    if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
37579      return null;
37580    }
37581
37582    var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
37583
37584    if (playlist.segments) {
37585      mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
37586    } // resolve any segment URIs to prevent us from having to do it later
37587
37588
37589    mergedPlaylist.segments.forEach(function (segment) {
37590      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
37591    }); // TODO Right now in the playlists array there are two references to each playlist, one
37592    // that is referenced by index, and one by URI. The index reference may no longer be
37593    // necessary.
37594
37595    for (var i = 0; i < result.playlists.length; i++) {
37596      if (result.playlists[i].uri === media.uri) {
37597        result.playlists[i] = mergedPlaylist;
37598      }
37599    }
37600
37601    result.playlists[media.uri] = mergedPlaylist;
37602    return result;
37603  };
37604
37605  var setupMediaPlaylists = function setupMediaPlaylists(master) {
37606    // setup by-URI lookups and resolve media playlist URIs
37607    var i = master.playlists.length;
37608
37609    while (i--) {
37610      var playlist = master.playlists[i];
37611      master.playlists[playlist.uri] = playlist;
37612      playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
37613      playlist.id = i;
37614
37615      if (!playlist.attributes) {
37616        // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
37617        // BANDWIDTH attribute, we can play the stream without it. This means a poorly
37618        // formatted master playlist may not have an attribute list. An attributes
37619        // property is added here to prevent undefined references when we encounter
37620        // this scenario.
37621        playlist.attributes = {};
37622        log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
37623      }
37624    }
37625  };
37626
37627  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
37628    forEachMediaGroup(master, function (properties) {
37629      if (properties.uri) {
37630        properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
37631      }
37632    });
37633  };
37634  /**
37635   * Calculates the time to wait before refreshing a live playlist
37636   *
37637   * @param {Object} media
37638   *        The current media
37639   * @param {Boolean} update
37640   *        True if there were any updates from the last refresh, false otherwise
37641   * @return {Number}
37642   *         The time in ms to wait before refreshing the live playlist
37643   */
37644
37645
37646  var refreshDelay = function refreshDelay(media, update) {
37647    var lastSegment = media.segments[media.segments.length - 1];
37648    var delay = void 0;
37649
37650    if (update && lastSegment && lastSegment.duration) {
37651      delay = lastSegment.duration * 1000;
37652    } else {
37653      // if the playlist is unchanged since the last reload or last segment duration
37654      // cannot be determined, try again after half the target duration
37655      delay = (media.targetDuration || 10) * 500;
37656    }
37657
37658    return delay;
37659  };
37660  /**
37661   * Load a playlist from a remote location
37662   *
37663   * @class PlaylistLoader
37664   * @extends Stream
37665   * @param {String} srcUrl the url to start with
37666   * @param {Boolean} withCredentials the withCredentials xhr option
37667   * @constructor
37668   */
37669
37670
37671  var PlaylistLoader = function (_EventTarget) {
37672    inherits$2(PlaylistLoader, _EventTarget);
37673
37674    function PlaylistLoader(srcUrl, hls) {
37675      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
37676      classCallCheck$1(this, PlaylistLoader);
37677
37678      var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
37679
37680      var _options$withCredenti = options.withCredentials,
37681          withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
37682          _options$handleManife = options.handleManifestRedirects,
37683          handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
37684      _this.srcUrl = srcUrl;
37685      _this.hls_ = hls;
37686      _this.withCredentials = withCredentials;
37687      _this.handleManifestRedirects = handleManifestRedirects;
37688      var hlsOptions = hls.options_;
37689      _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
37690      _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
37691
37692      if (!_this.srcUrl) {
37693        throw new Error('A non-empty playlist URL is required');
37694      } // initialize the loader state
37695
37696
37697      _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
37698
37699      _this.on('mediaupdatetimeout', function () {
37700        if (_this.state !== 'HAVE_METADATA') {
37701          // only refresh the media playlist if no other activity is going on
37702          return;
37703        }
37704
37705        _this.state = 'HAVE_CURRENT_METADATA';
37706        _this.request = _this.hls_.xhr({
37707          uri: resolveUrl$1(_this.master.uri, _this.media().uri),
37708          withCredentials: _this.withCredentials
37709        }, function (error, req) {
37710          // disposed
37711          if (!_this.request) {
37712            return;
37713          }
37714
37715          if (error) {
37716            return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
37717          }
37718
37719          _this.haveMetadata(_this.request, _this.media().uri);
37720        });
37721      });
37722
37723      return _this;
37724    }
37725
37726    createClass$1(PlaylistLoader, [{
37727      key: 'playlistRequestError',
37728      value: function playlistRequestError(xhr, url, startingState) {
37729        // any in-flight request is now finished
37730        this.request = null;
37731
37732        if (startingState) {
37733          this.state = startingState;
37734        }
37735
37736        this.error = {
37737          playlist: this.master.playlists[url],
37738          status: xhr.status,
37739          message: 'HLS playlist request error at URL: ' + url + '.',
37740          responseText: xhr.responseText,
37741          code: xhr.status >= 500 ? 4 : 2
37742        };
37743        this.trigger('error');
37744      } // update the playlist loader's state in response to a new or
37745      // updated playlist.
37746
37747    }, {
37748      key: 'haveMetadata',
37749      value: function haveMetadata(xhr, url) {
37750        var _this2 = this; // any in-flight request is now finished
37751
37752
37753        this.request = null;
37754        this.state = 'HAVE_METADATA';
37755        var parser = new Parser(); // adding custom tag parsers
37756
37757        this.customTagParsers.forEach(function (customParser) {
37758          return parser.addParser(customParser);
37759        }); // adding custom tag mappers
37760
37761        this.customTagMappers.forEach(function (mapper) {
37762          return parser.addTagMapper(mapper);
37763        });
37764        parser.push(xhr.responseText);
37765        parser.end();
37766        parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
37767        // sure that the property is attached to avoid undefined reference errors
37768
37769        parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
37770
37771        var update = updateMaster(this.master, parser.manifest);
37772        this.targetDuration = parser.manifest.targetDuration;
37773
37774        if (update) {
37775          this.master = update;
37776          this.media_ = this.master.playlists[parser.manifest.uri];
37777        } else {
37778          this.trigger('playlistunchanged');
37779        } // refresh live playlists after a target duration passes
37780
37781
37782        if (!this.media().endList) {
37783          window$1.clearTimeout(this.mediaUpdateTimeout);
37784          this.mediaUpdateTimeout = window$1.setTimeout(function () {
37785            _this2.trigger('mediaupdatetimeout');
37786          }, refreshDelay(this.media(), !!update));
37787        }
37788
37789        this.trigger('loadedplaylist');
37790      }
37791      /**
37792       * Abort any outstanding work and clean up.
37793       */
37794
37795    }, {
37796      key: 'dispose',
37797      value: function dispose() {
37798        this.stopRequest();
37799        window$1.clearTimeout(this.mediaUpdateTimeout);
37800        window$1.clearTimeout(this.finalRenditionTimeout);
37801      }
37802    }, {
37803      key: 'stopRequest',
37804      value: function stopRequest() {
37805        if (this.request) {
37806          var oldRequest = this.request;
37807          this.request = null;
37808          oldRequest.onreadystatechange = null;
37809          oldRequest.abort();
37810        }
37811      }
37812      /**
37813       * When called without any arguments, returns the currently
37814       * active media playlist. When called with a single argument,
37815       * triggers the playlist loader to asynchronously switch to the
37816       * specified media playlist. Calling this method while the
37817       * loader is in the HAVE_NOTHING causes an error to be emitted
37818       * but otherwise has no effect.
37819       *
37820       * @param {Object=} playlist the parsed media playlist
37821       * object to switch to
37822       * @param {Boolean=} is this the last available playlist
37823       *
37824       * @return {Playlist} the current loaded media
37825       */
37826
37827    }, {
37828      key: 'media',
37829      value: function media(playlist, isFinalRendition) {
37830        var _this3 = this; // getter
37831
37832
37833        if (!playlist) {
37834          return this.media_;
37835        } // setter
37836
37837
37838        if (this.state === 'HAVE_NOTHING') {
37839          throw new Error('Cannot switch media playlist from ' + this.state);
37840        } // find the playlist object if the target playlist has been
37841        // specified by URI
37842
37843
37844        if (typeof playlist === 'string') {
37845          if (!this.master.playlists[playlist]) {
37846            throw new Error('Unknown playlist URI: ' + playlist);
37847          }
37848
37849          playlist = this.master.playlists[playlist];
37850        }
37851
37852        window$1.clearTimeout(this.finalRenditionTimeout);
37853
37854        if (isFinalRendition) {
37855          var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000;
37856          this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay);
37857          return;
37858        }
37859
37860        var startingState = this.state;
37861        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
37862
37863        if (this.master.playlists[playlist.uri].endList) {
37864          // abort outstanding playlist requests
37865          if (this.request) {
37866            this.request.onreadystatechange = null;
37867            this.request.abort();
37868            this.request = null;
37869          }
37870
37871          this.state = 'HAVE_METADATA';
37872          this.media_ = playlist; // trigger media change if the active media has been updated
37873
37874          if (mediaChange) {
37875            this.trigger('mediachanging');
37876            this.trigger('mediachange');
37877          }
37878
37879          return;
37880        } // switching to the active playlist is a no-op
37881
37882
37883        if (!mediaChange) {
37884          return;
37885        }
37886
37887        this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
37888
37889        if (this.request) {
37890          if (playlist.resolvedUri === this.request.url) {
37891            // requesting to switch to the same playlist multiple times
37892            // has no effect after the first
37893            return;
37894          }
37895
37896          this.request.onreadystatechange = null;
37897          this.request.abort();
37898          this.request = null;
37899        } // request the new playlist
37900
37901
37902        if (this.media_) {
37903          this.trigger('mediachanging');
37904        }
37905
37906        this.request = this.hls_.xhr({
37907          uri: playlist.resolvedUri,
37908          withCredentials: this.withCredentials
37909        }, function (error, req) {
37910          // disposed
37911          if (!_this3.request) {
37912            return;
37913          }
37914
37915          playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
37916
37917          if (error) {
37918            return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
37919          }
37920
37921          _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
37922
37923
37924          if (startingState === 'HAVE_MASTER') {
37925            _this3.trigger('loadedmetadata');
37926          } else {
37927            _this3.trigger('mediachange');
37928          }
37929        });
37930      }
37931      /**
37932       * pause loading of the playlist
37933       */
37934
37935    }, {
37936      key: 'pause',
37937      value: function pause() {
37938        this.stopRequest();
37939        window$1.clearTimeout(this.mediaUpdateTimeout);
37940
37941        if (this.state === 'HAVE_NOTHING') {
37942          // If we pause the loader before any data has been retrieved, its as if we never
37943          // started, so reset to an unstarted state.
37944          this.started = false;
37945        } // Need to restore state now that no activity is happening
37946
37947
37948        if (this.state === 'SWITCHING_MEDIA') {
37949          // if the loader was in the process of switching media, it should either return to
37950          // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
37951          // playlist yet. This is determined by the existence of loader.media_
37952          if (this.media_) {
37953            this.state = 'HAVE_METADATA';
37954          } else {
37955            this.state = 'HAVE_MASTER';
37956          }
37957        } else if (this.state === 'HAVE_CURRENT_METADATA') {
37958          this.state = 'HAVE_METADATA';
37959        }
37960      }
37961      /**
37962       * start loading of the playlist
37963       */
37964
37965    }, {
37966      key: 'load',
37967      value: function load(isFinalRendition) {
37968        var _this4 = this;
37969
37970        window$1.clearTimeout(this.mediaUpdateTimeout);
37971        var media = this.media();
37972
37973        if (isFinalRendition) {
37974          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
37975          this.mediaUpdateTimeout = window$1.setTimeout(function () {
37976            return _this4.load();
37977          }, delay);
37978          return;
37979        }
37980
37981        if (!this.started) {
37982          this.start();
37983          return;
37984        }
37985
37986        if (media && !media.endList) {
37987          this.trigger('mediaupdatetimeout');
37988        } else {
37989          this.trigger('loadedplaylist');
37990        }
37991      }
37992      /**
37993       * start loading of the playlist
37994       */
37995
37996    }, {
37997      key: 'start',
37998      value: function start() {
37999        var _this5 = this;
38000
38001        this.started = true; // request the specified URL
38002
38003        this.request = this.hls_.xhr({
38004          uri: this.srcUrl,
38005          withCredentials: this.withCredentials
38006        }, function (error, req) {
38007          // disposed
38008          if (!_this5.request) {
38009            return;
38010          } // clear the loader's request reference
38011
38012
38013          _this5.request = null;
38014
38015          if (error) {
38016            _this5.error = {
38017              status: req.status,
38018              message: 'HLS playlist request error at URL: ' + _this5.srcUrl + '.',
38019              responseText: req.responseText,
38020              // MEDIA_ERR_NETWORK
38021              code: 2
38022            };
38023
38024            if (_this5.state === 'HAVE_NOTHING') {
38025              _this5.started = false;
38026            }
38027
38028            return _this5.trigger('error');
38029          }
38030
38031          var parser = new Parser(); // adding custom tag parsers
38032
38033          _this5.customTagParsers.forEach(function (customParser) {
38034            return parser.addParser(customParser);
38035          }); // adding custom tag mappers
38036
38037
38038          _this5.customTagMappers.forEach(function (mapper) {
38039            return parser.addTagMapper(mapper);
38040          });
38041
38042          parser.push(req.responseText);
38043          parser.end();
38044          _this5.state = 'HAVE_MASTER';
38045          _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
38046          parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
38047
38048          if (parser.manifest.playlists) {
38049            _this5.master = parser.manifest;
38050            setupMediaPlaylists(_this5.master);
38051            resolveMediaGroupUris(_this5.master);
38052
38053            _this5.trigger('loadedplaylist');
38054
38055            if (!_this5.request) {
38056              // no media playlist was specifically selected so start
38057              // from the first listed one
38058              _this5.media(parser.manifest.playlists[0]);
38059            }
38060
38061            return;
38062          } // loaded a media playlist
38063          // infer a master playlist if none was previously requested
38064
38065
38066          _this5.master = {
38067            mediaGroups: {
38068              'AUDIO': {},
38069              'VIDEO': {},
38070              'CLOSED-CAPTIONS': {},
38071              'SUBTITLES': {}
38072            },
38073            uri: window$1.location.href,
38074            playlists: [{
38075              uri: _this5.srcUrl,
38076              id: 0,
38077              resolvedUri: _this5.srcUrl,
38078              // m3u8-parser does not attach an attributes property to media playlists so make
38079              // sure that the property is attached to avoid undefined reference errors
38080              attributes: {}
38081            }]
38082          };
38083          _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
38084
38085          _this5.haveMetadata(req, _this5.srcUrl);
38086
38087          return _this5.trigger('loadedmetadata');
38088        });
38089      }
38090    }]);
38091    return PlaylistLoader;
38092  }(EventTarget$1);
38093  /**
38094   * @file playlist.js
38095   *
38096   * Playlist related utilities.
38097   */
38098
38099
38100  var createTimeRange = videojs$1.createTimeRange;
38101  /**
38102   * walk backward until we find a duration we can use
38103   * or return a failure
38104   *
38105   * @param {Playlist} playlist the playlist to walk through
38106   * @param {Number} endSequence the mediaSequence to stop walking on
38107   */
38108
38109  var backwardDuration = function backwardDuration(playlist, endSequence) {
38110    var result = 0;
38111    var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
38112    // the interval, use it
38113
38114    var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
38115    // information that is earlier than endSequence
38116
38117    if (segment) {
38118      if (typeof segment.start !== 'undefined') {
38119        return {
38120          result: segment.start,
38121          precise: true
38122        };
38123      }
38124
38125      if (typeof segment.end !== 'undefined') {
38126        return {
38127          result: segment.end - segment.duration,
38128          precise: true
38129        };
38130      }
38131    }
38132
38133    while (i--) {
38134      segment = playlist.segments[i];
38135
38136      if (typeof segment.end !== 'undefined') {
38137        return {
38138          result: result + segment.end,
38139          precise: true
38140        };
38141      }
38142
38143      result += segment.duration;
38144
38145      if (typeof segment.start !== 'undefined') {
38146        return {
38147          result: result + segment.start,
38148          precise: true
38149        };
38150      }
38151    }
38152
38153    return {
38154      result: result,
38155      precise: false
38156    };
38157  };
38158  /**
38159   * walk forward until we find a duration we can use
38160   * or return a failure
38161   *
38162   * @param {Playlist} playlist the playlist to walk through
38163   * @param {Number} endSequence the mediaSequence to stop walking on
38164   */
38165
38166
38167  var forwardDuration = function forwardDuration(playlist, endSequence) {
38168    var result = 0;
38169    var segment = void 0;
38170    var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
38171    // information
38172
38173    for (; i < playlist.segments.length; i++) {
38174      segment = playlist.segments[i];
38175
38176      if (typeof segment.start !== 'undefined') {
38177        return {
38178          result: segment.start - result,
38179          precise: true
38180        };
38181      }
38182
38183      result += segment.duration;
38184
38185      if (typeof segment.end !== 'undefined') {
38186        return {
38187          result: segment.end - result,
38188          precise: true
38189        };
38190      }
38191    } // indicate we didn't find a useful duration estimate
38192
38193
38194    return {
38195      result: -1,
38196      precise: false
38197    };
38198  };
38199  /**
38200    * Calculate the media duration from the segments associated with a
38201    * playlist. The duration of a subinterval of the available segments
38202    * may be calculated by specifying an end index.
38203    *
38204    * @param {Object} playlist a media playlist object
38205    * @param {Number=} endSequence an exclusive upper boundary
38206    * for the playlist.  Defaults to playlist length.
38207    * @param {Number} expired the amount of time that has dropped
38208    * off the front of the playlist in a live scenario
38209    * @return {Number} the duration between the first available segment
38210    * and end index.
38211    */
38212
38213
38214  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
38215    var backward = void 0;
38216    var forward = void 0;
38217
38218    if (typeof endSequence === 'undefined') {
38219      endSequence = playlist.mediaSequence + playlist.segments.length;
38220    }
38221
38222    if (endSequence < playlist.mediaSequence) {
38223      return 0;
38224    } // do a backward walk to estimate the duration
38225
38226
38227    backward = backwardDuration(playlist, endSequence);
38228
38229    if (backward.precise) {
38230      // if we were able to base our duration estimate on timing
38231      // information provided directly from the Media Source, return
38232      // it
38233      return backward.result;
38234    } // walk forward to see if a precise duration estimate can be made
38235    // that way
38236
38237
38238    forward = forwardDuration(playlist, endSequence);
38239
38240    if (forward.precise) {
38241      // we found a segment that has been buffered and so it's
38242      // position is known precisely
38243      return forward.result;
38244    } // return the less-precise, playlist-based duration estimate
38245
38246
38247    return backward.result + expired;
38248  };
38249  /**
38250    * Calculates the duration of a playlist. If a start and end index
38251    * are specified, the duration will be for the subset of the media
38252    * timeline between those two indices. The total duration for live
38253    * playlists is always Infinity.
38254    *
38255    * @param {Object} playlist a media playlist object
38256    * @param {Number=} endSequence an exclusive upper
38257    * boundary for the playlist. Defaults to the playlist media
38258    * sequence number plus its length.
38259    * @param {Number=} expired the amount of time that has
38260    * dropped off the front of the playlist in a live scenario
38261    * @return {Number} the duration between the start index and end
38262    * index.
38263    */
38264
38265
38266  var duration = function duration(playlist, endSequence, expired) {
38267    if (!playlist) {
38268      return 0;
38269    }
38270
38271    if (typeof expired !== 'number') {
38272      expired = 0;
38273    } // if a slice of the total duration is not requested, use
38274    // playlist-level duration indicators when they're present
38275
38276
38277    if (typeof endSequence === 'undefined') {
38278      // if present, use the duration specified in the playlist
38279      if (playlist.totalDuration) {
38280        return playlist.totalDuration;
38281      } // duration should be Infinity for live playlists
38282
38283
38284      if (!playlist.endList) {
38285        return window$1.Infinity;
38286      }
38287    } // calculate the total duration based on the segment durations
38288
38289
38290    return intervalDuration(playlist, endSequence, expired);
38291  };
38292  /**
38293    * Calculate the time between two indexes in the current playlist
38294    * neight the start- nor the end-index need to be within the current
38295    * playlist in which case, the targetDuration of the playlist is used
38296    * to approximate the durations of the segments
38297    *
38298    * @param {Object} playlist a media playlist object
38299    * @param {Number} startIndex
38300    * @param {Number} endIndex
38301    * @return {Number} the number of seconds between startIndex and endIndex
38302    */
38303
38304
38305  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
38306    var durations = 0;
38307
38308    if (startIndex > endIndex) {
38309      var _ref = [endIndex, startIndex];
38310      startIndex = _ref[0];
38311      endIndex = _ref[1];
38312    }
38313
38314    if (startIndex < 0) {
38315      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
38316        durations += playlist.targetDuration;
38317      }
38318
38319      startIndex = 0;
38320    }
38321
38322    for (var _i = startIndex; _i < endIndex; _i++) {
38323      durations += playlist.segments[_i].duration;
38324    }
38325
38326    return durations;
38327  };
38328  /**
38329   * Determines the media index of the segment corresponding to the safe edge of the live
38330   * window which is the duration of the last segment plus 2 target durations from the end
38331   * of the playlist.
38332   *
38333   * @param {Object} playlist
38334   *        a media playlist object
38335   * @return {Number}
38336   *         The media index of the segment at the safe live point. 0 if there is no "safe"
38337   *         point.
38338   * @function safeLiveIndex
38339   */
38340
38341
38342  var safeLiveIndex = function safeLiveIndex(playlist) {
38343    if (!playlist.segments.length) {
38344      return 0;
38345    }
38346
38347    var i = playlist.segments.length - 1;
38348    var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
38349    var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
38350
38351    while (i--) {
38352      distanceFromEnd += playlist.segments[i].duration;
38353
38354      if (distanceFromEnd >= safeDistance) {
38355        break;
38356      }
38357    }
38358
38359    return Math.max(0, i);
38360  };
38361  /**
38362   * Calculates the playlist end time
38363   *
38364   * @param {Object} playlist a media playlist object
38365   * @param {Number=} expired the amount of time that has
38366   *                  dropped off the front of the playlist in a live scenario
38367   * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
38368   *                        playlist end calculation should consider the safe live end
38369   *                        (truncate the playlist end by three segments). This is normally
38370   *                        used for calculating the end of the playlist's seekable range.
38371   * @returns {Number} the end time of playlist
38372   * @function playlistEnd
38373   */
38374
38375
38376  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
38377    if (!playlist || !playlist.segments) {
38378      return null;
38379    }
38380
38381    if (playlist.endList) {
38382      return duration(playlist);
38383    }
38384
38385    if (expired === null) {
38386      return null;
38387    }
38388
38389    expired = expired || 0;
38390    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
38391    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
38392  };
38393  /**
38394    * Calculates the interval of time that is currently seekable in a
38395    * playlist. The returned time ranges are relative to the earliest
38396    * moment in the specified playlist that is still available. A full
38397    * seekable implementation for live streams would need to offset
38398    * these values by the duration of content that has expired from the
38399    * stream.
38400    *
38401    * @param {Object} playlist a media playlist object
38402    * dropped off the front of the playlist in a live scenario
38403    * @param {Number=} expired the amount of time that has
38404    * dropped off the front of the playlist in a live scenario
38405    * @return {TimeRanges} the periods of time that are valid targets
38406    * for seeking
38407    */
38408
38409
38410  var seekable = function seekable(playlist, expired) {
38411    var useSafeLiveEnd = true;
38412    var seekableStart = expired || 0;
38413    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
38414
38415    if (seekableEnd === null) {
38416      return createTimeRange();
38417    }
38418
38419    return createTimeRange(seekableStart, seekableEnd);
38420  };
38421
38422  var isWholeNumber = function isWholeNumber(num) {
38423    return num - Math.floor(num) === 0;
38424  };
38425
38426  var roundSignificantDigit = function roundSignificantDigit(increment, num) {
38427    // If we have a whole number, just add 1 to it
38428    if (isWholeNumber(num)) {
38429      return num + increment * 0.1;
38430    }
38431
38432    var numDecimalDigits = num.toString().split('.')[1].length;
38433
38434    for (var i = 1; i <= numDecimalDigits; i++) {
38435      var scale = Math.pow(10, i);
38436      var temp = num * scale;
38437
38438      if (isWholeNumber(temp) || i === numDecimalDigits) {
38439        return (temp + increment) / scale;
38440      }
38441    }
38442  };
38443
38444  var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
38445  var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
38446  /**
38447   * Determine the index and estimated starting time of the segment that
38448   * contains a specified playback position in a media playlist.
38449   *
38450   * @param {Object} playlist the media playlist to query
38451   * @param {Number} currentTime The number of seconds since the earliest
38452   * possible position to determine the containing segment for
38453   * @param {Number} startIndex
38454   * @param {Number} startTime
38455   * @return {Object}
38456   */
38457
38458  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
38459    var i = void 0;
38460    var segment = void 0;
38461    var numSegments = playlist.segments.length;
38462    var time = currentTime - startTime;
38463
38464    if (time < 0) {
38465      // Walk backward from startIndex in the playlist, adding durations
38466      // until we find a segment that contains `time` and return it
38467      if (startIndex > 0) {
38468        for (i = startIndex - 1; i >= 0; i--) {
38469          segment = playlist.segments[i];
38470          time += floorLeastSignificantDigit(segment.duration);
38471
38472          if (time > 0) {
38473            return {
38474              mediaIndex: i,
38475              startTime: startTime - sumDurations(playlist, startIndex, i)
38476            };
38477          }
38478        }
38479      } // We were unable to find a good segment within the playlist
38480      // so select the first segment
38481
38482
38483      return {
38484        mediaIndex: 0,
38485        startTime: currentTime
38486      };
38487    } // When startIndex is negative, we first walk forward to first segment
38488    // adding target durations. If we "run out of time" before getting to
38489    // the first segment, return the first segment
38490
38491
38492    if (startIndex < 0) {
38493      for (i = startIndex; i < 0; i++) {
38494        time -= playlist.targetDuration;
38495
38496        if (time < 0) {
38497          return {
38498            mediaIndex: 0,
38499            startTime: currentTime
38500          };
38501        }
38502      }
38503
38504      startIndex = 0;
38505    } // Walk forward from startIndex in the playlist, subtracting durations
38506    // until we find a segment that contains `time` and return it
38507
38508
38509    for (i = startIndex; i < numSegments; i++) {
38510      segment = playlist.segments[i];
38511      time -= ceilLeastSignificantDigit(segment.duration);
38512
38513      if (time < 0) {
38514        return {
38515          mediaIndex: i,
38516          startTime: startTime + sumDurations(playlist, startIndex, i)
38517        };
38518      }
38519    } // We are out of possible candidates so load the last one...
38520
38521
38522    return {
38523      mediaIndex: numSegments - 1,
38524      startTime: currentTime
38525    };
38526  };
38527  /**
38528   * Check whether the playlist is blacklisted or not.
38529   *
38530   * @param {Object} playlist the media playlist object
38531   * @return {boolean} whether the playlist is blacklisted or not
38532   * @function isBlacklisted
38533   */
38534
38535
38536  var isBlacklisted = function isBlacklisted(playlist) {
38537    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
38538  };
38539  /**
38540   * Check whether the playlist is compatible with current playback configuration or has
38541   * been blacklisted permanently for being incompatible.
38542   *
38543   * @param {Object} playlist the media playlist object
38544   * @return {boolean} whether the playlist is incompatible or not
38545   * @function isIncompatible
38546   */
38547
38548
38549  var isIncompatible = function isIncompatible(playlist) {
38550    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
38551  };
38552  /**
38553   * Check whether the playlist is enabled or not.
38554   *
38555   * @param {Object} playlist the media playlist object
38556   * @return {boolean} whether the playlist is enabled or not
38557   * @function isEnabled
38558   */
38559
38560
38561  var isEnabled = function isEnabled(playlist) {
38562    var blacklisted = isBlacklisted(playlist);
38563    return !playlist.disabled && !blacklisted;
38564  };
38565  /**
38566   * Check whether the playlist has been manually disabled through the representations api.
38567   *
38568   * @param {Object} playlist the media playlist object
38569   * @return {boolean} whether the playlist is disabled manually or not
38570   * @function isDisabled
38571   */
38572
38573
38574  var isDisabled = function isDisabled(playlist) {
38575    return playlist.disabled;
38576  };
38577  /**
38578   * Returns whether the current playlist is an AES encrypted HLS stream
38579   *
38580   * @return {Boolean} true if it's an AES encrypted HLS stream
38581   */
38582
38583
38584  var isAes = function isAes(media) {
38585    for (var i = 0; i < media.segments.length; i++) {
38586      if (media.segments[i].key) {
38587        return true;
38588      }
38589    }
38590
38591    return false;
38592  };
38593  /**
38594   * Returns whether the current playlist contains fMP4
38595   *
38596   * @return {Boolean} true if the playlist contains fMP4
38597   */
38598
38599
38600  var isFmp4 = function isFmp4(media) {
38601    for (var i = 0; i < media.segments.length; i++) {
38602      if (media.segments[i].map) {
38603        return true;
38604      }
38605    }
38606
38607    return false;
38608  };
38609  /**
38610   * Checks if the playlist has a value for the specified attribute
38611   *
38612   * @param {String} attr
38613   *        Attribute to check for
38614   * @param {Object} playlist
38615   *        The media playlist object
38616   * @return {Boolean}
38617   *         Whether the playlist contains a value for the attribute or not
38618   * @function hasAttribute
38619   */
38620
38621
38622  var hasAttribute = function hasAttribute(attr, playlist) {
38623    return playlist.attributes && playlist.attributes[attr];
38624  };
38625  /**
38626   * Estimates the time required to complete a segment download from the specified playlist
38627   *
38628   * @param {Number} segmentDuration
38629   *        Duration of requested segment
38630   * @param {Number} bandwidth
38631   *        Current measured bandwidth of the player
38632   * @param {Object} playlist
38633   *        The media playlist object
38634   * @param {Number=} bytesReceived
38635   *        Number of bytes already received for the request. Defaults to 0
38636   * @return {Number|NaN}
38637   *         The estimated time to request the segment. NaN if bandwidth information for
38638   *         the given playlist is unavailable
38639   * @function estimateSegmentRequestTime
38640   */
38641
38642
38643  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
38644    var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
38645
38646    if (!hasAttribute('BANDWIDTH', playlist)) {
38647      return NaN;
38648    }
38649
38650    var size = segmentDuration * playlist.attributes.BANDWIDTH;
38651    return (size - bytesReceived * 8) / bandwidth;
38652  };
38653  /*
38654   * Returns whether the current playlist is the lowest rendition
38655   *
38656   * @return {Boolean} true if on lowest rendition
38657   */
38658
38659
38660  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
38661    if (master.playlists.length === 1) {
38662      return true;
38663    }
38664
38665    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
38666    return master.playlists.filter(function (playlist) {
38667      if (!isEnabled(playlist)) {
38668        return false;
38669      }
38670
38671      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
38672    }).length === 0;
38673  }; // exports
38674
38675
38676  var Playlist = {
38677    duration: duration,
38678    seekable: seekable,
38679    safeLiveIndex: safeLiveIndex,
38680    getMediaInfoForTime: getMediaInfoForTime,
38681    isEnabled: isEnabled,
38682    isDisabled: isDisabled,
38683    isBlacklisted: isBlacklisted,
38684    isIncompatible: isIncompatible,
38685    playlistEnd: playlistEnd,
38686    isAes: isAes,
38687    isFmp4: isFmp4,
38688    hasAttribute: hasAttribute,
38689    estimateSegmentRequestTime: estimateSegmentRequestTime,
38690    isLowestEnabledRendition: isLowestEnabledRendition
38691  };
38692  /**
38693   * @file xhr.js
38694   */
38695
38696  var videojsXHR = videojs$1.xhr,
38697      mergeOptions$1$1 = videojs$1.mergeOptions;
38698
38699  var xhrFactory = function xhrFactory() {
38700    var xhr = function XhrFunction(options, callback) {
38701      // Add a default timeout for all hls requests
38702      options = mergeOptions$1$1({
38703        timeout: 45e3
38704      }, options); // Allow an optional user-specified function to modify the option
38705      // object before we construct the xhr request
38706
38707      var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
38708
38709      if (beforeRequest && typeof beforeRequest === 'function') {
38710        var newOptions = beforeRequest(options);
38711
38712        if (newOptions) {
38713          options = newOptions;
38714        }
38715      }
38716
38717      var request = videojsXHR(options, function (error, response) {
38718        var reqResponse = request.response;
38719
38720        if (!error && reqResponse) {
38721          request.responseTime = Date.now();
38722          request.roundTripTime = request.responseTime - request.requestTime;
38723          request.bytesReceived = reqResponse.byteLength || reqResponse.length;
38724
38725          if (!request.bandwidth) {
38726            request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
38727          }
38728        }
38729
38730        if (response.headers) {
38731          request.responseHeaders = response.headers;
38732        } // videojs.xhr now uses a specific code on the error
38733        // object to signal that a request has timed out instead
38734        // of setting a boolean on the request object
38735
38736
38737        if (error && error.code === 'ETIMEDOUT') {
38738          request.timedout = true;
38739        } // videojs.xhr no longer considers status codes outside of 200 and 0
38740        // (for file uris) to be errors, but the old XHR did, so emulate that
38741        // behavior. Status 206 may be used in response to byterange requests.
38742
38743
38744        if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
38745          error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
38746        }
38747
38748        callback(error, request);
38749      });
38750      var originalAbort = request.abort;
38751
38752      request.abort = function () {
38753        request.aborted = true;
38754        return originalAbort.apply(request, arguments);
38755      };
38756
38757      request.uri = options.uri;
38758      request.requestTime = Date.now();
38759      return request;
38760    };
38761
38762    return xhr;
38763  };
38764  /**
38765   * Turns segment byterange into a string suitable for use in
38766   * HTTP Range requests
38767   *
38768   * @param {Object} byterange - an object with two values defining the start and end
38769   *                             of a byte-range
38770   */
38771
38772
38773  var byterangeStr = function byterangeStr(byterange) {
38774    var byterangeStart = void 0;
38775    var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
38776    // header uses inclusive ranges
38777
38778    byterangeEnd = byterange.offset + byterange.length - 1;
38779    byterangeStart = byterange.offset;
38780    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
38781  };
38782  /**
38783   * Defines headers for use in the xhr request for a particular segment.
38784   *
38785   * @param {Object} segment - a simplified copy of the segmentInfo object
38786   *                           from SegmentLoader
38787   */
38788
38789
38790  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
38791    var headers = {};
38792
38793    if (segment.byterange) {
38794      headers.Range = byterangeStr(segment.byterange);
38795    }
38796
38797    return headers;
38798  };
38799  /**
38800   * @file bin-utils.js
38801   */
38802
38803  /**
38804   * convert a TimeRange to text
38805   *
38806   * @param {TimeRange} range the timerange to use for conversion
38807   * @param {Number} i the iterator on the range to convert
38808   */
38809
38810
38811  var textRange = function textRange(range, i) {
38812    return range.start(i) + '-' + range.end(i);
38813  };
38814  /**
38815   * format a number as hex string
38816   *
38817   * @param {Number} e The number
38818   * @param {Number} i the iterator
38819   */
38820
38821
38822  var formatHexString = function formatHexString(e, i) {
38823    var value = e.toString(16);
38824    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
38825  };
38826
38827  var formatAsciiString = function formatAsciiString(e) {
38828    if (e >= 0x20 && e < 0x7e) {
38829      return String.fromCharCode(e);
38830    }
38831
38832    return '.';
38833  };
38834  /**
38835   * Creates an object for sending to a web worker modifying properties that are TypedArrays
38836   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
38837   *
38838   * @param {Object} message
38839   *        Object of properties and values to send to the web worker
38840   * @return {Object}
38841   *         Modified message with TypedArray values expanded
38842   * @function createTransferableMessage
38843   */
38844
38845
38846  var createTransferableMessage = function createTransferableMessage(message) {
38847    var transferable = {};
38848    Object.keys(message).forEach(function (key) {
38849      var value = message[key];
38850
38851      if (ArrayBuffer.isView(value)) {
38852        transferable[key] = {
38853          bytes: value.buffer,
38854          byteOffset: value.byteOffset,
38855          byteLength: value.byteLength
38856        };
38857      } else {
38858        transferable[key] = value;
38859      }
38860    });
38861    return transferable;
38862  };
38863  /**
38864   * Returns a unique string identifier for a media initialization
38865   * segment.
38866   */
38867
38868
38869  var initSegmentId = function initSegmentId(initSegment) {
38870    var byterange = initSegment.byterange || {
38871      length: Infinity,
38872      offset: 0
38873    };
38874    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
38875  };
38876  /**
38877   * Returns a unique string identifier for a media segment key.
38878   */
38879
38880
38881  var segmentKeyId = function segmentKeyId(key) {
38882    return key.resolvedUri;
38883  };
38884  /**
38885   * utils to help dump binary data to the console
38886   */
38887
38888
38889  var hexDump = function hexDump(data) {
38890    var bytes = Array.prototype.slice.call(data);
38891    var step = 16;
38892    var result = '';
38893    var hex = void 0;
38894    var ascii = void 0;
38895
38896    for (var j = 0; j < bytes.length / step; j++) {
38897      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
38898      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
38899      result += hex + ' ' + ascii + '\n';
38900    }
38901
38902    return result;
38903  };
38904
38905  var tagDump = function tagDump(_ref) {
38906    var bytes = _ref.bytes;
38907    return hexDump(bytes);
38908  };
38909
38910  var textRanges = function textRanges(ranges) {
38911    var result = '';
38912    var i = void 0;
38913
38914    for (i = 0; i < ranges.length; i++) {
38915      result += textRange(ranges, i) + ' ';
38916    }
38917
38918    return result;
38919  };
38920
38921  var utils$1 =
38922  /*#__PURE__*/
38923  Object.freeze({
38924    createTransferableMessage: createTransferableMessage,
38925    initSegmentId: initSegmentId,
38926    segmentKeyId: segmentKeyId,
38927    hexDump: hexDump,
38928    tagDump: tagDump,
38929    textRanges: textRanges
38930  }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
38931  // Add 25% to the segment duration to account for small discrepencies in segment timing.
38932  // 25% was arbitrarily chosen, and may need to be refined over time.
38933
38934  var SEGMENT_END_FUDGE_PERCENT = 0.25;
38935  /**
38936   * Converts a player time (any time that can be gotten/set from player.currentTime(),
38937   * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
38938   * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
38939   *
38940   * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
38941   * point" (a point where we have a mapping from program time to player time, with player
38942   * time being the post transmux start of the segment).
38943   *
38944   * For more details, see [this doc](../../docs/program-time-from-player-time.md).
38945   *
38946   * @param {Number} playerTime the player time
38947   * @param {Object} segment the segment which contains the player time
38948   * @return {Date} program time
38949   */
38950
38951  var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
38952    if (!segment.dateTimeObject) {
38953      // Can't convert without an "anchor point" for the program time (i.e., a time that can
38954      // be used to map the start of a segment with a real world time).
38955      return null;
38956    }
38957
38958    var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
38959    var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
38960
38961    var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
38962    var offsetFromSegmentStart = playerTime - startOfSegment;
38963    return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
38964  };
38965
38966  var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
38967    return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
38968  };
38969  /**
38970   * Finds a segment that contains the time requested given as an ISO-8601 string. The
38971   * returned segment might be an estimate or an accurate match.
38972   *
38973   * @param {String} programTime The ISO-8601 programTime to find a match for
38974   * @param {Object} playlist A playlist object to search within
38975   */
38976
38977
38978  var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
38979    // Assumptions:
38980    //  - verifyProgramDateTimeTags has already been run
38981    //  - live streams have been started
38982    var dateTimeObject = void 0;
38983
38984    try {
38985      dateTimeObject = new Date(programTime);
38986    } catch (e) {
38987      return null;
38988    }
38989
38990    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
38991      return null;
38992    }
38993
38994    var segment = playlist.segments[0];
38995
38996    if (dateTimeObject < segment.dateTimeObject) {
38997      // Requested time is before stream start.
38998      return null;
38999    }
39000
39001    for (var i = 0; i < playlist.segments.length - 1; i++) {
39002      segment = playlist.segments[i];
39003      var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
39004
39005      if (dateTimeObject < nextSegmentStart) {
39006        break;
39007      }
39008    }
39009
39010    var lastSegment = playlist.segments[playlist.segments.length - 1];
39011    var lastSegmentStart = lastSegment.dateTimeObject;
39012    var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
39013    var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
39014
39015    if (dateTimeObject > lastSegmentEnd) {
39016      // Beyond the end of the stream, or our best guess of the end of the stream.
39017      return null;
39018    }
39019
39020    if (dateTimeObject > lastSegmentStart) {
39021      segment = lastSegment;
39022    }
39023
39024    return {
39025      segment: segment,
39026      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
39027      // Although, given that all segments have accurate date time objects, the segment
39028      // selected should be accurate, unless the video has been transmuxed at some point
39029      // (determined by the presence of the videoTimingInfo object), the segment's "player
39030      // time" (the start time in the player) can't be considered accurate.
39031      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
39032    };
39033  };
39034  /**
39035   * Finds a segment that contains the given player time(in seconds).
39036   *
39037   * @param {Number} time The player time to find a match for
39038   * @param {Object} playlist A playlist object to search within
39039   */
39040
39041
39042  var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
39043    // Assumptions:
39044    // - there will always be a segment.duration
39045    // - we can start from zero
39046    // - segments are in time order
39047    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
39048      return null;
39049    }
39050
39051    var segmentEnd = 0;
39052    var segment = void 0;
39053
39054    for (var i = 0; i < playlist.segments.length; i++) {
39055      segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
39056      // should contain the most accurate values we have for the segment's player times.
39057      //
39058      // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
39059      // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
39060      // calculate an end value.
39061
39062      segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
39063
39064      if (time <= segmentEnd) {
39065        break;
39066      }
39067    }
39068
39069    var lastSegment = playlist.segments[playlist.segments.length - 1];
39070
39071    if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
39072      // The time requested is beyond the stream end.
39073      return null;
39074    }
39075
39076    if (time > segmentEnd) {
39077      // The time is within or beyond the last segment.
39078      //
39079      // Check to see if the time is beyond a reasonable guess of the end of the stream.
39080      if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
39081        // Technically, because the duration value is only an estimate, the time may still
39082        // exist in the last segment, however, there isn't enough information to make even
39083        // a reasonable estimate.
39084        return null;
39085      }
39086
39087      segment = lastSegment;
39088    }
39089
39090    return {
39091      segment: segment,
39092      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
39093      // Because videoTimingInfo is only set after transmux, it is the only way to get
39094      // accurate timing values.
39095      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
39096    };
39097  };
39098  /**
39099   * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
39100   * If the offset returned is positive, the programTime occurs after the
39101   * comparisonTimestamp.
39102   * If the offset is negative, the programTime occurs before the comparisonTimestamp.
39103   *
39104   * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
39105   * @param {String} programTime The programTime as an ISO-8601 string
39106   * @return {Number} offset
39107   */
39108
39109
39110  var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
39111    var segmentDateTime = void 0;
39112    var programDateTime = void 0;
39113
39114    try {
39115      segmentDateTime = new Date(comparisonTimeStamp);
39116      programDateTime = new Date(programTime);
39117    } catch (e) {// TODO handle error
39118    }
39119
39120    var segmentTimeEpoch = segmentDateTime.getTime();
39121    var programTimeEpoch = programDateTime.getTime();
39122    return (programTimeEpoch - segmentTimeEpoch) / 1000;
39123  };
39124  /**
39125   * Checks that all segments in this playlist have programDateTime tags.
39126   *
39127   * @param {Object} playlist A playlist object
39128   */
39129
39130
39131  var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
39132    if (!playlist.segments || playlist.segments.length === 0) {
39133      return false;
39134    }
39135
39136    for (var i = 0; i < playlist.segments.length; i++) {
39137      var segment = playlist.segments[i];
39138
39139      if (!segment.dateTimeObject) {
39140        return false;
39141      }
39142    }
39143
39144    return true;
39145  };
39146  /**
39147   * Returns the programTime of the media given a playlist and a playerTime.
39148   * The playlist must have programDateTime tags for a programDateTime tag to be returned.
39149   * If the segments containing the time requested have not been buffered yet, an estimate
39150   * may be returned to the callback.
39151   *
39152   * @param {Object} args
39153   * @param {Object} args.playlist A playlist object to search within
39154   * @param {Number} time A playerTime in seconds
39155   * @param {Function} callback(err, programTime)
39156   * @returns {String} err.message A detailed error message
39157   * @returns {Object} programTime
39158   * @returns {Number} programTime.mediaSeconds The streamTime in seconds
39159   * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
39160   */
39161
39162
39163  var getProgramTime = function getProgramTime(_ref) {
39164    var playlist = _ref.playlist,
39165        _ref$time = _ref.time,
39166        time = _ref$time === undefined ? undefined : _ref$time,
39167        callback = _ref.callback;
39168
39169    if (!callback) {
39170      throw new Error('getProgramTime: callback must be provided');
39171    }
39172
39173    if (!playlist || time === undefined) {
39174      return callback({
39175        message: 'getProgramTime: playlist and time must be provided'
39176      });
39177    }
39178
39179    var matchedSegment = findSegmentForPlayerTime(time, playlist);
39180
39181    if (!matchedSegment) {
39182      return callback({
39183        message: 'valid programTime was not found'
39184      });
39185    }
39186
39187    if (matchedSegment.type === 'estimate') {
39188      return callback({
39189        message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
39190        seekTime: matchedSegment.estimatedStart
39191      });
39192    }
39193
39194    var programTimeObject = {
39195      mediaSeconds: time
39196    };
39197    var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
39198
39199    if (programTime) {
39200      programTimeObject.programDateTime = programTime.toISOString();
39201    }
39202
39203    return callback(null, programTimeObject);
39204  };
39205  /**
39206   * Seeks in the player to a time that matches the given programTime ISO-8601 string.
39207   *
39208   * @param {Object} args
39209   * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
39210   * @param {Object} args.playlist A playlist to look within
39211   * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
39212   * @param {Function} args.seekTo A method to perform a seek
39213   * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
39214   * @param {Object} args.tech The tech to seek on
39215   * @param {Function} args.callback(err, newTime) A callback to return the new time to
39216   * @returns {String} err.message A detailed error message
39217   * @returns {Number} newTime The exact time that was seeked to in seconds
39218   */
39219
39220
39221  var seekToProgramTime = function seekToProgramTime(_ref2) {
39222    var programTime = _ref2.programTime,
39223        playlist = _ref2.playlist,
39224        _ref2$retryCount = _ref2.retryCount,
39225        retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
39226        seekTo = _ref2.seekTo,
39227        _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
39228        pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
39229        tech = _ref2.tech,
39230        callback = _ref2.callback;
39231
39232    if (!callback) {
39233      throw new Error('seekToProgramTime: callback must be provided');
39234    }
39235
39236    if (typeof programTime === 'undefined' || !playlist || !seekTo) {
39237      return callback({
39238        message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
39239      });
39240    }
39241
39242    if (!playlist.endList && !tech.hasStarted_) {
39243      return callback({
39244        message: 'player must be playing a live stream to start buffering'
39245      });
39246    }
39247
39248    if (!verifyProgramDateTimeTags(playlist)) {
39249      return callback({
39250        message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
39251      });
39252    }
39253
39254    var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
39255
39256    if (!matchedSegment) {
39257      return callback({
39258        message: programTime + ' was not found in the stream'
39259      });
39260    }
39261
39262    var segment = matchedSegment.segment;
39263    var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
39264
39265    if (matchedSegment.type === 'estimate') {
39266      // we've run out of retries
39267      if (retryCount === 0) {
39268        return callback({
39269          message: programTime + ' is not buffered yet. Try again'
39270        });
39271      }
39272
39273      seekTo(matchedSegment.estimatedStart + mediaOffset);
39274      tech.one('seeked', function () {
39275        seekToProgramTime({
39276          programTime: programTime,
39277          playlist: playlist,
39278          retryCount: retryCount - 1,
39279          seekTo: seekTo,
39280          pauseAfterSeek: pauseAfterSeek,
39281          tech: tech,
39282          callback: callback
39283        });
39284      });
39285      return;
39286    } // Since the segment.start value is determined from the buffered end or ending time
39287    // of the prior segment, the seekToTime doesn't need to account for any transmuxer
39288    // modifications.
39289
39290
39291    var seekToTime = segment.start + mediaOffset;
39292
39293    var seekedCallback = function seekedCallback() {
39294      return callback(null, tech.currentTime());
39295    }; // listen for seeked event
39296
39297
39298    tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
39299
39300    if (pauseAfterSeek) {
39301      tech.pause();
39302    }
39303
39304    seekTo(seekToTime);
39305  };
39306  /**
39307   * ranges
39308   *
39309   * Utilities for working with TimeRanges.
39310   *
39311   */
39312  // Fudge factor to account for TimeRanges rounding
39313
39314
39315  var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
39316  // can be misleading because of precision differences or when the current media has poorly
39317  // aligned audio and video, which can cause values to be slightly off from what you would
39318  // expect. This value is what we consider to be safe to use in such comparisons to account
39319  // for these scenarios.
39320
39321  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
39322
39323  var filterRanges = function filterRanges(timeRanges, predicate) {
39324    var results = [];
39325    var i = void 0;
39326
39327    if (timeRanges && timeRanges.length) {
39328      // Search for ranges that match the predicate
39329      for (i = 0; i < timeRanges.length; i++) {
39330        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
39331          results.push([timeRanges.start(i), timeRanges.end(i)]);
39332        }
39333      }
39334    }
39335
39336    return videojs$1.createTimeRanges(results);
39337  };
39338  /**
39339   * Attempts to find the buffered TimeRange that contains the specified
39340   * time.
39341   * @param {TimeRanges} buffered - the TimeRanges object to query
39342   * @param {number} time  - the time to filter on.
39343   * @returns {TimeRanges} a new TimeRanges object
39344   */
39345
39346
39347  var findRange = function findRange(buffered, time) {
39348    return filterRanges(buffered, function (start, end) {
39349      return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
39350    });
vendor: 20,100 bytes, lines 39351-39985
39351  };
39352  /**
39353   * Returns the TimeRanges that begin later than the specified time.
39354   * @param {TimeRanges} timeRanges - the TimeRanges object to query
39355   * @param {number} time - the time to filter on.
39356   * @returns {TimeRanges} a new TimeRanges object.
39357   */
39358
39359
39360  var findNextRange = function findNextRange(timeRanges, time) {
39361    return filterRanges(timeRanges, function (start) {
39362      return start - TIME_FUDGE_FACTOR >= time;
39363    });
39364  };
39365  /**
39366   * Returns gaps within a list of TimeRanges
39367   * @param {TimeRanges} buffered - the TimeRanges object
39368   * @return {TimeRanges} a TimeRanges object of gaps
39369   */
39370
39371
39372  var findGaps = function findGaps(buffered) {
39373    if (buffered.length < 2) {
39374      return videojs$1.createTimeRanges();
39375    }
39376
39377    var ranges = [];
39378
39379    for (var i = 1; i < buffered.length; i++) {
39380      var start = buffered.end(i - 1);
39381      var end = buffered.start(i);
39382      ranges.push([start, end]);
39383    }
39384
39385    return videojs$1.createTimeRanges(ranges);
39386  };
39387  /**
39388   * Gets a human readable string for a TimeRange
39389   *
39390   * @param {TimeRange} range
39391   * @returns {String} a human readable string
39392   */
39393
39394
39395  var printableRange = function printableRange(range) {
39396    var strArr = [];
39397
39398    if (!range || !range.length) {
39399      return '';
39400    }
39401
39402    for (var i = 0; i < range.length; i++) {
39403      strArr.push(range.start(i) + ' => ' + range.end(i));
39404    }
39405
39406    return strArr.join(', ');
39407  };
39408  /**
39409   * Calculates the amount of time left in seconds until the player hits the end of the
39410   * buffer and causes a rebuffer
39411   *
39412   * @param {TimeRange} buffered
39413   *        The state of the buffer
39414   * @param {Numnber} currentTime
39415   *        The current time of the player
39416   * @param {Number} playbackRate
39417   *        The current playback rate of the player. Defaults to 1.
39418   * @return {Number}
39419   *         Time until the player has to start rebuffering in seconds.
39420   * @function timeUntilRebuffer
39421   */
39422
39423
39424  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
39425    var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
39426    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
39427    return (bufferedEnd - currentTime) / playbackRate;
39428  };
39429  /**
39430   * Converts a TimeRanges object into an array representation
39431   * @param {TimeRanges} timeRanges
39432   * @returns {Array}
39433   */
39434
39435
39436  var timeRangesToArray = function timeRangesToArray(timeRanges) {
39437    var timeRangesList = [];
39438
39439    for (var i = 0; i < timeRanges.length; i++) {
39440      timeRangesList.push({
39441        start: timeRanges.start(i),
39442        end: timeRanges.end(i)
39443      });
39444    }
39445
39446    return timeRangesList;
39447  };
39448  /**
39449   * @file create-text-tracks-if-necessary.js
39450   */
39451
39452  /**
39453   * Create text tracks on video.js if they exist on a segment.
39454   *
39455   * @param {Object} sourceBuffer the VSB or FSB
39456   * @param {Object} mediaSource the HTML media source
39457   * @param {Object} segment the segment that may contain the text track
39458   * @private
39459   */
39460
39461
39462  var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
39463    var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
39464
39465    if (segment.captions && segment.captions.length) {
39466      if (!sourceBuffer.inbandTextTracks_) {
39467        sourceBuffer.inbandTextTracks_ = {};
39468      }
39469
39470      for (var trackId in segment.captionStreams) {
39471        if (!sourceBuffer.inbandTextTracks_[trackId]) {
39472          player.tech_.trigger({
39473            type: 'usage',
39474            name: 'hls-608'
39475          });
39476          var track = player.textTracks().getTrackById(trackId);
39477
39478          if (track) {
39479            // Resuse an existing track with a CC# id because this was
39480            // very likely created by videojs-contrib-hls from information
39481            // in the m3u8 for us to use
39482            sourceBuffer.inbandTextTracks_[trackId] = track;
39483          } else {
39484            // Otherwise, create a track with the default `CC#` label and
39485            // without a language
39486            sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
39487              kind: 'captions',
39488              id: trackId,
39489              label: trackId
39490            }, false).track;
39491          }
39492        }
39493      }
39494    }
39495
39496    if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
39497      sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
39498        kind: 'metadata',
39499        label: 'Timed Metadata'
39500      }, false).track;
39501      sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
39502    }
39503  };
39504  /**
39505   * @file remove-cues-from-track.js
39506   */
39507
39508  /**
39509   * Remove cues from a track on video.js.
39510   *
39511   * @param {Double} start start of where we should remove the cue
39512   * @param {Double} end end of where the we should remove the cue
39513   * @param {Object} track the text track to remove the cues from
39514   * @private
39515   */
39516
39517
39518  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
39519    var i = void 0;
39520    var cue = void 0;
39521
39522    if (!track) {
39523      return;
39524    }
39525
39526    if (!track.cues) {
39527      return;
39528    }
39529
39530    i = track.cues.length;
39531
39532    while (i--) {
39533      cue = track.cues[i]; // Remove any overlapping cue
39534
39535      if (cue.startTime <= end && cue.endTime >= start) {
39536        track.removeCue(cue);
39537      }
39538    }
39539  };
39540  /**
39541   * @file add-text-track-data.js
39542   */
39543
39544  /**
39545   * Define properties on a cue for backwards compatability,
39546   * but warn the user that the way that they are using it
39547   * is depricated and will be removed at a later date.
39548   *
39549   * @param {Cue} cue the cue to add the properties on
39550   * @private
39551   */
39552
39553
39554  var deprecateOldCue = function deprecateOldCue(cue) {
39555    Object.defineProperties(cue.frame, {
39556      id: {
39557        get: function get() {
39558          videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
39559          return cue.value.key;
39560        }
39561      },
39562      value: {
39563        get: function get() {
39564          videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
39565          return cue.value.data;
39566        }
39567      },
39568      privateData: {
39569        get: function get() {
39570          videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
39571          return cue.value.data;
39572        }
39573      }
39574    });
39575  };
39576
39577  var durationOfVideo = function durationOfVideo(duration) {
39578    var dur = void 0;
39579
39580    if (isNaN(duration) || Math.abs(duration) === Infinity) {
39581      dur = Number.MAX_VALUE;
39582    } else {
39583      dur = duration;
39584    }
39585
39586    return dur;
39587  };
39588  /**
39589   * Add text track data to a source handler given the captions and
39590   * metadata from the buffer.
39591   *
39592   * @param {Object} sourceHandler the virtual source buffer
39593   * @param {Array} captionArray an array of caption data
39594   * @param {Array} metadataArray an array of meta data
39595   * @private
39596   */
39597
39598
39599  var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
39600    var Cue = window$1.WebKitDataCue || window$1.VTTCue;
39601
39602    if (captionArray) {
39603      captionArray.forEach(function (caption) {
39604        var track = caption.stream;
39605        this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
39606      }, sourceHandler);
39607    }
39608
39609    if (metadataArray) {
39610      var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
39611      metadataArray.forEach(function (metadata) {
39612        var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
39613        // ignore this bit of metadata.
39614        // This likely occurs when you have an non-timed ID3 tag like TIT2,
39615        // which is the "Title/Songname/Content description" frame
39616
39617        if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
39618          return;
39619        }
39620
39621        metadata.frames.forEach(function (frame) {
39622          var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
39623          cue.frame = frame;
39624          cue.value = frame;
39625          deprecateOldCue(cue);
39626          this.metadataTrack_.addCue(cue);
39627        }, this);
39628      }, sourceHandler); // Updating the metadeta cues so that
39629      // the endTime of each cue is the startTime of the next cue
39630      // the endTime of last cue is the duration of the video
39631
39632      if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
39633        var cues = sourceHandler.metadataTrack_.cues;
39634        var cuesArray = []; // Create a copy of the TextTrackCueList...
39635        // ...disregarding cues with a falsey value
39636
39637        for (var i = 0; i < cues.length; i++) {
39638          if (cues[i]) {
39639            cuesArray.push(cues[i]);
39640          }
39641        } // Group cues by their startTime value
39642
39643
39644        var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
39645          var timeSlot = obj[cue.startTime] || [];
39646          timeSlot.push(cue);
39647          obj[cue.startTime] = timeSlot;
39648          return obj;
39649        }, {}); // Sort startTimes by ascending order
39650
39651        var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
39652          return Number(a) - Number(b);
39653        }); // Map each cue group's endTime to the next group's startTime
39654
39655        sortedStartTimes.forEach(function (startTime, idx) {
39656          var cueGroup = cuesGroupedByStartTime[startTime];
39657          var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
39658
39659          cueGroup.forEach(function (cue) {
39660            cue.endTime = nextTime;
39661          });
39662        });
39663      }
39664    }
39665  };
39666
39667  var win = typeof window !== 'undefined' ? window : {},
39668      TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
39669      SCRIPT_TYPE = 'application/javascript',
39670      BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
39671      URL = win.URL || win.webkitURL || URL && URL.msURL,
39672      Worker = win.Worker;
39673  /**
39674   * Returns a wrapper around Web Worker code that is constructible.
39675   *
39676   * @function shimWorker
39677   *
39678   * @param { String }    filename    The name of the file
39679   * @param { Function }  fn          Function wrapping the code of the worker
39680   */
39681
39682  function shimWorker(filename, fn) {
39683    return function ShimWorker(forceFallback) {
39684      var o = this;
39685
39686      if (!fn) {
39687        return new Worker(filename);
39688      } else if (Worker && !forceFallback) {
39689        // Convert the function's inner code to a string to construct the worker
39690        var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
39691            objURL = createSourceObject(source);
39692        this[TARGET] = new Worker(objURL);
39693        wrapTerminate(this[TARGET], objURL);
39694        return this[TARGET];
39695      } else {
39696        var selfShim = {
39697          postMessage: function postMessage(m) {
39698            if (o.onmessage) {
39699              setTimeout(function () {
39700                o.onmessage({
39701                  data: m,
39702                  target: selfShim
39703                });
39704              });
39705            }
39706          }
39707        };
39708        fn.call(selfShim);
39709
39710        this.postMessage = function (m) {
39711          setTimeout(function () {
39712            selfShim.onmessage({
39713              data: m,
39714              target: o
39715            });
39716          });
39717        };
39718
39719        this.isThisThread = true;
39720      }
39721    };
39722  } // Test Worker capabilities
39723
39724
39725  if (Worker) {
39726    var testWorker,
39727        objURL = createSourceObject('self.onmessage = function () {}'),
39728        testArray = new Uint8Array(1);
39729
39730    try {
39731      testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
39732
39733      testWorker.postMessage(testArray, [testArray.buffer]);
39734    } catch (e) {
39735      Worker = null;
39736    } finally {
39737      URL.revokeObjectURL(objURL);
39738
39739      if (testWorker) {
39740        testWorker.terminate();
39741      }
39742    }
39743  }
39744
39745  function createSourceObject(str) {
39746    try {
39747      return URL.createObjectURL(new Blob([str], {
39748        type: SCRIPT_TYPE
39749      }));
39750    } catch (e) {
39751      var blob = new BlobBuilder();
39752      blob.append(str);
39753      return URL.createObjectURL(blob.getBlob(type));
39754    }
39755  }
39756
39757  function wrapTerminate(worker, objURL) {
39758    if (!worker || !objURL) return;
39759    var term = worker.terminate;
39760    worker.objURL = objURL;
39761
39762    worker.terminate = function () {
39763      if (worker.objURL) URL.revokeObjectURL(worker.objURL);
39764      term.call(worker);
39765    };
39766  }
39767
39768  var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
39769    var self = this;
39770
39771    var transmuxerWorker = function () {
39772      /**
39773       * mux.js
39774       *
39775       * Copyright (c) Brightcove
39776       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39777       *
39778       * A lightweight readable stream implemention that handles event dispatching.
39779       * Objects that inherit from streams should call init in their constructors.
39780       */
39781      var Stream = function Stream() {
39782        this.init = function () {
39783          var listeners = {};
39784          /**
39785           * Add a listener for a specified event type.
39786           * @param type {string} the event name
39787           * @param listener {function} the callback to be invoked when an event of
39788           * the specified type occurs
39789           */
39790
39791          this.on = function (type, listener) {
39792            if (!listeners[type]) {
39793              listeners[type] = [];
39794            }
39795
39796            listeners[type] = listeners[type].concat(listener);
39797          };
39798          /**
39799           * Remove a listener for a specified event type.
39800           * @param type {string} the event name
39801           * @param listener {function} a function previously registered for this
39802           * type of event through `on`
39803           */
39804
39805
39806          this.off = function (type, listener) {
39807            var index;
39808
39809            if (!listeners[type]) {
39810              return false;
39811            }
39812
39813            index = listeners[type].indexOf(listener);
39814            listeners[type] = listeners[type].slice();
39815            listeners[type].splice(index, 1);
39816            return index > -1;
39817          };
39818          /**
39819           * Trigger an event of the specified type on this stream. Any additional
39820           * arguments to this function are passed as parameters to event listeners.
39821           * @param type {string} the event name
39822           */
39823
39824
39825          this.trigger = function (type) {
39826            var callbacks, i, length, args;
39827            callbacks = listeners[type];
39828
39829            if (!callbacks) {
39830              return;
39831            } // Slicing the arguments on every invocation of this method
39832            // can add a significant amount of overhead. Avoid the
39833            // intermediate object creation for the common case of a
39834            // single callback argument
39835
39836
39837            if (arguments.length === 2) {
39838              length = callbacks.length;
39839
39840              for (i = 0; i < length; ++i) {
39841                callbacks[i].call(this, arguments[1]);
39842              }
39843            } else {
39844              args = [];
39845              i = arguments.length;
39846
39847              for (i = 1; i < arguments.length; ++i) {
39848                args.push(arguments[i]);
39849              }
39850
39851              length = callbacks.length;
39852
39853              for (i = 0; i < length; ++i) {
39854                callbacks[i].apply(this, args);
39855              }
39856            }
39857          };
39858          /**
39859           * Destroys the stream and cleans up.
39860           */
39861
39862
39863          this.dispose = function () {
39864            listeners = {};
39865          };
39866        };
39867      };
39868      /**
39869       * Forwards all `data` events on this stream to the destination stream. The
39870       * destination stream should provide a method `push` to receive the data
39871       * events as they arrive.
39872       * @param destination {stream} the stream that will receive all `data` events
39873       * @param autoFlush {boolean} if false, we will not call `flush` on the destination
39874       *                            when the current stream emits a 'done' event
39875       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
39876       */
39877
39878
39879      Stream.prototype.pipe = function (destination) {
39880        this.on('data', function (data) {
39881          destination.push(data);
39882        });
39883        this.on('done', function (flushSource) {
39884          destination.flush(flushSource);
39885        });
39886        this.on('partialdone', function (flushSource) {
39887          destination.partialFlush(flushSource);
39888        });
39889        this.on('endedtimeline', function (flushSource) {
39890          destination.endTimeline(flushSource);
39891        });
39892        this.on('reset', function (flushSource) {
39893          destination.reset(flushSource);
39894        });
39895        return destination;
39896      }; // Default stream functions that are expected to be overridden to perform
39897      // actual work. These are provided by the prototype as a sort of no-op
39898      // implementation so that we don't have to check for their existence in the
39899      // `pipe` function above.
39900
39901
39902      Stream.prototype.push = function (data) {
39903        this.trigger('data', data);
39904      };
39905
39906      Stream.prototype.flush = function (flushSource) {
39907        this.trigger('done', flushSource);
39908      };
39909
39910      Stream.prototype.partialFlush = function (flushSource) {
39911        this.trigger('partialdone', flushSource);
39912      };
39913
39914      Stream.prototype.endTimeline = function (flushSource) {
39915        this.trigger('endedtimeline', flushSource);
39916      };
39917
39918      Stream.prototype.reset = function (flushSource) {
39919        this.trigger('reset', flushSource);
39920      };
39921
39922      var stream = Stream;
39923      /**
39924       * mux.js
39925       *
39926       * Copyright (c) Brightcove
39927       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39928       *
39929       * Functions that generate fragmented MP4s suitable for use with Media
39930       * Source Extensions.
39931       */
39932
39933      var UINT32_MAX = Math.pow(2, 32) - 1;
39934      var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
39935
39936      (function () {
39937        var i;
39938        types = {
39939          avc1: [],
39940          // codingname
39941          avcC: [],
39942          btrt: [],
39943          dinf: [],
39944          dref: [],
39945          esds: [],
39946          ftyp: [],
39947          hdlr: [],
39948          mdat: [],
39949          mdhd: [],
39950          mdia: [],
39951          mfhd: [],
39952          minf: [],
39953          moof: [],
39954          moov: [],
39955          mp4a: [],
39956          // codingname
39957          mvex: [],
39958          mvhd: [],
39959          pasp: [],
39960          sdtp: [],
39961          smhd: [],
39962          stbl: [],
39963          stco: [],
39964          stsc: [],
39965          stsd: [],
39966          stsz: [],
39967          stts: [],
39968          styp: [],
39969          tfdt: [],
39970          tfhd: [],
39971          traf: [],
39972          trak: [],
39973          trun: [],
39974          trex: [],
39975          tkhd: [],
39976          vmhd: []
39977        }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
39978        // don't throw an error
39979
39980        if (typeof Uint8Array === 'undefined') {
39981          return;
39982        }
39983
39984        for (i in types) {
39985          if (types.hasOwnProperty(i)) {
vendor: 16,384 bytes, lines 39986-40355
39986            types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
39987          }
39988        }
39989
39990        MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
39991        AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
39992        MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
39993        VIDEO_HDLR = new Uint8Array([0x00, // version 0
39994        0x00, 0x00, 0x00, // flags
39995        0x00, 0x00, 0x00, 0x00, // pre_defined
39996        0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
39997        0x00, 0x00, 0x00, 0x00, // reserved
39998        0x00, 0x00, 0x00, 0x00, // reserved
39999        0x00, 0x00, 0x00, 0x00, // reserved
40000        0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
40001        ]);
40002        AUDIO_HDLR = new Uint8Array([0x00, // version 0
40003        0x00, 0x00, 0x00, // flags
40004        0x00, 0x00, 0x00, 0x00, // pre_defined
40005        0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
40006        0x00, 0x00, 0x00, 0x00, // reserved
40007        0x00, 0x00, 0x00, 0x00, // reserved
40008        0x00, 0x00, 0x00, 0x00, // reserved
40009        0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
40010        ]);
40011        HDLR_TYPES = {
40012          video: VIDEO_HDLR,
40013          audio: AUDIO_HDLR
40014        };
40015        DREF = new Uint8Array([0x00, // version 0
40016        0x00, 0x00, 0x00, // flags
40017        0x00, 0x00, 0x00, 0x01, // entry_count
40018        0x00, 0x00, 0x00, 0x0c, // entry_size
40019        0x75, 0x72, 0x6c, 0x20, // 'url' type
40020        0x00, // version 0
40021        0x00, 0x00, 0x01 // entry_flags
40022        ]);
40023        SMHD = new Uint8Array([0x00, // version
40024        0x00, 0x00, 0x00, // flags
40025        0x00, 0x00, // balance, 0 means centered
40026        0x00, 0x00 // reserved
40027        ]);
40028        STCO = new Uint8Array([0x00, // version
40029        0x00, 0x00, 0x00, // flags
40030        0x00, 0x00, 0x00, 0x00 // entry_count
40031        ]);
40032        STSC = STCO;
40033        STSZ = new Uint8Array([0x00, // version
40034        0x00, 0x00, 0x00, // flags
40035        0x00, 0x00, 0x00, 0x00, // sample_size
40036        0x00, 0x00, 0x00, 0x00 // sample_count
40037        ]);
40038        STTS = STCO;
40039        VMHD = new Uint8Array([0x00, // version
40040        0x00, 0x00, 0x01, // flags
40041        0x00, 0x00, // graphicsmode
40042        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
40043        ]);
40044      })();
40045
40046      box = function box(type) {
40047        var payload = [],
40048            size = 0,
40049            i,
40050            result,
40051            view;
40052
40053        for (i = 1; i < arguments.length; i++) {
40054          payload.push(arguments[i]);
40055        }
40056
40057        i = payload.length; // calculate the total size we need to allocate
40058
40059        while (i--) {
40060          size += payload[i].byteLength;
40061        }
40062
40063        result = new Uint8Array(size + 8);
40064        view = new DataView(result.buffer, result.byteOffset, result.byteLength);
40065        view.setUint32(0, result.byteLength);
40066        result.set(type, 4); // copy the payload into the result
40067
40068        for (i = 0, size = 8; i < payload.length; i++) {
40069          result.set(payload[i], size);
40070          size += payload[i].byteLength;
40071        }
40072
40073        return result;
40074      };
40075
40076      dinf = function dinf() {
40077        return box(types.dinf, box(types.dref, DREF));
40078      };
40079
40080      esds = function esds(track) {
40081        return box(types.esds, new Uint8Array([0x00, // version
40082        0x00, 0x00, 0x00, // flags
40083        // ES_Descriptor
40084        0x03, // tag, ES_DescrTag
40085        0x19, // length
40086        0x00, 0x00, // ES_ID
40087        0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
40088        // DecoderConfigDescriptor
40089        0x04, // tag, DecoderConfigDescrTag
40090        0x11, // length
40091        0x40, // object type
40092        0x15, // streamType
40093        0x00, 0x06, 0x00, // bufferSizeDB
40094        0x00, 0x00, 0xda, 0xc0, // maxBitrate
40095        0x00, 0x00, 0xda, 0xc0, // avgBitrate
40096        // DecoderSpecificInfo
40097        0x05, // tag, DecoderSpecificInfoTag
40098        0x02, // length
40099        // ISO/IEC 14496-3, AudioSpecificConfig
40100        // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
40101        track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
40102        ]));
40103      };
40104
40105      ftyp = function ftyp() {
40106        return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
40107      };
40108
40109      hdlr = function hdlr(type) {
40110        return box(types.hdlr, HDLR_TYPES[type]);
40111      };
40112
40113      mdat = function mdat(data) {
40114        return box(types.mdat, data);
40115      };
40116
40117      mdhd = function mdhd(track) {
40118        var result = new Uint8Array([0x00, // version 0
40119        0x00, 0x00, 0x00, // flags
40120        0x00, 0x00, 0x00, 0x02, // creation_time
40121        0x00, 0x00, 0x00, 0x03, // modification_time
40122        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
40123        track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
40124        0x55, 0xc4, // 'und' language (undetermined)
40125        0x00, 0x00]); // Use the sample rate from the track metadata, when it is
40126        // defined. The sample rate can be parsed out of an ADTS header, for
40127        // instance.
40128
40129        if (track.samplerate) {
40130          result[12] = track.samplerate >>> 24 & 0xFF;
40131          result[13] = track.samplerate >>> 16 & 0xFF;
40132          result[14] = track.samplerate >>> 8 & 0xFF;
40133          result[15] = track.samplerate & 0xFF;
40134        }
40135
40136        return box(types.mdhd, result);
40137      };
40138
40139      mdia = function mdia(track) {
40140        return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
40141      };
40142
40143      mfhd = function mfhd(sequenceNumber) {
40144        return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
40145        (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
40146        ]));
40147      };
40148
40149      minf = function minf(track) {
40150        return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
40151      };
40152
40153      moof = function moof(sequenceNumber, tracks) {
40154        var trackFragments = [],
40155            i = tracks.length; // build traf boxes for each track fragment
40156
40157        while (i--) {
40158          trackFragments[i] = traf(tracks[i]);
40159        }
40160
40161        return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
40162      };
40163      /**
40164       * Returns a movie box.
40165       * @param tracks {array} the tracks associated with this movie
40166       * @see ISO/IEC 14496-12:2012(E), section 8.2.1
40167       */
40168
40169
40170      moov = function moov(tracks) {
40171        var i = tracks.length,
40172            boxes = [];
40173
40174        while (i--) {
40175          boxes[i] = trak(tracks[i]);
40176        }
40177
40178        return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
40179      };
40180
40181      mvex = function mvex(tracks) {
40182        var i = tracks.length,
40183            boxes = [];
40184
40185        while (i--) {
40186          boxes[i] = trex(tracks[i]);
40187        }
40188
40189        return box.apply(null, [types.mvex].concat(boxes));
40190      };
40191
40192      mvhd = function mvhd(duration) {
40193        var bytes = new Uint8Array([0x00, // version 0
40194        0x00, 0x00, 0x00, // flags
40195        0x00, 0x00, 0x00, 0x01, // creation_time
40196        0x00, 0x00, 0x00, 0x02, // modification_time
40197        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
40198        (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
40199        0x00, 0x01, 0x00, 0x00, // 1.0 rate
40200        0x01, 0x00, // 1.0 volume
40201        0x00, 0x00, // reserved
40202        0x00, 0x00, 0x00, 0x00, // reserved
40203        0x00, 0x00, 0x00, 0x00, // reserved
40204        0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
40205        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
40206        0xff, 0xff, 0xff, 0xff // next_track_ID
40207        ]);
40208        return box(types.mvhd, bytes);
40209      };
40210
40211      sdtp = function sdtp(track) {
40212        var samples = track.samples || [],
40213            bytes = new Uint8Array(4 + samples.length),
40214            flags,
40215            i; // leave the full box header (4 bytes) all zero
40216        // write the sample table
40217
40218        for (i = 0; i < samples.length; i++) {
40219          flags = samples[i].flags;
40220          bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
40221        }
40222
40223        return box(types.sdtp, bytes);
40224      };
40225
40226      stbl = function stbl(track) {
40227        return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
40228      };
40229
40230      (function () {
40231        var videoSample, audioSample;
40232
40233        stsd = function stsd(track) {
40234          return box(types.stsd, new Uint8Array([0x00, // version 0
40235          0x00, 0x00, 0x00, // flags
40236          0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
40237        };
40238
40239        videoSample = function videoSample(track) {
40240          var sps = track.sps || [],
40241              pps = track.pps || [],
40242              sequenceParameterSets = [],
40243              pictureParameterSets = [],
40244              i,
40245              avc1Box; // assemble the SPSs
40246
40247          for (i = 0; i < sps.length; i++) {
40248            sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
40249            sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
40250
40251            sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
40252          } // assemble the PPSs
40253
40254
40255          for (i = 0; i < pps.length; i++) {
40256            pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
40257            pictureParameterSets.push(pps[i].byteLength & 0xFF);
40258            pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
40259          }
40260
40261          avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
40262          0x00, 0x01, // data_reference_index
40263          0x00, 0x00, // pre_defined
40264          0x00, 0x00, // reserved
40265          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
40266          (track.width & 0xff00) >> 8, track.width & 0xff, // width
40267          (track.height & 0xff00) >> 8, track.height & 0xff, // height
40268          0x00, 0x48, 0x00, 0x00, // horizresolution
40269          0x00, 0x48, 0x00, 0x00, // vertresolution
40270          0x00, 0x00, 0x00, 0x00, // reserved
40271          0x00, 0x01, // frame_count
40272          0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
40273          0x00, 0x18, // depth = 24
40274          0x11, 0x11 // pre_defined = -1
40275          ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
40276          track.profileIdc, // AVCProfileIndication
40277          track.profileCompatibility, // profile_compatibility
40278          track.levelIdc, // AVCLevelIndication
40279          0xff // lengthSizeMinusOne, hard-coded to 4 bytes
40280          ].concat([sps.length], // numOfSequenceParameterSets
40281          sequenceParameterSets, // "SPS"
40282          [pps.length], // numOfPictureParameterSets
40283          pictureParameterSets // "PPS"
40284          ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
40285          0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
40286          0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
40287          ]))];
40288
40289          if (track.sarRatio) {
40290            var hSpacing = track.sarRatio[0],
40291                vSpacing = track.sarRatio[1];
40292            avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
40293          }
40294
40295          return box.apply(null, avc1Box);
40296        };
40297
40298        audioSample = function audioSample(track) {
40299          return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
40300          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
40301          0x00, 0x01, // data_reference_index
40302          // AudioSampleEntry, ISO/IEC 14496-12
40303          0x00, 0x00, 0x00, 0x00, // reserved
40304          0x00, 0x00, 0x00, 0x00, // reserved
40305          (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
40306          (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
40307          0x00, 0x00, // pre_defined
40308          0x00, 0x00, // reserved
40309          (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
40310          // MP4AudioSampleEntry, ISO/IEC 14496-14
40311          ]), esds(track));
40312        };
40313      })();
40314
40315      tkhd = function tkhd(track) {
40316        var result = new Uint8Array([0x00, // version 0
40317        0x00, 0x00, 0x07, // flags
40318        0x00, 0x00, 0x00, 0x00, // creation_time
40319        0x00, 0x00, 0x00, 0x00, // modification_time
40320        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
40321        0x00, 0x00, 0x00, 0x00, // reserved
40322        (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
40323        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
40324        0x00, 0x00, // layer
40325        0x00, 0x00, // alternate_group
40326        0x01, 0x00, // non-audio track volume
40327        0x00, 0x00, // reserved
40328        0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
40329        (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
40330        (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
40331        ]);
40332        return box(types.tkhd, result);
40333      };
40334      /**
40335       * Generate a track fragment (traf) box. A traf box collects metadata
40336       * about tracks in a movie fragment (moof) box.
40337       */
40338
40339
40340      traf = function traf(track) {
40341        var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
40342        trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
40343        0x00, 0x00, 0x3a, // flags
40344        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
40345        0x00, 0x00, 0x00, 0x01, // sample_description_index
40346        0x00, 0x00, 0x00, 0x00, // default_sample_duration
40347        0x00, 0x00, 0x00, 0x00, // default_sample_size
40348        0x00, 0x00, 0x00, 0x00 // default_sample_flags
40349        ]));
40350        upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
40351        lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
40352        trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
40353        0x00, 0x00, 0x00, // flags
40354        // baseMediaDecodeTime
40355        upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 
vendor: 10,330 bytes, lines 40355-40600
403550xFF])); // the data offset specifies the number of bytes from the start of
40356        // the containing moof to the first payload byte of the associated
40357        // mdat
40358
40359        dataOffset = 32 + // tfhd
40360        20 + // tfdt
40361        8 + // traf header
40362        16 + // mfhd
40363        8 + // moof header
40364        8; // mdat header
40365        // audio tracks require less metadata
40366
40367        if (track.type === 'audio') {
40368          trackFragmentRun = trun(track, dataOffset);
40369          return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
40370        } // video tracks should contain an independent and disposable samples
40371        // box (sdtp)
40372        // generate one and adjust offsets to match
40373
40374
40375        sampleDependencyTable = sdtp(track);
40376        trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
40377        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
40378      };
40379      /**
40380       * Generate a track box.
40381       * @param track {object} a track definition
40382       * @return {Uint8Array} the track box
40383       */
40384
40385
40386      trak = function trak(track) {
40387        track.duration = track.duration || 0xffffffff;
40388        return box(types.trak, tkhd(track), mdia(track));
40389      };
40390
40391      trex = function trex(track) {
40392        var result = new Uint8Array([0x00, // version 0
40393        0x00, 0x00, 0x00, // flags
40394        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
40395        0x00, 0x00, 0x00, 0x01, // default_sample_description_index
40396        0x00, 0x00, 0x00, 0x00, // default_sample_duration
40397        0x00, 0x00, 0x00, 0x00, // default_sample_size
40398        0x00, 0x01, 0x00, 0x01 // default_sample_flags
40399        ]); // the last two bytes of default_sample_flags is the sample
40400        // degradation priority, a hint about the importance of this sample
40401        // relative to others. Lower the degradation priority for all sample
40402        // types other than video.
40403
40404        if (track.type !== 'video') {
40405          result[result.length - 1] = 0x00;
40406        }
40407
40408        return box(types.trex, result);
40409      };
40410
40411      (function () {
40412        var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
40413        // duration is present for the first sample, it will be present for
40414        // all subsequent samples.
40415        // see ISO/IEC 14496-12:2012, Section 8.8.8.1
40416
40417        trunHeader = function trunHeader(samples, offset) {
40418          var durationPresent = 0,
40419              sizePresent = 0,
40420              flagsPresent = 0,
40421              compositionTimeOffset = 0; // trun flag constants
40422
40423          if (samples.length) {
40424            if (samples[0].duration !== undefined) {
40425              durationPresent = 0x1;
40426            }
40427
40428            if (samples[0].size !== undefined) {
40429              sizePresent = 0x2;
40430            }
40431
40432            if (samples[0].flags !== undefined) {
40433              flagsPresent = 0x4;
40434            }
40435
40436            if (samples[0].compositionTimeOffset !== undefined) {
40437              compositionTimeOffset = 0x8;
40438            }
40439          }
40440
40441          return [0x00, // version 0
40442          0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
40443          (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
40444          (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
40445          ];
40446        };
40447
40448        videoTrun = function videoTrun(track, offset) {
40449          var bytes, samples, sample, i;
40450          samples = track.samples || [];
40451          offset += 8 + 12 + 16 * samples.length;
40452          bytes = trunHeader(samples, offset);
40453
40454          for (i = 0; i < samples.length; i++) {
40455            sample = samples[i];
40456            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
40457            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
40458            sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags
40459            (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
40460            ]);
40461          }
40462
40463          return box(types.trun, new Uint8Array(bytes));
40464        };
40465
40466        audioTrun = function audioTrun(track, offset) {
40467          var bytes, samples, sample, i;
40468          samples = track.samples || [];
40469          offset += 8 + 12 + 8 * samples.length;
40470          bytes = trunHeader(samples, offset);
40471
40472          for (i = 0; i < samples.length; i++) {
40473            sample = samples[i];
40474            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
40475            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
40476          }
40477
40478          return box(types.trun, new Uint8Array(bytes));
40479        };
40480
40481        trun = function trun(track, offset) {
40482          if (track.type === 'audio') {
40483            return audioTrun(track, offset);
40484          }
40485
40486          return videoTrun(track, offset);
40487        };
40488      })();
40489
40490      var mp4Generator = {
40491        ftyp: ftyp,
40492        mdat: mdat,
40493        moof: moof,
40494        moov: moov,
40495        initSegment: function initSegment(tracks) {
40496          var fileType = ftyp(),
40497              movie = moov(tracks),
40498              result;
40499          result = new Uint8Array(fileType.byteLength + movie.byteLength);
40500          result.set(fileType);
40501          result.set(movie, fileType.byteLength);
40502          return result;
40503        }
40504      };
40505      /**
40506       * mux.js
40507       *
40508       * Copyright (c) Brightcove
40509       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40510       */
40511      // Convert an array of nal units into an array of frames with each frame being
40512      // composed of the nal units that make up that frame
40513      // Also keep track of cummulative data about the frame from the nal units such
40514      // as the frame duration, starting pts, etc.
40515
40516      var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
40517        var i,
40518            currentNal,
40519            currentFrame = [],
40520            frames = []; // TODO added for LHLS, make sure this is OK
40521
40522        frames.byteLength = 0;
40523        frames.nalCount = 0;
40524        frames.duration = 0;
40525        currentFrame.byteLength = 0;
40526
40527        for (i = 0; i < nalUnits.length; i++) {
40528          currentNal = nalUnits[i]; // Split on 'aud'-type nal units
40529
40530          if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
40531            // Since the very first nal unit is expected to be an AUD
40532            // only push to the frames array when currentFrame is not empty
40533            if (currentFrame.length) {
40534              currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
40535
40536              frames.byteLength += currentFrame.byteLength;
40537              frames.nalCount += currentFrame.length;
40538              frames.duration += currentFrame.duration;
40539              frames.push(currentFrame);
40540            }
40541
40542            currentFrame = [currentNal];
40543            currentFrame.byteLength = currentNal.data.byteLength;
40544            currentFrame.pts = currentNal.pts;
40545            currentFrame.dts = currentNal.dts;
40546          } else {
40547            // Specifically flag key frames for ease of use later
40548            if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
40549              currentFrame.keyFrame = true;
40550            }
40551
40552            currentFrame.duration = currentNal.dts - currentFrame.dts;
40553            currentFrame.byteLength += currentNal.data.byteLength;
40554            currentFrame.push(currentNal);
40555          }
40556        } // For the last frame, use the duration of the previous frame if we
40557        // have nothing better to go on
40558
40559
40560        if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
40561          currentFrame.duration = frames[frames.length - 1].duration;
40562        } // Push the final frame
40563        // TODO added for LHLS, make sure this is OK
40564
40565
40566        frames.byteLength += currentFrame.byteLength;
40567        frames.nalCount += currentFrame.length;
40568        frames.duration += currentFrame.duration;
40569        frames.push(currentFrame);
40570        return frames;
40571      }; // Convert an array of frames into an array of Gop with each Gop being composed
40572      // of the frames that make up that Gop
40573      // Also keep track of cummulative data about the Gop from the frames such as the
40574      // Gop duration, starting pts, etc.
40575
40576
40577      var groupFramesIntoGops = function groupFramesIntoGops(frames) {
40578        var i,
40579            currentFrame,
40580            currentGop = [],
40581            gops = []; // We must pre-set some of the values on the Gop since we
40582        // keep running totals of these values
40583
40584        currentGop.byteLength = 0;
40585        currentGop.nalCount = 0;
40586        currentGop.duration = 0;
40587        currentGop.pts = frames[0].pts;
40588        currentGop.dts = frames[0].dts; // store some metadata about all the Gops
40589
40590        gops.byteLength = 0;
40591        gops.nalCount = 0;
40592        gops.duration = 0;
40593        gops.pts = frames[0].pts;
40594        gops.dts = frames[0].dts;
40595
40596        for (i = 0; i < frames.length; i++) {
40597          currentFrame = frames[i];
40598
40599          if (currentFrame.keyFrame) {
40600            // Since the very first frame is expected to be an keyframe
vendor: 7,922 bytes, lines 40601-40833
40601            // only push to the gops array when currentGop is not empty
40602            if (currentGop.length) {
40603              gops.push(currentGop);
40604              gops.byteLength += currentGop.byteLength;
40605              gops.nalCount += currentGop.nalCount;
40606              gops.duration += currentGop.duration;
40607            }
40608
40609            currentGop = [currentFrame];
40610            currentGop.nalCount = currentFrame.length;
40611            currentGop.byteLength = currentFrame.byteLength;
40612            currentGop.pts = currentFrame.pts;
40613            currentGop.dts = currentFrame.dts;
40614            currentGop.duration = currentFrame.duration;
40615          } else {
40616            currentGop.duration += currentFrame.duration;
40617            currentGop.nalCount += currentFrame.length;
40618            currentGop.byteLength += currentFrame.byteLength;
40619            currentGop.push(currentFrame);
40620          }
40621        }
40622
40623        if (gops.length && currentGop.duration <= 0) {
40624          currentGop.duration = gops[gops.length - 1].duration;
40625        }
40626
40627        gops.byteLength += currentGop.byteLength;
40628        gops.nalCount += currentGop.nalCount;
40629        gops.duration += currentGop.duration; // push the final Gop
40630
40631        gops.push(currentGop);
40632        return gops;
40633      };
40634      /*
40635       * Search for the first keyframe in the GOPs and throw away all frames
40636       * until that keyframe. Then extend the duration of the pulled keyframe
40637       * and pull the PTS and DTS of the keyframe so that it covers the time
40638       * range of the frames that were disposed.
40639       *
40640       * @param {Array} gops video GOPs
40641       * @returns {Array} modified video GOPs
40642       */
40643
40644
40645      var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
40646        var currentGop;
40647
40648        if (!gops[0][0].keyFrame && gops.length > 1) {
40649          // Remove the first GOP
40650          currentGop = gops.shift();
40651          gops.byteLength -= currentGop.byteLength;
40652          gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
40653          // first gop to cover the time period of the
40654          // frames we just removed
40655
40656          gops[0][0].dts = currentGop.dts;
40657          gops[0][0].pts = currentGop.pts;
40658          gops[0][0].duration += currentGop.duration;
40659        }
40660
40661        return gops;
40662      };
40663      /**
40664       * Default sample object
40665       * see ISO/IEC 14496-12:2012, section 8.6.4.3
40666       */
40667
40668
40669      var createDefaultSample = function createDefaultSample() {
40670        return {
40671          size: 0,
40672          flags: {
40673            isLeading: 0,
40674            dependsOn: 1,
40675            isDependedOn: 0,
40676            hasRedundancy: 0,
40677            degradationPriority: 0,
40678            isNonSyncSample: 1
40679          }
40680        };
40681      };
40682      /*
40683       * Collates information from a video frame into an object for eventual
40684       * entry into an MP4 sample table.
40685       *
40686       * @param {Object} frame the video frame
40687       * @param {Number} dataOffset the byte offset to position the sample
40688       * @return {Object} object containing sample table info for a frame
40689       */
40690
40691
40692      var sampleForFrame = function sampleForFrame(frame, dataOffset) {
40693        var sample = createDefaultSample();
40694        sample.dataOffset = dataOffset;
40695        sample.compositionTimeOffset = frame.pts - frame.dts;
40696        sample.duration = frame.duration;
40697        sample.size = 4 * frame.length; // Space for nal unit size
40698
40699        sample.size += frame.byteLength;
40700
40701        if (frame.keyFrame) {
40702          sample.flags.dependsOn = 2;
40703          sample.flags.isNonSyncSample = 0;
40704        }
40705
40706        return sample;
40707      }; // generate the track's sample table from an array of gops
40708
40709
40710      var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
40711        var h,
40712            i,
40713            sample,
40714            currentGop,
40715            currentFrame,
40716            dataOffset = baseDataOffset || 0,
40717            samples = [];
40718
40719        for (h = 0; h < gops.length; h++) {
40720          currentGop = gops[h];
40721
40722          for (i = 0; i < currentGop.length; i++) {
40723            currentFrame = currentGop[i];
40724            sample = sampleForFrame(currentFrame, dataOffset);
40725            dataOffset += sample.size;
40726            samples.push(sample);
40727          }
40728        }
40729
40730        return samples;
40731      }; // generate the track's raw mdat data from an array of gops
40732
40733
40734      var concatenateNalData = function concatenateNalData(gops) {
40735        var h,
40736            i,
40737            j,
40738            currentGop,
40739            currentFrame,
40740            currentNal,
40741            dataOffset = 0,
40742            nalsByteLength = gops.byteLength,
40743            numberOfNals = gops.nalCount,
40744            totalByteLength = nalsByteLength + 4 * numberOfNals,
40745            data = new Uint8Array(totalByteLength),
40746            view = new DataView(data.buffer); // For each Gop..
40747
40748        for (h = 0; h < gops.length; h++) {
40749          currentGop = gops[h]; // For each Frame..
40750
40751          for (i = 0; i < currentGop.length; i++) {
40752            currentFrame = currentGop[i]; // For each NAL..
40753
40754            for (j = 0; j < currentFrame.length; j++) {
40755              currentNal = currentFrame[j];
40756              view.setUint32(dataOffset, currentNal.data.byteLength);
40757              dataOffset += 4;
40758              data.set(currentNal.data, dataOffset);
40759              dataOffset += currentNal.data.byteLength;
40760            }
40761          }
40762        }
40763
40764        return data;
40765      }; // generate the track's sample table from a frame
40766
40767
40768      var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
40769        var sample,
40770            dataOffset = baseDataOffset || 0,
40771            samples = [];
40772        sample = sampleForFrame(frame, dataOffset);
40773        samples.push(sample);
40774        return samples;
40775      }; // generate the track's raw mdat data from a frame
40776
40777
40778      var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
40779        var i,
40780            currentNal,
40781            dataOffset = 0,
40782            nalsByteLength = frame.byteLength,
40783            numberOfNals = frame.length,
40784            totalByteLength = nalsByteLength + 4 * numberOfNals,
40785            data = new Uint8Array(totalByteLength),
40786            view = new DataView(data.buffer); // For each NAL..
40787
40788        for (i = 0; i < frame.length; i++) {
40789          currentNal = frame[i];
40790          view.setUint32(dataOffset, currentNal.data.byteLength);
40791          dataOffset += 4;
40792          data.set(currentNal.data, dataOffset);
40793          dataOffset += currentNal.data.byteLength;
40794        }
40795
40796        return data;
40797      };
40798
40799      var frameUtils = {
40800        groupNalsIntoFrames: groupNalsIntoFrames,
40801        groupFramesIntoGops: groupFramesIntoGops,
40802        extendFirstKeyFrame: extendFirstKeyFrame,
40803        generateSampleTable: generateSampleTable,
40804        concatenateNalData: concatenateNalData,
40805        generateSampleTableForFrame: generateSampleTableForFrame,
40806        concatenateNalDataForFrame: concatenateNalDataForFrame
40807      };
40808      /**
40809       * mux.js
40810       *
40811       * Copyright (c) Brightcove
40812       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40813       */
40814
40815      var highPrefix = [33, 16, 5, 32, 164, 27];
40816      var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
40817
40818      var zeroFill = function zeroFill(count) {
40819        var a = [];
40820
40821        while (count--) {
40822          a.push(0);
40823        }
40824
40825        return a;
40826      };
40827
40828      var makeTable = function makeTable(metaTable) {
40829        return Object.keys(metaTable).reduce(function (obj, key) {
40830          obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
40831            return arr.concat(part);
40832          }, []));
40833          return obj;
vendor: 8,797 bytes, lines 40834-41061
40834        }, {});
40835      }; // Frames-of-silence to use for filling in missing AAC frames
40836
40837
40838      var coneOfSilence = {
40839        96000: [highPrefix, [227, 64], zeroFill(154), [56]],
40840        88200: [highPrefix, [231], zeroFill(170), [56]],
40841        64000: [highPrefix, [248, 192], zeroFill(240), [56]],
40842        48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
40843        44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
40844        32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
40845        24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
40846        16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
40847        12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]],
40848        11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]],
40849        8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
40850      };
40851      var silence = makeTable(coneOfSilence);
40852      /**
40853       * mux.js
40854       *
40855       * Copyright (c) Brightcove
40856       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40857       */
40858
40859      var ONE_SECOND_IN_TS = 90000,
40860          // 90kHz clock
40861      secondsToVideoTs,
40862          secondsToAudioTs,
40863          videoTsToSeconds,
40864          audioTsToSeconds,
40865          audioTsToVideoTs,
40866          videoTsToAudioTs,
40867          metadataTsToSeconds;
40868
40869      secondsToVideoTs = function secondsToVideoTs(seconds) {
40870        return seconds * ONE_SECOND_IN_TS;
40871      };
40872
40873      secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
40874        return seconds * sampleRate;
40875      };
40876
40877      videoTsToSeconds = function videoTsToSeconds(timestamp) {
40878        return timestamp / ONE_SECOND_IN_TS;
40879      };
40880
40881      audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
40882        return timestamp / sampleRate;
40883      };
40884
40885      audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
40886        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
40887      };
40888
40889      videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
40890        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
40891      };
40892      /**
40893       * Adjust ID3 tag or caption timing information by the timeline pts values
40894       * (if keepOriginalTimestamps is false) and convert to seconds
40895       */
40896
40897
40898      metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
40899        return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
40900      };
40901
40902      var clock = {
40903        ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
40904        secondsToVideoTs: secondsToVideoTs,
40905        secondsToAudioTs: secondsToAudioTs,
40906        videoTsToSeconds: videoTsToSeconds,
40907        audioTsToSeconds: audioTsToSeconds,
40908        audioTsToVideoTs: audioTsToVideoTs,
40909        videoTsToAudioTs: videoTsToAudioTs,
40910        metadataTsToSeconds: metadataTsToSeconds
40911      };
40912      /**
40913       * mux.js
40914       *
40915       * Copyright (c) Brightcove
40916       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
40917       */
40918
40919      /**
40920       * Sum the `byteLength` properties of the data in each AAC frame
40921       */
40922
40923      var sumFrameByteLengths = function sumFrameByteLengths(array) {
40924        var i,
40925            currentObj,
40926            sum = 0; // sum the byteLength's all each nal unit in the frame
40927
40928        for (i = 0; i < array.length; i++) {
40929          currentObj = array[i];
40930          sum += currentObj.data.byteLength;
40931        }
40932
40933        return sum;
40934      }; // Possibly pad (prefix) the audio track with silence if appending this track
40935      // would lead to the introduction of a gap in the audio buffer
40936
40937
40938      var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
40939        var baseMediaDecodeTimeTs,
40940            frameDuration = 0,
40941            audioGapDuration = 0,
40942            audioFillFrameCount = 0,
40943            audioFillDuration = 0,
40944            silentFrame,
40945            i,
40946            firstFrame;
40947
40948        if (!frames.length) {
40949          return;
40950        }
40951
40952        baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
40953
40954        frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
40955
40956        if (audioAppendStartTs && videoBaseMediaDecodeTime) {
40957          // insert the shortest possible amount (audio gap or audio to video gap)
40958          audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
40959
40960          audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
40961          audioFillDuration = audioFillFrameCount * frameDuration;
40962        } // don't attempt to fill gaps smaller than a single frame or larger
40963        // than a half second
40964
40965
40966        if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
40967          return;
40968        }
40969
40970        silentFrame = silence[track.samplerate];
40971
40972        if (!silentFrame) {
40973          // we don't have a silent frame pregenerated for the sample rate, so use a frame
40974          // from the content instead
40975          silentFrame = frames[0].data;
40976        }
40977
40978        for (i = 0; i < audioFillFrameCount; i++) {
40979          firstFrame = frames[0];
40980          frames.splice(0, 0, {
40981            data: silentFrame,
40982            dts: firstFrame.dts - frameDuration,
40983            pts: firstFrame.pts - frameDuration
40984          });
40985        }
40986
40987        track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
40988      }; // If the audio segment extends before the earliest allowed dts
40989      // value, remove AAC frames until starts at or after the earliest
40990      // allowed DTS so that we don't end up with a negative baseMedia-
40991      // DecodeTime for the audio track
40992
40993
40994      var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
40995        if (track.minSegmentDts >= earliestAllowedDts) {
40996          return adtsFrames;
40997        } // We will need to recalculate the earliest segment Dts
40998
40999
41000        track.minSegmentDts = Infinity;
41001        return adtsFrames.filter(function (currentFrame) {
41002          // If this is an allowed frame, keep it and record it's Dts
41003          if (currentFrame.dts >= earliestAllowedDts) {
41004            track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
41005            track.minSegmentPts = track.minSegmentDts;
41006            return true;
41007          } // Otherwise, discard it
41008
41009
41010          return false;
41011        });
41012      }; // generate the track's raw mdat data from an array of frames
41013
41014
41015      var generateSampleTable$1 = function generateSampleTable(frames) {
41016        var i,
41017            currentFrame,
41018            samples = [];
41019
41020        for (i = 0; i < frames.length; i++) {
41021          currentFrame = frames[i];
41022          samples.push({
41023            size: currentFrame.data.byteLength,
41024            duration: 1024 // For AAC audio, all samples contain 1024 samples
41025
41026          });
41027        }
41028
41029        return samples;
41030      }; // generate the track's sample table from an array of frames
41031
41032
41033      var concatenateFrameData = function concatenateFrameData(frames) {
41034        var i,
41035            currentFrame,
41036            dataOffset = 0,
41037            data = new Uint8Array(sumFrameByteLengths(frames));
41038
41039        for (i = 0; i < frames.length; i++) {
41040          currentFrame = frames[i];
41041          data.set(currentFrame.data, dataOffset);
41042          dataOffset += currentFrame.data.byteLength;
41043        }
41044
41045        return data;
41046      };
41047
41048      var audioFrameUtils = {
41049        prefixWithSilence: prefixWithSilence,
41050        trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
41051        generateSampleTable: generateSampleTable$1,
41052        concatenateFrameData: concatenateFrameData
41053      };
41054      /**
41055       * mux.js
41056       *
41057       * Copyright (c) Brightcove
41058       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
41059       */
41060
41061      var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
vendor: 12,931 bytes, lines 41062-41420
41062      /**
41063       * Store information about the start and end of the track and the
41064       * duration for each frame/sample we process in order to calculate
41065       * the baseMediaDecodeTime
41066       */
41067
41068      var collectDtsInfo = function collectDtsInfo(track, data) {
41069        if (typeof data.pts === 'number') {
41070          if (track.timelineStartInfo.pts === undefined) {
41071            track.timelineStartInfo.pts = data.pts;
41072          }
41073
41074          if (track.minSegmentPts === undefined) {
41075            track.minSegmentPts = data.pts;
41076          } else {
41077            track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
41078          }
41079
41080          if (track.maxSegmentPts === undefined) {
41081            track.maxSegmentPts = data.pts;
41082          } else {
41083            track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
41084          }
41085        }
41086
41087        if (typeof data.dts === 'number') {
41088          if (track.timelineStartInfo.dts === undefined) {
41089            track.timelineStartInfo.dts = data.dts;
41090          }
41091
41092          if (track.minSegmentDts === undefined) {
41093            track.minSegmentDts = data.dts;
41094          } else {
41095            track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
41096          }
41097
41098          if (track.maxSegmentDts === undefined) {
41099            track.maxSegmentDts = data.dts;
41100          } else {
41101            track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
41102          }
41103        }
41104      };
41105      /**
41106       * Clear values used to calculate the baseMediaDecodeTime between
41107       * tracks
41108       */
41109
41110
41111      var clearDtsInfo = function clearDtsInfo(track) {
41112        delete track.minSegmentDts;
41113        delete track.maxSegmentDts;
41114        delete track.minSegmentPts;
41115        delete track.maxSegmentPts;
41116      };
41117      /**
41118       * Calculate the track's baseMediaDecodeTime based on the earliest
41119       * DTS the transmuxer has ever seen and the minimum DTS for the
41120       * current track
41121       * @param track {object} track metadata configuration
41122       * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
41123       *        in the source; false to adjust the first segment to start at 0.
41124       */
41125
41126
41127      var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
41128        var baseMediaDecodeTime,
41129            scale,
41130            minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
41131
41132        if (!keepOriginalTimestamps) {
41133          minSegmentDts -= track.timelineStartInfo.dts;
41134        } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
41135        // we want the start of the first segment to be placed
41136
41137
41138        baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
41139
41140        baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
41141
41142        baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
41143
41144        if (track.type === 'audio') {
41145          // Audio has a different clock equal to the sampling_rate so we need to
41146          // scale the PTS values into the clock rate of the track
41147          scale = track.samplerate / ONE_SECOND_IN_TS$1;
41148          baseMediaDecodeTime *= scale;
41149          baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
41150        }
41151
41152        return baseMediaDecodeTime;
41153      };
41154
41155      var trackDecodeInfo = {
41156        clearDtsInfo: clearDtsInfo,
41157        calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
41158        collectDtsInfo: collectDtsInfo
41159      };
41160      /**
41161       * mux.js
41162       *
41163       * Copyright (c) Brightcove
41164       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
41165       *
41166       * Reads in-band caption information from a video elementary
41167       * stream. Captions must follow the CEA-708 standard for injection
41168       * into an MPEG-2 transport streams.
41169       * @see https://en.wikipedia.org/wiki/CEA-708
41170       * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
41171       */
41172      // Supplemental enhancement information (SEI) NAL units have a
41173      // payload type field to indicate how they are to be
41174      // interpreted. CEAS-708 caption content is always transmitted with
41175      // payload type 0x04.
41176
41177      var USER_DATA_REGISTERED_ITU_T_T35 = 4,
41178          RBSP_TRAILING_BITS = 128;
41179      /**
41180        * Parse a supplemental enhancement information (SEI) NAL unit.
41181        * Stops parsing once a message of type ITU T T35 has been found.
41182        *
41183        * @param bytes {Uint8Array} the bytes of a SEI NAL unit
41184        * @return {object} the parsed SEI payload
41185        * @see Rec. ITU-T H.264, 7.3.2.3.1
41186        */
41187
41188      var parseSei = function parseSei(bytes) {
41189        var i = 0,
41190            result = {
41191          payloadType: -1,
41192          payloadSize: 0
41193        },
41194            payloadType = 0,
41195            payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
41196
41197        while (i < bytes.byteLength) {
41198          // stop once we have hit the end of the sei_rbsp
41199          if (bytes[i] === RBSP_TRAILING_BITS) {
41200            break;
41201          } // Parse payload type
41202
41203
41204          while (bytes[i] === 0xFF) {
41205            payloadType += 255;
41206            i++;
41207          }
41208
41209          payloadType += bytes[i++]; // Parse payload size
41210
41211          while (bytes[i] === 0xFF) {
41212            payloadSize += 255;
41213            i++;
41214          }
41215
41216          payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
41217          // there can only ever be one caption message in a frame's sei
41218
41219          if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
41220            result.payloadType = payloadType;
41221            result.payloadSize = payloadSize;
41222            result.payload = bytes.subarray(i, i + payloadSize);
41223            break;
41224          } // skip the payload and parse the next message
41225
41226
41227          i += payloadSize;
41228          payloadType = 0;
41229          payloadSize = 0;
41230        }
41231
41232        return result;
41233      }; // see ANSI/SCTE 128-1 (2013), section 8.1
41234
41235
41236      var parseUserData = function parseUserData(sei) {
41237        // itu_t_t35_contry_code must be 181 (United States) for
41238        // captions
41239        if (sei.payload[0] !== 181) {
41240          return null;
41241        } // itu_t_t35_provider_code should be 49 (ATSC) for captions
41242
41243
41244        if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
41245          return null;
41246        } // the user_identifier should be "GA94" to indicate ATSC1 data
41247
41248
41249        if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
41250          return null;
41251        } // finally, user_data_type_code should be 0x03 for caption data
41252
41253
41254        if (sei.payload[7] !== 0x03) {
41255          return null;
41256        } // return the user_data_type_structure and strip the trailing
41257        // marker bits
41258
41259
41260        return sei.payload.subarray(8, sei.payload.length - 1);
41261      }; // see CEA-708-D, section 4.4
41262
41263
41264      var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
41265        var results = [],
41266            i,
41267            count,
41268            offset,
41269            data; // if this is just filler, return immediately
41270
41271        if (!(userData[0] & 0x40)) {
41272          return results;
41273        } // parse out the cc_data_1 and cc_data_2 fields
41274
41275
41276        count = userData[0] & 0x1f;
41277
41278        for (i = 0; i < count; i++) {
41279          offset = i * 3;
41280          data = {
41281            type: userData[offset + 2] & 0x03,
41282            pts: pts
41283          }; // capture cc data when cc_valid is 1
41284
41285          if (userData[offset + 2] & 0x04) {
41286            data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
41287            results.push(data);
41288          }
41289        }
41290
41291        return results;
41292      };
41293
41294      var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
41295        var length = data.byteLength,
41296            emulationPreventionBytesPositions = [],
41297            i = 1,
41298            newLength,
41299            newData; // Find all `Emulation Prevention Bytes`
41300
41301        while (i < length - 2) {
41302          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
41303            emulationPreventionBytesPositions.push(i + 2);
41304            i += 2;
41305          } else {
41306            i++;
41307          }
41308        } // If no Emulation Prevention Bytes were found just return the original
41309        // array
41310
41311
41312        if (emulationPreventionBytesPositions.length === 0) {
41313          return data;
41314        } // Create a new array to hold the NAL unit data
41315
41316
41317        newLength = length - emulationPreventionBytesPositions.length;
41318        newData = new Uint8Array(newLength);
41319        var sourceIndex = 0;
41320
41321        for (i = 0; i < newLength; sourceIndex++, i++) {
41322          if (sourceIndex === emulationPreventionBytesPositions[0]) {
41323            // Skip this byte
41324            sourceIndex++; // Remove this position index
41325
41326            emulationPreventionBytesPositions.shift();
41327          }
41328
41329          newData[i] = data[sourceIndex];
41330        }
41331
41332        return newData;
41333      }; // exports
41334
41335
41336      var captionPacketParser = {
41337        parseSei: parseSei,
41338        parseUserData: parseUserData,
41339        parseCaptionPackets: parseCaptionPackets,
41340        discardEmulationPreventionBytes: discardEmulationPreventionBytes,
41341        USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
41342      }; // -----------------
41343      // Link To Transport
41344      // -----------------
41345
41346      var CaptionStream = function CaptionStream() {
41347        CaptionStream.prototype.init.call(this);
41348        this.captionPackets_ = [];
41349        this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
41350        new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
41351        new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
41352        new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
41353        ];
41354        this.reset(); // forward data and done events from CCs to this CaptionStream
41355
41356        this.ccStreams_.forEach(function (cc) {
41357          cc.on('data', this.trigger.bind(this, 'data'));
41358          cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
41359          cc.on('done', this.trigger.bind(this, 'done'));
41360        }, this);
41361      };
41362
41363      CaptionStream.prototype = new stream();
41364
41365      CaptionStream.prototype.push = function (event) {
41366        var sei, userData, newCaptionPackets; // only examine SEI NALs
41367
41368        if (event.nalUnitType !== 'sei_rbsp') {
41369          return;
41370        } // parse the sei
41371
41372
41373        sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
41374
41375        if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
41376          return;
41377        } // parse out the user data payload
41378
41379
41380        userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
41381
41382        if (!userData) {
41383          return;
41384        } // Sometimes, the same segment # will be downloaded twice. To stop the
41385        // caption data from being processed twice, we track the latest dts we've
41386        // received and ignore everything with a dts before that. However, since
41387        // data for a specific dts can be split across packets on either side of
41388        // a segment boundary, we need to make sure we *don't* ignore the packets
41389        // from the *next* segment that have dts === this.latestDts_. By constantly
41390        // tracking the number of packets received with dts === this.latestDts_, we
41391        // know how many should be ignored once we start receiving duplicates.
41392
41393
41394        if (event.dts < this.latestDts_) {
41395          // We've started getting older data, so set the flag.
41396          this.ignoreNextEqualDts_ = true;
41397          return;
41398        } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
41399          this.numSameDts_--;
41400
41401          if (!this.numSameDts_) {
41402            // We've received the last duplicate packet, time to start processing again
41403            this.ignoreNextEqualDts_ = false;
41404          }
41405
41406          return;
41407        } // parse out CC data packets and save them for later
41408
41409
41410        newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
41411        this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
41412
41413        if (this.latestDts_ !== event.dts) {
41414          this.numSameDts_ = 0;
41415        }
41416
41417        this.numSameDts_++;
41418        this.latestDts_ = event.dts;
41419      };
41420
vendor: 5,244 bytes, lines 41421-41554
41421      CaptionStream.prototype.flushCCStreams = function (flushType) {
41422        this.ccStreams_.forEach(function (cc) {
41423          return flushType === 'flush' ? cc.flush() : cc.partialFlush();
41424        }, this);
41425      };
41426
41427      CaptionStream.prototype.flushStream = function (flushType) {
41428        // make sure we actually parsed captions before proceeding
41429        if (!this.captionPackets_.length) {
41430          this.flushCCStreams(flushType);
41431          return;
41432        } // In Chrome, the Array#sort function is not stable so add a
41433        // presortIndex that we can use to ensure we get a stable-sort
41434
41435
41436        this.captionPackets_.forEach(function (elem, idx) {
41437          elem.presortIndex = idx;
41438        }); // sort caption byte-pairs based on their PTS values
41439
41440        this.captionPackets_.sort(function (a, b) {
41441          if (a.pts === b.pts) {
41442            return a.presortIndex - b.presortIndex;
41443          }
41444
41445          return a.pts - b.pts;
41446        });
41447        this.captionPackets_.forEach(function (packet) {
41448          if (packet.type < 2) {
41449            // Dispatch packet to the right Cea608Stream
41450            this.dispatchCea608Packet(packet);
41451          } // this is where an 'else' would go for a dispatching packets
41452          // to a theoretical Cea708Stream that handles SERVICEn data
41453
41454        }, this);
41455        this.captionPackets_.length = 0;
41456        this.flushCCStreams(flushType);
41457      };
41458
41459      CaptionStream.prototype.flush = function () {
41460        return this.flushStream('flush');
41461      }; // Only called if handling partial data
41462
41463
41464      CaptionStream.prototype.partialFlush = function () {
41465        return this.flushStream('partialFlush');
41466      };
41467
41468      CaptionStream.prototype.reset = function () {
41469        this.latestDts_ = null;
41470        this.ignoreNextEqualDts_ = false;
41471        this.numSameDts_ = 0;
41472        this.activeCea608Channel_ = [null, null];
41473        this.ccStreams_.forEach(function (ccStream) {
41474          ccStream.reset();
41475        });
41476      }; // From the CEA-608 spec:
41477
41478      /*
41479       * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
41480       * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
41481       * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
41482       * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
41483       * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
41484       * to switch to captioning or Text.
41485      */
41486      // With that in mind, we ignore any data between an XDS control code and a
41487      // subsequent closed-captioning control code.
41488
41489
41490      CaptionStream.prototype.dispatchCea608Packet = function (packet) {
41491        // NOTE: packet.type is the CEA608 field
41492        if (this.setsTextOrXDSActive(packet)) {
41493          this.activeCea608Channel_[packet.type] = null;
41494        } else if (this.setsChannel1Active(packet)) {
41495          this.activeCea608Channel_[packet.type] = 0;
41496        } else if (this.setsChannel2Active(packet)) {
41497          this.activeCea608Channel_[packet.type] = 1;
41498        }
41499
41500        if (this.activeCea608Channel_[packet.type] === null) {
41501          // If we haven't received anything to set the active channel, or the
41502          // packets are Text/XDS data, discard the data; we don't want jumbled
41503          // captions
41504          return;
41505        }
41506
41507        this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
41508      };
41509
41510      CaptionStream.prototype.setsChannel1Active = function (packet) {
41511        return (packet.ccData & 0x7800) === 0x1000;
41512      };
41513
41514      CaptionStream.prototype.setsChannel2Active = function (packet) {
41515        return (packet.ccData & 0x7800) === 0x1800;
41516      };
41517
41518      CaptionStream.prototype.setsTextOrXDSActive = function (packet) {
41519        return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
41520      }; // ----------------------
41521      // Session to Application
41522      // ----------------------
41523      // This hash maps non-ASCII, special, and extended character codes to their
41524      // proper Unicode equivalent. The first keys that are only a single byte
41525      // are the non-standard ASCII characters, which simply map the CEA608 byte
41526      // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
41527      // character codes, but have their MSB bitmasked with 0x03 so that a lookup
41528      // can be performed regardless of the field and data channel on which the
41529      // character code was received.
41530
41531
41532      var CHARACTER_TRANSLATION = {
41533        0x2a: 0xe1,
41534        // á
41535        0x5c: 0xe9,
41536        // é
41537        0x5e: 0xed,
41538        // í
41539        0x5f: 0xf3,
41540        // ó
41541        0x60: 0xfa,
41542        // ú
41543        0x7b: 0xe7,
41544        // ç
41545        0x7c: 0xf7,
41546        // ÷
41547        0x7d: 0xd1,
41548        // Ñ
41549        0x7e: 0xf1,
41550        // ñ
41551        0x7f: 0x2588,
41552        // █
41553        0x0130: 0xae,
41554        // ®
41555        0x0131: 0xb0,
41556        // °
41557        0x0132: 0xbd,
41558        // ½
41559        0x0133: 0xbf,
41560        // ¿
41561        0x0134: 0x2122,
41562        // ™
41563        0x0135: 0xa2,
41564        // ¢
41565        0x0136: 0xa3,
41566        // £
41567        0x0137: 0x266a,
41568        // ♪
41569        0x0138: 0xe0,
41570        // à
41571        0x0139: 0xa0,
41572        //
41573        0x013a: 0xe8,
41574        // è
41575        0x013b: 0xe2,
41576        // â
41577        0x013c: 0xea,
41578        // ê
41579        0x013d: 0xee,
41580        // î
41581        0x013e: 0xf4,
41582        // ô
41583        0x013f: 0xfb,
41584        // û
41585        0x0220: 0xc1,
41586        // Á
41587        0x0221: 0xc9,
41588        // É
41589        0x0222: 0xd3,
41590        // Ó
41591        0x0223: 0xda,
41592        // Ú
41593        0x0224: 0xdc,
41594        // Ü
41595        0x0225: 0xfc,
41596        // ü
41597        0x0226: 0x2018,
41598        // ‘
41599        0x0227: 0xa1,
41600        // ¡
41601        0x0228: 0x2a,
41602        // *
41603        0x0229: 0x27,
41604        // '
41605        0x022a: 0x2014,
41606        // —
41607        0x022b: 0xa9,
41608        // ©
41609        0x022c: 0x2120,
41610        // ℠
41611        0x022d: 0x2022,
41612        // •
41613        0x022e: 0x201c,
41614        // “
41615        0x022f: 0x201d,
41616        // ”
41617        0x0230: 0xc0,
41618        // À
41619        0x0231: 0xc2,
41620        // Â
41621        0x0232: 0xc7,
41622        // Ç
41623        0x0233: 0xc8,
41624        // È
41625        0x0234: 0xca,
41626        // Ê
41627        0x0235: 0xcb,
41628        // Ë
41629        0x0236: 0xeb,
41630        // ë
41631        0x0237: 0xce,
41632        // Î
41633        0x0238: 0xcf,
41634        // Ï
41635        0x0239: 0xef,
41636        // ï
41637        0x023a: 0xd4,
41638        // Ô
41639        0x023b: 0xd9,
41640        // Ù
41641        0x023c: 0xf9,
41642        // ù
41643        0x023d: 0xdb,
41644        // Û
41645        0x023e: 0xab,
41646        // «
41647        0x023f: 0xbb,
41648        // »
41649        0x0320: 0xc3,
41650        // Ã
41651        0x0321: 0xe3,
41652        // ã
41653        0x0322: 0xcd,
41654        // Í
41655        0x0323: 0xcc,
41656        // Ì
41657        0x0324: 0xec,
41658        // ì
41659        0x0325: 0xd2,
41660        // Ò
41661        0x0326: 0xf2,
41662        // ò
41663        0x0327: 0xd5,
41664        // Õ
41665        0x0328: 0xf5,
41666        // õ
41667        0x0329: 0x7b,
41668        // {
41669        0x032a: 0x7d,
41670        // }
41671        0x032b: 0x5c,
41672        // \
41673        0x032c: 0x5e,
41674        // ^
41675        0x032d: 0x5f,
41676        // _
41677        0x032e: 0x7c,
41678        // |
41679        0x032f: 0x7e,
41680        // ~
41681        0x0330: 0xc4,
41682        // Ä
41683        0x0331: 0xe4,
vendor: 20,174 bytes, lines 41684-42183
41684        // ä
41685        0x0332: 0xd6,
41686        // Ö
41687        0x0333: 0xf6,
41688        // ö
41689        0x0334: 0xdf,
41690        // ß
41691        0x0335: 0xa5,
41692        // ¥
41693        0x0336: 0xa4,
41694        // ¤
41695        0x0337: 0x2502,
41696        // │
41697        0x0338: 0xc5,
41698        // Å
41699        0x0339: 0xe5,
41700        // å
41701        0x033a: 0xd8,
41702        // Ø
41703        0x033b: 0xf8,
41704        // ø
41705        0x033c: 0x250c,
41706        // ┌
41707        0x033d: 0x2510,
41708        // ┐
41709        0x033e: 0x2514,
41710        // └
41711        0x033f: 0x2518 // ┘
41712
41713      };
41714
41715      var getCharFromCode = function getCharFromCode(code) {
41716        if (code === null) {
41717          return '';
41718        }
41719
41720        code = CHARACTER_TRANSLATION[code] || code;
41721        return String.fromCharCode(code);
41722      }; // the index of the last row in a CEA-608 display buffer
41723
41724
41725      var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
41726      // getting it through bit logic.
41727
41728      var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
41729      // cells. The "bottom" row is the last element in the outer array.
41730
41731      var createDisplayBuffer = function createDisplayBuffer() {
41732        var result = [],
41733            i = BOTTOM_ROW + 1;
41734
41735        while (i--) {
41736          result.push('');
41737        }
41738
41739        return result;
41740      };
41741
41742      var Cea608Stream = function Cea608Stream(field, dataChannel) {
41743        Cea608Stream.prototype.init.call(this);
41744        this.field_ = field || 0;
41745        this.dataChannel_ = dataChannel || 0;
41746        this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
41747        this.setConstants();
41748        this.reset();
41749
41750        this.push = function (packet) {
41751          var data, swap, char0, char1, text; // remove the parity bits
41752
41753          data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
41754
41755          if (data === this.lastControlCode_) {
41756            this.lastControlCode_ = null;
41757            return;
41758          } // Store control codes
41759
41760
41761          if ((data & 0xf000) === 0x1000) {
41762            this.lastControlCode_ = data;
41763          } else if (data !== this.PADDING_) {
41764            this.lastControlCode_ = null;
41765          }
41766
41767          char0 = data >>> 8;
41768          char1 = data & 0xff;
41769
41770          if (data === this.PADDING_) {
41771            return;
41772          } else if (data === this.RESUME_CAPTION_LOADING_) {
41773            this.mode_ = 'popOn';
41774          } else if (data === this.END_OF_CAPTION_) {
41775            // If an EOC is received while in paint-on mode, the displayed caption
41776            // text should be swapped to non-displayed memory as if it was a pop-on
41777            // caption. Because of that, we should explicitly switch back to pop-on
41778            // mode
41779            this.mode_ = 'popOn';
41780            this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
41781
41782            this.flushDisplayed(packet.pts); // flip memory
41783
41784            swap = this.displayed_;
41785            this.displayed_ = this.nonDisplayed_;
41786            this.nonDisplayed_ = swap; // start measuring the time to display the caption
41787
41788            this.startPts_ = packet.pts;
41789          } else if (data === this.ROLL_UP_2_ROWS_) {
41790            this.rollUpRows_ = 2;
41791            this.setRollUp(packet.pts);
41792          } else if (data === this.ROLL_UP_3_ROWS_) {
41793            this.rollUpRows_ = 3;
41794            this.setRollUp(packet.pts);
41795          } else if (data === this.ROLL_UP_4_ROWS_) {
41796            this.rollUpRows_ = 4;
41797            this.setRollUp(packet.pts);
41798          } else if (data === this.CARRIAGE_RETURN_) {
41799            this.clearFormatting(packet.pts);
41800            this.flushDisplayed(packet.pts);
41801            this.shiftRowsUp_();
41802            this.startPts_ = packet.pts;
41803          } else if (data === this.BACKSPACE_) {
41804            if (this.mode_ === 'popOn') {
41805              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
41806            } else {
41807              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
41808            }
41809          } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
41810            this.flushDisplayed(packet.pts);
41811            this.displayed_ = createDisplayBuffer();
41812          } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
41813            this.nonDisplayed_ = createDisplayBuffer();
41814          } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
41815            if (this.mode_ !== 'paintOn') {
41816              // NOTE: This should be removed when proper caption positioning is
41817              // implemented
41818              this.flushDisplayed(packet.pts);
41819              this.displayed_ = createDisplayBuffer();
41820            }
41821
41822            this.mode_ = 'paintOn';
41823            this.startPts_ = packet.pts; // Append special characters to caption text
41824          } else if (this.isSpecialCharacter(char0, char1)) {
41825            // Bitmask char0 so that we can apply character transformations
41826            // regardless of field and data channel.
41827            // Then byte-shift to the left and OR with char1 so we can pass the
41828            // entire character code to `getCharFromCode`.
41829            char0 = (char0 & 0x03) << 8;
41830            text = getCharFromCode(char0 | char1);
41831            this[this.mode_](packet.pts, text);
41832            this.column_++; // Append extended characters to caption text
41833          } else if (this.isExtCharacter(char0, char1)) {
41834            // Extended characters always follow their "non-extended" equivalents.
41835            // IE if a "è" is desired, you'll always receive "eè"; non-compliant
41836            // decoders are supposed to drop the "è", while compliant decoders
41837            // backspace the "e" and insert "è".
41838            // Delete the previous character
41839            if (this.mode_ === 'popOn') {
41840              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
41841            } else {
41842              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
41843            } // Bitmask char0 so that we can apply character transformations
41844            // regardless of field and data channel.
41845            // Then byte-shift to the left and OR with char1 so we can pass the
41846            // entire character code to `getCharFromCode`.
41847
41848
41849            char0 = (char0 & 0x03) << 8;
41850            text = getCharFromCode(char0 | char1);
41851            this[this.mode_](packet.pts, text);
41852            this.column_++; // Process mid-row codes
41853          } else if (this.isMidRowCode(char0, char1)) {
41854            // Attributes are not additive, so clear all formatting
41855            this.clearFormatting(packet.pts); // According to the standard, mid-row codes
41856            // should be replaced with spaces, so add one now
41857
41858            this[this.mode_](packet.pts, ' ');
41859            this.column_++;
41860
41861            if ((char1 & 0xe) === 0xe) {
41862              this.addFormatting(packet.pts, ['i']);
41863            }
41864
41865            if ((char1 & 0x1) === 0x1) {
41866              this.addFormatting(packet.pts, ['u']);
41867            } // Detect offset control codes and adjust cursor
41868
41869          } else if (this.isOffsetControlCode(char0, char1)) {
41870            // Cursor position is set by indent PAC (see below) in 4-column
41871            // increments, with an additional offset code of 1-3 to reach any
41872            // of the 32 columns specified by CEA-608. So all we need to do
41873            // here is increment the column cursor by the given offset.
41874            this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
41875          } else if (this.isPAC(char0, char1)) {
41876            // There's no logic for PAC -> row mapping, so we have to just
41877            // find the row code in an array and use its index :(
41878            var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
41879
41880            if (this.mode_ === 'rollUp') {
41881              // This implies that the base row is incorrectly set.
41882              // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
41883              // of roll-up rows set.
41884              if (row - this.rollUpRows_ + 1 < 0) {
41885                row = this.rollUpRows_ - 1;
41886              }
41887
41888              this.setRollUp(packet.pts, row);
41889            }
41890
41891            if (row !== this.row_) {
41892              // formatting is only persistent for current row
41893              this.clearFormatting(packet.pts);
41894              this.row_ = row;
41895            } // All PACs can apply underline, so detect and apply
41896            // (All odd-numbered second bytes set underline)
41897
41898
41899            if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
41900              this.addFormatting(packet.pts, ['u']);
41901            }
41902
41903            if ((data & 0x10) === 0x10) {
41904              // We've got an indent level code. Each successive even number
41905              // increments the column cursor by 4, so we can get the desired
41906              // column position by bit-shifting to the right (to get n/2)
41907              // and multiplying by 4.
41908              this.column_ = ((data & 0xe) >> 1) * 4;
41909            }
41910
41911            if (this.isColorPAC(char1)) {
41912              // it's a color code, though we only support white, which
41913              // can be either normal or italicized. white italics can be
41914              // either 0x4e or 0x6e depending on the row, so we just
41915              // bitwise-and with 0xe to see if italics should be turned on
41916              if ((char1 & 0xe) === 0xe) {
41917                this.addFormatting(packet.pts, ['i']);
41918              }
41919            } // We have a normal character in char0, and possibly one in char1
41920
41921          } else if (this.isNormalChar(char0)) {
41922            if (char1 === 0x00) {
41923              char1 = null;
41924            }
41925
41926            text = getCharFromCode(char0);
41927            text += getCharFromCode(char1);
41928            this[this.mode_](packet.pts, text);
41929            this.column_ += text.length;
41930          } // finish data processing
41931
41932        };
41933      };
41934
41935      Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
41936      // display buffer
41937
41938      Cea608Stream.prototype.flushDisplayed = function (pts) {
41939        var content = this.displayed_ // remove spaces from the start and end of the string
41940        .map(function (row) {
41941          try {
41942            return row.trim();
41943          } catch (e) {
41944            // Ordinarily, this shouldn't happen. However, caption
41945            // parsing errors should not throw exceptions and
41946            // break playback.
41947            // eslint-disable-next-line no-console
41948            console.error('Skipping malformed caption.');
41949            return '';
41950          }
41951        }) // combine all text rows to display in one cue
41952        .join('\n') // and remove blank rows from the start and end, but not the middle
41953        .replace(/^\n+|\n+$/g, '');
41954
41955        if (content.length) {
41956          this.trigger('data', {
41957            startPts: this.startPts_,
41958            endPts: pts,
41959            text: content,
41960            stream: this.name_
41961          });
41962        }
41963      };
41964      /**
41965       * Zero out the data, used for startup and on seek
41966       */
41967
41968
41969      Cea608Stream.prototype.reset = function () {
41970        this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
41971        // actually display captions. If a caption is shifted to a row
41972        // with a lower index than this, it is cleared from the display
41973        // buffer
41974
41975        this.topRow_ = 0;
41976        this.startPts_ = 0;
41977        this.displayed_ = createDisplayBuffer();
41978        this.nonDisplayed_ = createDisplayBuffer();
41979        this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
41980
41981        this.column_ = 0;
41982        this.row_ = BOTTOM_ROW;
41983        this.rollUpRows_ = 2; // This variable holds currently-applied formatting
41984
41985        this.formatting_ = [];
41986      };
41987      /**
41988       * Sets up control code and related constants for this instance
41989       */
41990
41991
41992      Cea608Stream.prototype.setConstants = function () {
41993        // The following attributes have these uses:
41994        // ext_ :    char0 for mid-row codes, and the base for extended
41995        //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
41996        //           extended codes)
41997        // control_: char0 for control codes, except byte-shifted to the
41998        //           left so that we can do this.control_ | CONTROL_CODE
41999        // offset_:  char0 for tab offset codes
42000        //
42001        // It's also worth noting that control codes, and _only_ control codes,
42002        // differ between field 1 and field2. Field 2 control codes are always
42003        // their field 1 value plus 1. That's why there's the "| field" on the
42004        // control value.
42005        if (this.dataChannel_ === 0) {
42006          this.BASE_ = 0x10;
42007          this.EXT_ = 0x11;
42008          this.CONTROL_ = (0x14 | this.field_) << 8;
42009          this.OFFSET_ = 0x17;
42010        } else if (this.dataChannel_ === 1) {
42011          this.BASE_ = 0x18;
42012          this.EXT_ = 0x19;
42013          this.CONTROL_ = (0x1c | this.field_) << 8;
42014          this.OFFSET_ = 0x1f;
42015        } // Constants for the LSByte command codes recognized by Cea608Stream. This
42016        // list is not exhaustive. For a more comprehensive listing and semantics see
42017        // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
42018        // Padding
42019
42020
42021        this.PADDING_ = 0x0000; // Pop-on Mode
42022
42023        this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
42024        this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
42025
42026        this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
42027        this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
42028        this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
42029        this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
42030
42031        this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
42032
42033        this.BACKSPACE_ = this.CONTROL_ | 0x21;
42034        this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
42035        this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
42036      };
42037      /**
42038       * Detects if the 2-byte packet data is a special character
42039       *
42040       * Special characters have a second byte in the range 0x30 to 0x3f,
42041       * with the first byte being 0x11 (for data channel 1) or 0x19 (for
42042       * data channel 2).
42043       *
42044       * @param  {Integer} char0 The first byte
42045       * @param  {Integer} char1 The second byte
42046       * @return {Boolean}       Whether the 2 bytes are an special character
42047       */
42048
42049
42050      Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
42051        return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
42052      };
42053      /**
42054       * Detects if the 2-byte packet data is an extended character
42055       *
42056       * Extended characters have a second byte in the range 0x20 to 0x3f,
42057       * with the first byte being 0x12 or 0x13 (for data channel 1) or
42058       * 0x1a or 0x1b (for data channel 2).
42059       *
42060       * @param  {Integer} char0 The first byte
42061       * @param  {Integer} char1 The second byte
42062       * @return {Boolean}       Whether the 2 bytes are an extended character
42063       */
42064
42065
42066      Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
42067        return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
42068      };
42069      /**
42070       * Detects if the 2-byte packet is a mid-row code
42071       *
42072       * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
42073       * the first byte being 0x11 (for data channel 1) or 0x19 (for data
42074       * channel 2).
42075       *
42076       * @param  {Integer} char0 The first byte
42077       * @param  {Integer} char1 The second byte
42078       * @return {Boolean}       Whether the 2 bytes are a mid-row code
42079       */
42080
42081
42082      Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
42083        return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
42084      };
42085      /**
42086       * Detects if the 2-byte packet is an offset control code
42087       *
42088       * Offset control codes have a second byte in the range 0x21 to 0x23,
42089       * with the first byte being 0x17 (for data channel 1) or 0x1f (for
42090       * data channel 2).
42091       *
42092       * @param  {Integer} char0 The first byte
42093       * @param  {Integer} char1 The second byte
42094       * @return {Boolean}       Whether the 2 bytes are an offset control code
42095       */
42096
42097
42098      Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
42099        return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
42100      };
42101      /**
42102       * Detects if the 2-byte packet is a Preamble Address Code
42103       *
42104       * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
42105       * or 0x18 to 0x1f (for data channel 2), with the second byte in the
42106       * range 0x40 to 0x7f.
42107       *
42108       * @param  {Integer} char0 The first byte
42109       * @param  {Integer} char1 The second byte
42110       * @return {Boolean}       Whether the 2 bytes are a PAC
42111       */
42112
42113
42114      Cea608Stream.prototype.isPAC = function (char0, char1) {
42115        return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
42116      };
42117      /**
42118       * Detects if a packet's second byte is in the range of a PAC color code
42119       *
42120       * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
42121       * 0x60 to 0x6f.
42122       *
42123       * @param  {Integer} char1 The second byte
42124       * @return {Boolean}       Whether the byte is a color PAC
42125       */
42126
42127
42128      Cea608Stream.prototype.isColorPAC = function (char1) {
42129        return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
42130      };
42131      /**
42132       * Detects if a single byte is in the range of a normal character
42133       *
42134       * Normal text bytes are in the range 0x20 to 0x7f.
42135       *
42136       * @param  {Integer} char  The byte
42137       * @return {Boolean}       Whether the byte is a normal character
42138       */
42139
42140
42141      Cea608Stream.prototype.isNormalChar = function (_char) {
42142        return _char >= 0x20 && _char <= 0x7f;
42143      };
42144      /**
42145       * Configures roll-up
42146       *
42147       * @param  {Integer} pts         Current PTS
42148       * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
42149       *                               a new position
42150       */
42151
42152
42153      Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
42154        // Reset the base row to the bottom row when switching modes
42155        if (this.mode_ !== 'rollUp') {
42156          this.row_ = BOTTOM_ROW;
42157          this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
42158
42159          this.flushDisplayed(pts);
42160          this.nonDisplayed_ = createDisplayBuffer();
42161          this.displayed_ = createDisplayBuffer();
42162        }
42163
42164        if (newBaseRow !== undefined && newBaseRow !== this.row_) {
42165          // move currently displayed captions (up or down) to the new base row
42166          for (var i = 0; i < this.rollUpRows_; i++) {
42167            this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
42168            this.displayed_[this.row_ - i] = '';
42169          }
42170        }
42171
42172        if (newBaseRow === undefined) {
42173          newBaseRow = this.row_;
42174        }
42175
42176        this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
42177      }; // Adds the opening HTML tag for the passed character to the caption text,
42178      // and keeps track of it for later closing
42179
42180
42181      Cea608Stream.prototype.addFormatting = function (pts, format) {
42182        this.formatting_ = this.formatting_.concat(format);
42183        var text = format.reduce(function (text, format) {
vendor: 16,176 bytes, lines 42184-42626
42184          return text + '<' + format + '>';
42185        }, '');
42186        this[this.mode_](pts, text);
42187      }; // Adds HTML closing tags for current formatting to caption text and
42188      // clears remembered formatting
42189
42190
42191      Cea608Stream.prototype.clearFormatting = function (pts) {
42192        if (!this.formatting_.length) {
42193          return;
42194        }
42195
42196        var text = this.formatting_.reverse().reduce(function (text, format) {
42197          return text + '</' + format + '>';
42198        }, '');
42199        this.formatting_ = [];
42200        this[this.mode_](pts, text);
42201      }; // Mode Implementations
42202
42203
42204      Cea608Stream.prototype.popOn = function (pts, text) {
42205        var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
42206
42207        baseRow += text;
42208        this.nonDisplayed_[this.row_] = baseRow;
42209      };
42210
42211      Cea608Stream.prototype.rollUp = function (pts, text) {
42212        var baseRow = this.displayed_[this.row_];
42213        baseRow += text;
42214        this.displayed_[this.row_] = baseRow;
42215      };
42216
42217      Cea608Stream.prototype.shiftRowsUp_ = function () {
42218        var i; // clear out inactive rows
42219
42220        for (i = 0; i < this.topRow_; i++) {
42221          this.displayed_[i] = '';
42222        }
42223
42224        for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
42225          this.displayed_[i] = '';
42226        } // shift displayed rows up
42227
42228
42229        for (i = this.topRow_; i < this.row_; i++) {
42230          this.displayed_[i] = this.displayed_[i + 1];
42231        } // clear out the bottom row
42232
42233
42234        this.displayed_[this.row_] = '';
42235      };
42236
42237      Cea608Stream.prototype.paintOn = function (pts, text) {
42238        var baseRow = this.displayed_[this.row_];
42239        baseRow += text;
42240        this.displayed_[this.row_] = baseRow;
42241      }; // exports
42242
42243
42244      var captionStream = {
42245        CaptionStream: CaptionStream,
42246        Cea608Stream: Cea608Stream
42247      };
42248      /**
42249       * mux.js
42250       *
42251       * Copyright (c) Brightcove
42252       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
42253       */
42254
42255      var streamTypes = {
42256        H264_STREAM_TYPE: 0x1B,
42257        ADTS_STREAM_TYPE: 0x0F,
42258        METADATA_STREAM_TYPE: 0x15
42259      };
42260      var MAX_TS = 8589934592;
42261      var RO_THRESH = 4294967296;
42262      var TYPE_SHARED = 'shared';
42263
42264      var handleRollover = function handleRollover(value, reference) {
42265        var direction = 1;
42266
42267        if (value > reference) {
42268          // If the current timestamp value is greater than our reference timestamp and we detect a
42269          // timestamp rollover, this means the roll over is happening in the opposite direction.
42270          // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
42271          // point will be set to a small number, e.g. 1. The user then seeks backwards over the
42272          // rollover point. In loading this segment, the timestamp values will be very large,
42273          // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
42274          // the time stamp to be `value - 2^33`.
42275          direction = -1;
42276        } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
42277        // cause an incorrect adjustment.
42278
42279
42280        while (Math.abs(reference - value) > RO_THRESH) {
42281          value += direction * MAX_TS;
42282        }
42283
42284        return value;
42285      };
42286
42287      var TimestampRolloverStream = function TimestampRolloverStream(type) {
42288        var lastDTS, referenceDTS;
42289        TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
42290        // video and audio. We could use `undefined` here, but having a string
42291        // makes debugging a little clearer.
42292
42293        this.type_ = type || TYPE_SHARED;
42294
42295        this.push = function (data) {
42296          // Any "shared" rollover streams will accept _all_ data. Otherwise,
42297          // streams will only accept data that matches their type.
42298          if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
42299            return;
42300          }
42301
42302          if (referenceDTS === undefined) {
42303            referenceDTS = data.dts;
42304          }
42305
42306          data.dts = handleRollover(data.dts, referenceDTS);
42307          data.pts = handleRollover(data.pts, referenceDTS);
42308          lastDTS = data.dts;
42309          this.trigger('data', data);
42310        };
42311
42312        this.flush = function () {
42313          referenceDTS = lastDTS;
42314          this.trigger('done');
42315        };
42316
42317        this.endTimeline = function () {
42318          this.flush();
42319          this.trigger('endedtimeline');
42320        };
42321
42322        this.discontinuity = function () {
42323          referenceDTS = void 0;
42324          lastDTS = void 0;
42325        };
42326
42327        this.reset = function () {
42328          this.discontinuity();
42329          this.trigger('reset');
42330        };
42331      };
42332
42333      TimestampRolloverStream.prototype = new stream();
42334      var timestampRolloverStream = {
42335        TimestampRolloverStream: TimestampRolloverStream,
42336        handleRollover: handleRollover
42337      };
42338
42339      var percentEncode = function percentEncode(bytes, start, end) {
42340        var i,
42341            result = '';
42342
42343        for (i = start; i < end; i++) {
42344          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
42345        }
42346
42347        return result;
42348      },
42349          // return the string representation of the specified byte range,
42350      // interpreted as UTf-8.
42351      parseUtf8 = function parseUtf8(bytes, start, end) {
42352        return decodeURIComponent(percentEncode(bytes, start, end));
42353      },
42354          // return the string representation of the specified byte range,
42355      // interpreted as ISO-8859-1.
42356      parseIso88591 = function parseIso88591(bytes, start, end) {
42357        return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
42358      },
42359          parseSyncSafeInteger = function parseSyncSafeInteger(data) {
42360        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
42361      },
42362          tagParsers = {
42363        TXXX: function TXXX(tag) {
42364          var i;
42365
42366          if (tag.data[0] !== 3) {
42367            // ignore frames with unrecognized character encodings
42368            return;
42369          }
42370
42371          for (i = 1; i < tag.data.length; i++) {
42372            if (tag.data[i] === 0) {
42373              // parse the text fields
42374              tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
42375
42376              tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
42377              break;
42378            }
42379          }
42380
42381          tag.data = tag.value;
42382        },
42383        WXXX: function WXXX(tag) {
42384          var i;
42385
42386          if (tag.data[0] !== 3) {
42387            // ignore frames with unrecognized character encodings
42388            return;
42389          }
42390
42391          for (i = 1; i < tag.data.length; i++) {
42392            if (tag.data[i] === 0) {
42393              // parse the description and URL fields
42394              tag.description = parseUtf8(tag.data, 1, i);
42395              tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
42396              break;
42397            }
42398          }
42399        },
42400        PRIV: function PRIV(tag) {
42401          var i;
42402
42403          for (i = 0; i < tag.data.length; i++) {
42404            if (tag.data[i] === 0) {
42405              // parse the description and URL fields
42406              tag.owner = parseIso88591(tag.data, 0, i);
42407              break;
42408            }
42409          }
42410
42411          tag.privateData = tag.data.subarray(i + 1);
42412          tag.data = tag.privateData;
42413        }
42414      },
42415          _MetadataStream;
42416
42417      _MetadataStream = function MetadataStream(options) {
42418        var settings = {
42419          debug: !!(options && options.debug),
42420          // the bytes of the program-level descriptor field in MP2T
42421          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
42422          // program element descriptors"
42423          descriptor: options && options.descriptor
42424        },
42425            // the total size in bytes of the ID3 tag being parsed
42426        tagSize = 0,
42427            // tag data that is not complete enough to be parsed
42428        buffer = [],
42429            // the total number of bytes currently in the buffer
42430        bufferSize = 0,
42431            i;
42432
42433        _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
42434        // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
42435
42436
42437        this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
42438
42439        if (settings.descriptor) {
42440          for (i = 0; i < settings.descriptor.length; i++) {
42441            this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
42442          }
42443        }
42444
42445        this.push = function (chunk) {
42446          var tag, frameStart, frameSize, frame, i, frameHeader;
42447
42448          if (chunk.type !== 'timed-metadata') {
42449            return;
42450          } // if data_alignment_indicator is set in the PES header,
42451          // we must have the start of a new ID3 tag. Assume anything
42452          // remaining in the buffer was malformed and throw it out
42453
42454
42455          if (chunk.dataAlignmentIndicator) {
42456            bufferSize = 0;
42457            buffer.length = 0;
42458          } // ignore events that don't look like ID3 data
42459
42460
42461          if (buffer.length === 0 && (chunk.data.length < 10 || chunk.data[0] !== 'I'.charCodeAt(0) || chunk.data[1] !== 'D'.charCodeAt(0) || chunk.data[2] !== '3'.charCodeAt(0))) {
42462            if (settings.debug) {
42463              // eslint-disable-next-line no-console
42464              console.log('Skipping unrecognized metadata packet');
42465            }
42466
42467            return;
42468          } // add this chunk to the data we've collected so far
42469
42470
42471          buffer.push(chunk);
42472          bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
42473
42474          if (buffer.length === 1) {
42475            // the frame size is transmitted as a 28-bit integer in the
42476            // last four bytes of the ID3 header.
42477            // The most significant bit of each byte is dropped and the
42478            // results concatenated to recover the actual value.
42479            tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
42480            // convenient for our comparisons to include it
42481
42482            tagSize += 10;
42483          } // if the entire frame has not arrived, wait for more data
42484
42485
42486          if (bufferSize < tagSize) {
42487            return;
42488          } // collect the entire frame so it can be parsed
42489
42490
42491          tag = {
42492            data: new Uint8Array(tagSize),
42493            frames: [],
42494            pts: buffer[0].pts,
42495            dts: buffer[0].dts
42496          };
42497
42498          for (i = 0; i < tagSize;) {
42499            tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
42500            i += buffer[0].data.byteLength;
42501            bufferSize -= buffer[0].data.byteLength;
42502            buffer.shift();
42503          } // find the start of the first frame and the end of the tag
42504
42505
42506          frameStart = 10;
42507
42508          if (tag.data[5] & 0x40) {
42509            // advance the frame start past the extended header
42510            frameStart += 4; // header size field
42511
42512            frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
42513
42514            tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
42515          } // parse one or more ID3 frames
42516          // http://id3.org/id3v2.3.0#ID3v2_frame_overview
42517
42518
42519          do {
42520            // determine the number of bytes in this frame
42521            frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
42522
42523            if (frameSize < 1) {
42524              // eslint-disable-next-line no-console
42525              return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
42526            }
42527
42528            frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
42529            frame = {
42530              id: frameHeader,
42531              data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
42532            };
42533            frame.key = frame.id;
42534
42535            if (tagParsers[frame.id]) {
42536              tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
42537              // time for raw AAC data
42538
42539              if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
42540                var d = frame.data,
42541                    size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
42542                size *= 4;
42543                size += d[7] & 0x03;
42544                frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
42545                // on the value of this frame
42546                // we couldn't have known the appropriate pts and dts before
42547                // parsing this ID3 tag so set those values now
42548
42549                if (tag.pts === undefined && tag.dts === undefined) {
42550                  tag.pts = frame.timeStamp;
42551                  tag.dts = frame.timeStamp;
42552                }
42553
42554                this.trigger('timestamp', frame);
42555              }
42556            }
42557
42558            tag.frames.push(frame);
42559            frameStart += 10; // advance past the frame header
42560
42561            frameStart += frameSize; // advance past the frame body
42562          } while (frameStart < tagSize);
42563
42564          this.trigger('data', tag);
42565        };
42566      };
42567
42568      _MetadataStream.prototype = new stream();
42569      var metadataStream = _MetadataStream;
42570      var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
42571
42572      var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
42573
42574
42575      var MP2T_PACKET_LENGTH = 188,
42576          // bytes
42577      SYNC_BYTE = 0x47;
42578      /**
42579       * Splits an incoming stream of binary data into MPEG-2 Transport
42580       * Stream packets.
42581       */
42582
42583      _TransportPacketStream = function TransportPacketStream() {
42584        var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
42585            bytesInBuffer = 0;
42586
42587        _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
42588
42589        /**
42590         * Split a stream of data into M2TS packets
42591        **/
42592
42593
42594        this.push = function (bytes) {
42595          var startIndex = 0,
42596              endIndex = MP2T_PACKET_LENGTH,
42597              everything; // If there are bytes remaining from the last segment, prepend them to the
42598          // bytes that were pushed in
42599
42600          if (bytesInBuffer) {
42601            everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
42602            everything.set(buffer.subarray(0, bytesInBuffer));
42603            everything.set(bytes, bytesInBuffer);
42604            bytesInBuffer = 0;
42605          } else {
42606            everything = bytes;
42607          } // While we have enough data for a packet
42608
42609
42610          while (endIndex < everything.byteLength) {
42611            // Look for a pair of start and end sync bytes in the data..
42612            if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
42613              // We found a packet so emit it and jump one whole packet forward in
42614              // the stream
42615              this.trigger('data', everything.subarray(startIndex, endIndex));
42616              startIndex += MP2T_PACKET_LENGTH;
42617              endIndex += MP2T_PACKET_LENGTH;
42618              continue;
42619            } // If we get here, we have somehow become de-synchronized and we need to step
42620            // forward one byte at a time until we find a pair of sync bytes that denote
42621            // a packet
42622
42623
42624            startIndex++;
42625            endIndex++;
42626          } // If there was some data left over at the end of the segment that couldn't
vendor: 17,064 bytes, lines 42627-43062
42627          // possibly be a whole packet, keep it because it might be the start of a packet
42628          // that continues in the next segment
42629
42630
42631          if (startIndex < everything.byteLength) {
42632            buffer.set(everything.subarray(startIndex), 0);
42633            bytesInBuffer = everything.byteLength - startIndex;
42634          }
42635        };
42636        /**
42637         * Passes identified M2TS packets to the TransportParseStream to be parsed
42638        **/
42639
42640
42641        this.flush = function () {
42642          // If the buffer contains a whole packet when we are being flushed, emit it
42643          // and empty the buffer. Otherwise hold onto the data because it may be
42644          // important for decoding the next segment
42645          if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
42646            this.trigger('data', buffer);
42647            bytesInBuffer = 0;
42648          }
42649
42650          this.trigger('done');
42651        };
42652
42653        this.endTimeline = function () {
42654          this.flush();
42655          this.trigger('endedtimeline');
42656        };
42657
42658        this.reset = function () {
42659          bytesInBuffer = 0;
42660          this.trigger('reset');
42661        };
42662      };
42663
42664      _TransportPacketStream.prototype = new stream();
42665      /**
42666       * Accepts an MP2T TransportPacketStream and emits data events with parsed
42667       * forms of the individual transport stream packets.
42668       */
42669
42670      _TransportParseStream = function TransportParseStream() {
42671        var parsePsi, parsePat, parsePmt, self;
42672
42673        _TransportParseStream.prototype.init.call(this);
42674
42675        self = this;
42676        this.packetsWaitingForPmt = [];
42677        this.programMapTable = undefined;
42678
42679        parsePsi = function parsePsi(payload, psi) {
42680          var offset = 0; // PSI packets may be split into multiple sections and those
42681          // sections may be split into multiple packets. If a PSI
42682          // section starts in this packet, the payload_unit_start_indicator
42683          // will be true and the first byte of the payload will indicate
42684          // the offset from the current position to the start of the
42685          // section.
42686
42687          if (psi.payloadUnitStartIndicator) {
42688            offset += payload[offset] + 1;
42689          }
42690
42691          if (psi.type === 'pat') {
42692            parsePat(payload.subarray(offset), psi);
42693          } else {
42694            parsePmt(payload.subarray(offset), psi);
42695          }
42696        };
42697
42698        parsePat = function parsePat(payload, pat) {
42699          pat.section_number = payload[7]; // eslint-disable-line camelcase
42700
42701          pat.last_section_number = payload[8]; // eslint-disable-line camelcase
42702          // skip the PSI header and parse the first PMT entry
42703
42704          self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
42705          pat.pmtPid = self.pmtPid;
42706        };
42707        /**
42708         * Parse out the relevant fields of a Program Map Table (PMT).
42709         * @param payload {Uint8Array} the PMT-specific portion of an MP2T
42710         * packet. The first byte in this array should be the table_id
42711         * field.
42712         * @param pmt {object} the object that should be decorated with
42713         * fields parsed from the PMT.
42714         */
42715
42716
42717        parsePmt = function parsePmt(payload, pmt) {
42718          var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
42719          // take effect. We don't believe this should ever be the case
42720          // for HLS but we'll ignore "forward" PMT declarations if we see
42721          // them. Future PMT declarations have the current_next_indicator
42722          // set to zero.
42723
42724          if (!(payload[5] & 0x01)) {
42725            return;
42726          } // overwrite any existing program map table
42727
42728
42729          self.programMapTable = {
42730            video: null,
42731            audio: null,
42732            'timed-metadata': {}
42733          }; // the mapping table ends at the end of the current section
42734
42735          sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
42736          tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
42737          // long the program info descriptors are
42738
42739          programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
42740
42741          offset = 12 + programInfoLength;
42742
42743          while (offset < tableEnd) {
42744            var streamType = payload[offset];
42745            var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
42746            // TODO: should this be done for metadata too? for now maintain behavior of
42747            //       multiple metadata streams
42748
42749            if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
42750              self.programMapTable.video = pid;
42751            } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
42752              self.programMapTable.audio = pid;
42753            } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
42754              // map pid to stream type for metadata streams
42755              self.programMapTable['timed-metadata'][pid] = streamType;
42756            } // move to the next table entry
42757            // skip past the elementary stream descriptors, if present
42758
42759
42760            offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
42761          } // record the map on the packet as well
42762
42763
42764          pmt.programMapTable = self.programMapTable;
42765        };
42766        /**
42767         * Deliver a new MP2T packet to the next stream in the pipeline.
42768         */
42769
42770
42771        this.push = function (packet) {
42772          var result = {},
42773              offset = 4;
42774          result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
42775
42776          result.pid = packet[1] & 0x1f;
42777          result.pid <<= 8;
42778          result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
42779          // fifth byte of the TS packet header. The adaptation field is
42780          // used to add stuffing to PES packets that don't fill a complete
42781          // TS packet, and to specify some forms of timing and control data
42782          // that we do not currently use.
42783
42784          if ((packet[3] & 0x30) >>> 4 > 0x01) {
42785            offset += packet[offset] + 1;
42786          } // parse the rest of the packet based on the type
42787
42788
42789          if (result.pid === 0) {
42790            result.type = 'pat';
42791            parsePsi(packet.subarray(offset), result);
42792            this.trigger('data', result);
42793          } else if (result.pid === this.pmtPid) {
42794            result.type = 'pmt';
42795            parsePsi(packet.subarray(offset), result);
42796            this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
42797
42798            while (this.packetsWaitingForPmt.length) {
42799              this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
42800            }
42801          } else if (this.programMapTable === undefined) {
42802            // When we have not seen a PMT yet, defer further processing of
42803            // PES packets until one has been parsed
42804            this.packetsWaitingForPmt.push([packet, offset, result]);
42805          } else {
42806            this.processPes_(packet, offset, result);
42807          }
42808        };
42809
42810        this.processPes_ = function (packet, offset, result) {
42811          // set the appropriate stream type
42812          if (result.pid === this.programMapTable.video) {
42813            result.streamType = streamTypes.H264_STREAM_TYPE;
42814          } else if (result.pid === this.programMapTable.audio) {
42815            result.streamType = streamTypes.ADTS_STREAM_TYPE;
42816          } else {
42817            // if not video or audio, it is timed-metadata or unknown
42818            // if unknown, streamType will be undefined
42819            result.streamType = this.programMapTable['timed-metadata'][result.pid];
42820          }
42821
42822          result.type = 'pes';
42823          result.data = packet.subarray(offset);
42824          this.trigger('data', result);
42825        };
42826      };
42827
42828      _TransportParseStream.prototype = new stream();
42829      _TransportParseStream.STREAM_TYPES = {
42830        h264: 0x1b,
42831        adts: 0x0f
42832      };
42833      /**
42834       * Reconsistutes program elementary stream (PES) packets from parsed
42835       * transport stream packets. That is, if you pipe an
42836       * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
42837       * events will be events which capture the bytes for individual PES
42838       * packets plus relevant metadata that has been extracted from the
42839       * container.
42840       */
42841
42842      _ElementaryStream = function ElementaryStream() {
42843        var self = this,
42844            // PES packet fragments
42845        video = {
42846          data: [],
42847          size: 0
42848        },
42849            audio = {
42850          data: [],
42851          size: 0
42852        },
42853            timedMetadata = {
42854          data: [],
42855          size: 0
42856        },
42857            programMapTable,
42858            parsePes = function parsePes(payload, pes) {
42859          var ptsDtsFlags; // get the packet length, this will be 0 for video
42860
42861          pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
42862
42863          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
42864          // and a DTS value. Determine what combination of values is
42865          // available to work with.
42866
42867          ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
42868          // performs all bitwise operations on 32-bit integers but javascript
42869          // supports a much greater range (52-bits) of integer using standard
42870          // mathematical operations.
42871          // We construct a 31-bit value using bitwise operators over the 31
42872          // most significant bits and then multiply by 4 (equal to a left-shift
42873          // of 2) before we add the final 2 least significant bits of the
42874          // timestamp (equal to an OR.)
42875
42876          if (ptsDtsFlags & 0xC0) {
42877            // the PTS and DTS are not written out directly. For information
42878            // on how they are encoded, see
42879            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
42880            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
42881            pes.pts *= 4; // Left shift by 2
42882
42883            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
42884
42885            pes.dts = pes.pts;
42886
42887            if (ptsDtsFlags & 0x40) {
42888              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
42889              pes.dts *= 4; // Left shift by 2
42890
42891              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
42892            }
42893          } // the data section starts immediately after the PES header.
42894          // pes_header_data_length specifies the number of header bytes
42895          // that follow the last byte of the field.
42896
42897
42898          pes.data = payload.subarray(9 + payload[8]);
42899        },
42900
42901        /**
42902          * Pass completely parsed PES packets to the next stream in the pipeline
42903         **/
42904        flushStream = function flushStream(stream$$1, type, forceFlush) {
42905          var packetData = new Uint8Array(stream$$1.size),
42906              event = {
42907            type: type
42908          },
42909              i = 0,
42910              offset = 0,
42911              packetFlushable = false,
42912              fragment; // do nothing if there is not enough buffered data for a complete
42913          // PES header
42914
42915          if (!stream$$1.data.length || stream$$1.size < 9) {
42916            return;
42917          }
42918
42919          event.trackId = stream$$1.data[0].pid; // reassemble the packet
42920
42921          for (i = 0; i < stream$$1.data.length; i++) {
42922            fragment = stream$$1.data[i];
42923            packetData.set(fragment.data, offset);
42924            offset += fragment.data.byteLength;
42925          } // parse assembled packet's PES header
42926
42927
42928          parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
42929          // check that there is enough stream data to fill the packet
42930
42931          packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
42932
42933          if (forceFlush || packetFlushable) {
42934            stream$$1.size = 0;
42935            stream$$1.data.length = 0;
42936          } // only emit packets that are complete. this is to avoid assembling
42937          // incomplete PES packets due to poor segmentation
42938
42939
42940          if (packetFlushable) {
42941            self.trigger('data', event);
42942          }
42943        };
42944
42945        _ElementaryStream.prototype.init.call(this);
42946        /**
42947         * Identifies M2TS packet types and parses PES packets using metadata
42948         * parsed from the PMT
42949         **/
42950
42951
42952        this.push = function (data) {
42953          ({
42954            pat: function pat() {// we have to wait for the PMT to arrive as well before we
42955              // have any meaningful metadata
42956            },
42957            pes: function pes() {
42958              var stream$$1, streamType;
42959
42960              switch (data.streamType) {
42961                case streamTypes.H264_STREAM_TYPE:
42962                case streamTypes.H264_STREAM_TYPE:
42963                  stream$$1 = video;
42964                  streamType = 'video';
42965                  break;
42966
42967                case streamTypes.ADTS_STREAM_TYPE:
42968                  stream$$1 = audio;
42969                  streamType = 'audio';
42970                  break;
42971
42972                case streamTypes.METADATA_STREAM_TYPE:
42973                  stream$$1 = timedMetadata;
42974                  streamType = 'timed-metadata';
42975                  break;
42976
42977                default:
42978                  // ignore unknown stream types
42979                  return;
42980              } // if a new packet is starting, we can flush the completed
42981              // packet
42982
42983
42984              if (data.payloadUnitStartIndicator) {
42985                flushStream(stream$$1, streamType, true);
42986              } // buffer this fragment until we are sure we've received the
42987              // complete payload
42988
42989
42990              stream$$1.data.push(data);
42991              stream$$1.size += data.data.byteLength;
42992            },
42993            pmt: function pmt() {
42994              var event = {
42995                type: 'metadata',
42996                tracks: []
42997              };
42998              programMapTable = data.programMapTable; // translate audio and video streams to tracks
42999
43000              if (programMapTable.video !== null) {
43001                event.tracks.push({
43002                  timelineStartInfo: {
43003                    baseMediaDecodeTime: 0
43004                  },
43005                  id: +programMapTable.video,
43006                  codec: 'avc',
43007                  type: 'video'
43008                });
43009              }
43010
43011              if (programMapTable.audio !== null) {
43012                event.tracks.push({
43013                  timelineStartInfo: {
43014                    baseMediaDecodeTime: 0
43015                  },
43016                  id: +programMapTable.audio,
43017                  codec: 'adts',
43018                  type: 'audio'
43019                });
43020              }
43021
43022              self.trigger('data', event);
43023            }
43024          })[data.type]();
43025        };
43026
43027        this.reset = function () {
43028          video.size = 0;
43029          video.data.length = 0;
43030          audio.size = 0;
43031          audio.data.length = 0;
43032          this.trigger('reset');
43033        };
43034        /**
43035         * Flush any remaining input. Video PES packets may be of variable
43036         * length. Normally, the start of a new video packet can trigger the
43037         * finalization of the previous packet. That is not possible if no
43038         * more video is forthcoming, however. In that case, some other
43039         * mechanism (like the end of the file) has to be employed. When it is
43040         * clear that no additional data is forthcoming, calling this method
43041         * will flush the buffered packets.
43042         */
43043
43044
43045        this.flushStreams_ = function () {
43046          // !!THIS ORDER IS IMPORTANT!!
43047          // video first then audio
43048          flushStream(video, 'video');
43049          flushStream(audio, 'audio');
43050          flushStream(timedMetadata, 'timed-metadata');
43051        };
43052
43053        this.flush = function () {
43054          this.flushStreams_();
43055          this.trigger('done');
43056        };
43057      };
43058
43059      _ElementaryStream.prototype = new stream();
43060      var m2ts = {
43061        PAT_PID: 0x0000,
43062        MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 6,108 bytes, lines 43063-43230
43063        TransportPacketStream: _TransportPacketStream,
43064        TransportParseStream: _TransportParseStream,
43065        ElementaryStream: _ElementaryStream,
43066        TimestampRolloverStream: TimestampRolloverStream$1,
43067        CaptionStream: captionStream.CaptionStream,
43068        Cea608Stream: captionStream.Cea608Stream,
43069        MetadataStream: metadataStream
43070      };
43071
43072      for (var type in streamTypes) {
43073        if (streamTypes.hasOwnProperty(type)) {
43074          m2ts[type] = streamTypes[type];
43075        }
43076      }
43077
43078      var m2ts_1 = m2ts;
43079      var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS;
43080
43081      var _AdtsStream;
43082
43083      var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
43084      /*
43085       * Accepts a ElementaryStream and emits data events with parsed
43086       * AAC Audio Frames of the individual packets. Input audio in ADTS
43087       * format is unpacked and re-emitted as AAC frames.
43088       *
43089       * @see http://wiki.multimedia.cx/index.php?title=ADTS
43090       * @see http://wiki.multimedia.cx/?title=Understanding_AAC
43091       */
43092
43093      _AdtsStream = function AdtsStream(handlePartialSegments) {
43094        var buffer,
43095            frameNum = 0;
43096
43097        _AdtsStream.prototype.init.call(this);
43098
43099        this.push = function (packet) {
43100          var i = 0,
43101              frameLength,
43102              protectionSkipBytes,
43103              frameEnd,
43104              oldBuffer,
43105              sampleCount,
43106              adtsFrameDuration;
43107
43108          if (!handlePartialSegments) {
43109            frameNum = 0;
43110          }
43111
43112          if (packet.type !== 'audio') {
43113            // ignore non-audio data
43114            return;
43115          } // Prepend any data in the buffer to the input data so that we can parse
43116          // aac frames the cross a PES packet boundary
43117
43118
43119          if (buffer) {
43120            oldBuffer = buffer;
43121            buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
43122            buffer.set(oldBuffer);
43123            buffer.set(packet.data, oldBuffer.byteLength);
43124          } else {
43125            buffer = packet.data;
43126          } // unpack any ADTS frames which have been fully received
43127          // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
43128
43129
43130          while (i + 5 < buffer.length) {
43131            // Look for the start of an ADTS header..
43132            if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
43133              // If a valid header was not found,  jump one forward and attempt to
43134              // find a valid ADTS header starting at the next byte
43135              i++;
43136              continue;
43137            } // The protection skip bit tells us if we have 2 bytes of CRC data at the
43138            // end of the ADTS header
43139
43140
43141            protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
43142            // end of the sync sequence
43143
43144            frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
43145            sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
43146            adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
43147            frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
43148            // and wait for more data
43149
43150            if (buffer.byteLength < frameEnd) {
43151              return;
43152            } // Otherwise, deliver the complete AAC frame
43153
43154
43155            this.trigger('data', {
43156              pts: packet.pts + frameNum * adtsFrameDuration,
43157              dts: packet.dts + frameNum * adtsFrameDuration,
43158              sampleCount: sampleCount,
43159              audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
43160              channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
43161              samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
43162              samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
43163              // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
43164              samplesize: 16,
43165              data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
43166            });
43167            frameNum++; // If the buffer is empty, clear it and return
43168
43169            if (buffer.byteLength === frameEnd) {
43170              buffer = undefined;
43171              return;
43172            } // Remove the finished frame from the buffer and start the process again
43173
43174
43175            buffer = buffer.subarray(frameEnd);
43176          }
43177        };
43178
43179        this.flush = function () {
43180          frameNum = 0;
43181          this.trigger('done');
43182        };
43183
43184        this.reset = function () {
43185          buffer = void 0;
43186          this.trigger('reset');
43187        };
43188
43189        this.endTimeline = function () {
43190          buffer = void 0;
43191          this.trigger('endedtimeline');
43192        };
43193      };
43194
43195      _AdtsStream.prototype = new stream();
43196      var adts = _AdtsStream;
43197      /**
43198       * mux.js
43199       *
43200       * Copyright (c) Brightcove
43201       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43202       */
43203
43204      var ExpGolomb;
43205      /**
43206       * Parser for exponential Golomb codes, a variable-bitwidth number encoding
43207       * scheme used by h264.
43208       */
43209
43210      ExpGolomb = function ExpGolomb(workingData) {
43211        var // the number of bytes left to examine in workingData
43212        workingBytesAvailable = workingData.byteLength,
43213            // the current word being examined
43214        workingWord = 0,
43215            // :uint
43216        // the number of bits left to examine in the current word
43217        workingBitsAvailable = 0; // :uint;
43218        // ():uint
43219
43220        this.length = function () {
43221          return 8 * workingBytesAvailable;
43222        }; // ():uint
43223
43224
43225        this.bitsAvailable = function () {
43226          return 8 * workingBytesAvailable + workingBitsAvailable;
43227        }; // ():void
43228
43229
43230        this.loadWord = function () {
vendor: 8,404 bytes, lines 43231-43508
43231          var position = workingData.byteLength - workingBytesAvailable,
43232              workingBytes = new Uint8Array(4),
43233              availableBytes = Math.min(4, workingBytesAvailable);
43234
43235          if (availableBytes === 0) {
43236            throw new Error('no bytes available');
43237          }
43238
43239          workingBytes.set(workingData.subarray(position, position + availableBytes));
43240          workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
43241
43242          workingBitsAvailable = availableBytes * 8;
43243          workingBytesAvailable -= availableBytes;
43244        }; // (count:int):void
43245
43246
43247        this.skipBits = function (count) {
43248          var skipBytes; // :int
43249
43250          if (workingBitsAvailable > count) {
43251            workingWord <<= count;
43252            workingBitsAvailable -= count;
43253          } else {
43254            count -= workingBitsAvailable;
43255            skipBytes = Math.floor(count / 8);
43256            count -= skipBytes * 8;
43257            workingBytesAvailable -= skipBytes;
43258            this.loadWord();
43259            workingWord <<= count;
43260            workingBitsAvailable -= count;
43261          }
43262        }; // (size:int):uint
43263
43264
43265        this.readBits = function (size) {
43266          var bits = Math.min(workingBitsAvailable, size),
43267              // :uint
43268          valu = workingWord >>> 32 - bits; // :uint
43269          // if size > 31, handle error
43270
43271          workingBitsAvailable -= bits;
43272
43273          if (workingBitsAvailable > 0) {
43274            workingWord <<= bits;
43275          } else if (workingBytesAvailable > 0) {
43276            this.loadWord();
43277          }
43278
43279          bits = size - bits;
43280
43281          if (bits > 0) {
43282            return valu << bits | this.readBits(bits);
43283          }
43284
43285          return valu;
43286        }; // ():uint
43287
43288
43289        this.skipLeadingZeros = function () {
43290          var leadingZeroCount; // :uint
43291
43292          for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
43293            if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
43294              // the first bit of working word is 1
43295              workingWord <<= leadingZeroCount;
43296              workingBitsAvailable -= leadingZeroCount;
43297              return leadingZeroCount;
43298            }
43299          } // we exhausted workingWord and still have not found a 1
43300
43301
43302          this.loadWord();
43303          return leadingZeroCount + this.skipLeadingZeros();
43304        }; // ():void
43305
43306
43307        this.skipUnsignedExpGolomb = function () {
43308          this.skipBits(1 + this.skipLeadingZeros());
43309        }; // ():void
43310
43311
43312        this.skipExpGolomb = function () {
43313          this.skipBits(1 + this.skipLeadingZeros());
43314        }; // ():uint
43315
43316
43317        this.readUnsignedExpGolomb = function () {
43318          var clz = this.skipLeadingZeros(); // :uint
43319
43320          return this.readBits(clz + 1) - 1;
43321        }; // ():int
43322
43323
43324        this.readExpGolomb = function () {
43325          var valu = this.readUnsignedExpGolomb(); // :int
43326
43327          if (0x01 & valu) {
43328            // the number is odd if the low order bit is set
43329            return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
43330          }
43331
43332          return -1 * (valu >>> 1); // divide by two then make it negative
43333        }; // Some convenience functions
43334        // :Boolean
43335
43336
43337        this.readBoolean = function () {
43338          return this.readBits(1) === 1;
43339        }; // ():int
43340
43341
43342        this.readUnsignedByte = function () {
43343          return this.readBits(8);
43344        };
43345
43346        this.loadWord();
43347      };
43348
43349      var expGolomb = ExpGolomb;
43350
43351      var _H264Stream, _NalByteStream;
43352
43353      var PROFILES_WITH_OPTIONAL_SPS_DATA;
43354      /**
43355       * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
43356       */
43357
43358      _NalByteStream = function NalByteStream() {
43359        var syncPoint = 0,
43360            i,
43361            buffer;
43362
43363        _NalByteStream.prototype.init.call(this);
43364        /*
43365         * Scans a byte stream and triggers a data event with the NAL units found.
43366         * @param {Object} data Event received from H264Stream
43367         * @param {Uint8Array} data.data The h264 byte stream to be scanned
43368         *
43369         * @see H264Stream.push
43370         */
43371
43372
43373        this.push = function (data) {
43374          var swapBuffer;
43375
43376          if (!buffer) {
43377            buffer = data.data;
43378          } else {
43379            swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
43380            swapBuffer.set(buffer);
43381            swapBuffer.set(data.data, buffer.byteLength);
43382            buffer = swapBuffer;
43383          }
43384
43385          var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
43386          // scan for NAL unit boundaries
43387          // a match looks like this:
43388          // 0 0 1 .. NAL .. 0 0 1
43389          // ^ sync point        ^ i
43390          // or this:
43391          // 0 0 1 .. NAL .. 0 0 0
43392          // ^ sync point        ^ i
43393          // advance the sync point to a NAL start, if necessary
43394
43395          for (; syncPoint < len - 3; syncPoint++) {
43396            if (buffer[syncPoint + 2] === 1) {
43397              // the sync point is properly aligned
43398              i = syncPoint + 5;
43399              break;
43400            }
43401          }
43402
43403          while (i < len) {
43404            // look at the current byte to determine if we've hit the end of
43405            // a NAL unit boundary
43406            switch (buffer[i]) {
43407              case 0:
43408                // skip past non-sync sequences
43409                if (buffer[i - 1] !== 0) {
43410                  i += 2;
43411                  break;
43412                } else if (buffer[i - 2] !== 0) {
43413                  i++;
43414                  break;
43415                } // deliver the NAL unit if it isn't empty
43416
43417
43418                if (syncPoint + 3 !== i - 2) {
43419                  this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
43420                } // drop trailing zeroes
43421
43422
43423                do {
43424                  i++;
43425                } while (buffer[i] !== 1 && i < len);
43426
43427                syncPoint = i - 2;
43428                i += 3;
43429                break;
43430
43431              case 1:
43432                // skip past non-sync sequences
43433                if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
43434                  i += 3;
43435                  break;
43436                } // deliver the NAL unit
43437
43438
43439                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
43440                syncPoint = i - 2;
43441                i += 3;
43442                break;
43443
43444              default:
43445                // the current byte isn't a one or zero, so it cannot be part
43446                // of a sync sequence
43447                i += 3;
43448                break;
43449            }
43450          } // filter out the NAL units that were delivered
43451
43452
43453          buffer = buffer.subarray(syncPoint);
43454          i -= syncPoint;
43455          syncPoint = 0;
43456        };
43457
43458        this.reset = function () {
43459          buffer = null;
43460          syncPoint = 0;
43461          this.trigger('reset');
43462        };
43463
43464        this.flush = function () {
43465          // deliver the last buffered NAL unit
43466          if (buffer && buffer.byteLength > 3) {
43467            this.trigger('data', buffer.subarray(syncPoint + 3));
43468          } // reset the stream state
43469
43470
43471          buffer = null;
43472          syncPoint = 0;
43473          this.trigger('done');
43474        };
43475
43476        this.endTimeline = function () {
43477          this.flush();
43478          this.trigger('endedtimeline');
43479        };
43480      };
43481
43482      _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
43483      // see Recommendation ITU-T H.264 (4/2013),
43484      // 7.3.2.1.1 Sequence parameter set data syntax
43485
43486      PROFILES_WITH_OPTIONAL_SPS_DATA = {
43487        100: true,
43488        110: true,
43489        122: true,
43490        244: true,
43491        44: true,
43492        83: true,
43493        86: true,
43494        118: true,
43495        128: true,
43496        138: true,
43497        139: true,
43498        134: true
43499      };
43500      /**
43501       * Accepts input from a ElementaryStream and produces H.264 NAL unit data
43502       * events.
43503       */
43504
43505      _H264Stream = function H264Stream() {
43506        var nalByteStream = new _NalByteStream(),
43507            self,
43508            trackId,
43509            currentPts,
43510            currentDts,
43511            discardEmulationPreventionBytes,
43512            readSequenceParameterSet,
43513            skipScalingList;
43514
43515        _H264Stream.prototype.init.call(this);
43516
43517        self = this;
43518        /*
43519         * Pushes a packet from a stream onto the NalByteStream
43520         *
43521         * @param {Object} packet - A packet received from a stream
43522         * @param {Uint8Array} packet.data - The raw bytes of the packet
43523         * @param {Number} packet.dts - Decode timestamp of the packet
43524         * @param {Number} packet.pts - Presentation timestamp of the packet
43525         * @param {Number} packet.trackId - The id of the h264 track this packet came from
43526         * @param {('video'|'audio')} packet.type - The type of packet
43527         *
43528         */
43529
43530        this.push = function (packet) {
43531          if (packet.type !== 'video') {
43532            return;
43533          }
43534
43535          trackId = packet.trackId;
43536          currentPts = packet.pts;
43537          currentDts = packet.dts;
43538          nalByteStream.push(packet);
43539        };
43540        /*
43541         * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
43542         * for the NALUs to the next stream component.
43543         * Also, preprocess caption and sequence parameter NALUs.
43544         *
43545         * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
43546         * @see NalByteStream.push
43547         */
43548
43549
43550        nalByteStream.on('data', function (data) {
43551          var event = {
43552            trackId: trackId,
vendor: 16,384 bytes, lines 43553-44023
43553            pts: currentPts,
43554            dts: currentDts,
43555            data: data
43556          };
43557
43558          switch (data[0] & 0x1f) {
43559            case 0x05:
43560              event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
43561              break;
43562
43563            case 0x06:
43564              event.nalUnitType = 'sei_rbsp';
43565              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
43566              break;
43567
43568            case 0x07:
43569              event.nalUnitType = 'seq_parameter_set_rbsp';
43570              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
43571              event.config = readSequenceParameterSet(event.escapedRBSP);
43572              break;
43573
43574            case 0x08:
43575              event.nalUnitType = 'pic_parameter_set_rbsp';
43576              break;
43577
43578            case 0x09:
43579              event.nalUnitType = 'access_unit_delimiter_rbsp';
43580              break;
43581
43582            default:
43583              break;
43584          } // This triggers data on the H264Stream
43585
43586
43587          self.trigger('data', event);
43588        });
43589        nalByteStream.on('done', function () {
43590          self.trigger('done');
43591        });
43592        nalByteStream.on('partialdone', function () {
43593          self.trigger('partialdone');
43594        });
43595        nalByteStream.on('reset', function () {
43596          self.trigger('reset');
43597        });
43598        nalByteStream.on('endedtimeline', function () {
43599          self.trigger('endedtimeline');
43600        });
43601
43602        this.flush = function () {
43603          nalByteStream.flush();
43604        };
43605
43606        this.partialFlush = function () {
43607          nalByteStream.partialFlush();
43608        };
43609
43610        this.reset = function () {
43611          nalByteStream.reset();
43612        };
43613
43614        this.endTimeline = function () {
43615          nalByteStream.endTimeline();
43616        };
43617        /**
43618         * Advance the ExpGolomb decoder past a scaling list. The scaling
43619         * list is optionally transmitted as part of a sequence parameter
43620         * set and is not relevant to transmuxing.
43621         * @param count {number} the number of entries in this scaling list
43622         * @param expGolombDecoder {object} an ExpGolomb pointed to the
43623         * start of a scaling list
43624         * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
43625         */
43626
43627
43628        skipScalingList = function skipScalingList(count, expGolombDecoder) {
43629          var lastScale = 8,
43630              nextScale = 8,
43631              j,
43632              deltaScale;
43633
43634          for (j = 0; j < count; j++) {
43635            if (nextScale !== 0) {
43636              deltaScale = expGolombDecoder.readExpGolomb();
43637              nextScale = (lastScale + deltaScale + 256) % 256;
43638            }
43639
43640            lastScale = nextScale === 0 ? lastScale : nextScale;
43641          }
43642        };
43643        /**
43644         * Expunge any "Emulation Prevention" bytes from a "Raw Byte
43645         * Sequence Payload"
43646         * @param data {Uint8Array} the bytes of a RBSP from a NAL
43647         * unit
43648         * @return {Uint8Array} the RBSP without any Emulation
43649         * Prevention Bytes
43650         */
43651
43652
43653        discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
43654          var length = data.byteLength,
43655              emulationPreventionBytesPositions = [],
43656              i = 1,
43657              newLength,
43658              newData; // Find all `Emulation Prevention Bytes`
43659
43660          while (i < length - 2) {
43661            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
43662              emulationPreventionBytesPositions.push(i + 2);
43663              i += 2;
43664            } else {
43665              i++;
43666            }
43667          } // If no Emulation Prevention Bytes were found just return the original
43668          // array
43669
43670
43671          if (emulationPreventionBytesPositions.length === 0) {
43672            return data;
43673          } // Create a new array to hold the NAL unit data
43674
43675
43676          newLength = length - emulationPreventionBytesPositions.length;
43677          newData = new Uint8Array(newLength);
43678          var sourceIndex = 0;
43679
43680          for (i = 0; i < newLength; sourceIndex++, i++) {
43681            if (sourceIndex === emulationPreventionBytesPositions[0]) {
43682              // Skip this byte
43683              sourceIndex++; // Remove this position index
43684
43685              emulationPreventionBytesPositions.shift();
43686            }
43687
43688            newData[i] = data[sourceIndex];
43689          }
43690
43691          return newData;
43692        };
43693        /**
43694         * Read a sequence parameter set and return some interesting video
43695         * properties. A sequence parameter set is the H264 metadata that
43696         * describes the properties of upcoming video frames.
43697         * @param data {Uint8Array} the bytes of a sequence parameter set
43698         * @return {object} an object with configuration parsed from the
43699         * sequence parameter set, including the dimensions of the
43700         * associated video frames.
43701         */
43702
43703
43704        readSequenceParameterSet = function readSequenceParameterSet(data) {
43705          var frameCropLeftOffset = 0,
43706              frameCropRightOffset = 0,
43707              frameCropTopOffset = 0,
43708              frameCropBottomOffset = 0,
43709              sarScale = 1,
43710              expGolombDecoder,
43711              profileIdc,
43712              levelIdc,
43713              profileCompatibility,
43714              chromaFormatIdc,
43715              picOrderCntType,
43716              numRefFramesInPicOrderCntCycle,
43717              picWidthInMbsMinus1,
43718              picHeightInMapUnitsMinus1,
43719              frameMbsOnlyFlag,
43720              scalingListCount,
43721              sarRatio,
43722              aspectRatioIdc,
43723              i;
43724          expGolombDecoder = new expGolomb(data);
43725          profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
43726
43727          profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
43728
43729          levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
43730
43731          expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
43732          // some profiles have more optional data we don't need
43733
43734          if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
43735            chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
43736
43737            if (chromaFormatIdc === 3) {
43738              expGolombDecoder.skipBits(1); // separate_colour_plane_flag
43739            }
43740
43741            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
43742
43743            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
43744
43745            expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
43746
43747            if (expGolombDecoder.readBoolean()) {
43748              // seq_scaling_matrix_present_flag
43749              scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
43750
43751              for (i = 0; i < scalingListCount; i++) {
43752                if (expGolombDecoder.readBoolean()) {
43753                  // seq_scaling_list_present_flag[ i ]
43754                  if (i < 6) {
43755                    skipScalingList(16, expGolombDecoder);
43756                  } else {
43757                    skipScalingList(64, expGolombDecoder);
43758                  }
43759                }
43760              }
43761            }
43762          }
43763
43764          expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
43765
43766          picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
43767
43768          if (picOrderCntType === 0) {
43769            expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
43770          } else if (picOrderCntType === 1) {
43771            expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
43772
43773            expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
43774
43775            expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
43776
43777            numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
43778
43779            for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
43780              expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
43781            }
43782          }
43783
43784          expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
43785
43786          expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
43787
43788          picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
43789          picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
43790          frameMbsOnlyFlag = expGolombDecoder.readBits(1);
43791
43792          if (frameMbsOnlyFlag === 0) {
43793            expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
43794          }
43795
43796          expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
43797
43798          if (expGolombDecoder.readBoolean()) {
43799            // frame_cropping_flag
43800            frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
43801            frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
43802            frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
43803            frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
43804          }
43805
43806          if (expGolombDecoder.readBoolean()) {
43807            // vui_parameters_present_flag
43808            if (expGolombDecoder.readBoolean()) {
43809              // aspect_ratio_info_present_flag
43810              aspectRatioIdc = expGolombDecoder.readUnsignedByte();
43811
43812              switch (aspectRatioIdc) {
43813                case 1:
43814                  sarRatio = [1, 1];
43815                  break;
43816
43817                case 2:
43818                  sarRatio = [12, 11];
43819                  break;
43820
43821                case 3:
43822                  sarRatio = [10, 11];
43823                  break;
43824
43825                case 4:
43826                  sarRatio = [16, 11];
43827                  break;
43828
43829                case 5:
43830                  sarRatio = [40, 33];
43831                  break;
43832
43833                case 6:
43834                  sarRatio = [24, 11];
43835                  break;
43836
43837                case 7:
43838                  sarRatio = [20, 11];
43839                  break;
43840
43841                case 8:
43842                  sarRatio = [32, 11];
43843                  break;
43844
43845                case 9:
43846                  sarRatio = [80, 33];
43847                  break;
43848
43849                case 10:
43850                  sarRatio = [18, 11];
43851                  break;
43852
43853                case 11:
43854                  sarRatio = [15, 11];
43855                  break;
43856
43857                case 12:
43858                  sarRatio = [64, 33];
43859                  break;
43860
43861                case 13:
43862                  sarRatio = [160, 99];
43863                  break;
43864
43865                case 14:
43866                  sarRatio = [4, 3];
43867                  break;
43868
43869                case 15:
43870                  sarRatio = [3, 2];
43871                  break;
43872
43873                case 16:
43874                  sarRatio = [2, 1];
43875                  break;
43876
43877                case 255:
43878                  {
43879                    sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
43880                    break;
43881                  }
43882              }
43883
43884              if (sarRatio) {
43885                sarScale = sarRatio[0] / sarRatio[1];
43886              }
43887            }
43888          }
43889
43890          return {
43891            profileIdc: profileIdc,
43892            levelIdc: levelIdc,
43893            profileCompatibility: profileCompatibility,
43894            width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
43895            height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
43896            sarRatio: sarRatio
43897          };
43898        };
43899      };
43900
43901      _H264Stream.prototype = new stream();
43902      var h264 = {
43903        H264Stream: _H264Stream,
43904        NalByteStream: _NalByteStream
43905      };
43906      /**
43907       * mux.js
43908       *
43909       * Copyright (c) Brightcove
43910       * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43911       *
43912       * Utilities to detect basic properties and metadata about Aac data.
43913       */
43914
43915      var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
43916
43917      var isLikelyAacData = function isLikelyAacData(data) {
43918        if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
43919          return true;
43920        }
43921
43922        return false;
43923      };
43924
43925      var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
43926        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
43927      }; // return a percent-encoded representation of the specified byte range
43928      // @see http://en.wikipedia.org/wiki/Percent-encoding
43929
43930
43931      var percentEncode$1 = function percentEncode(bytes, start, end) {
43932        var i,
43933            result = '';
43934
43935        for (i = start; i < end; i++) {
43936          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
43937        }
43938
43939        return result;
43940      }; // return the string representation of the specified byte range,
43941      // interpreted as ISO-8859-1.
43942
43943
43944      var parseIso88591$1 = function parseIso88591(bytes, start, end) {
43945        return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
43946      };
43947
43948      var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
43949        var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
43950            flags = header[byteIndex + 5],
43951            footerPresent = (flags & 16) >> 4;
43952
43953        if (footerPresent) {
43954          return returnSize + 20;
43955        }
43956
43957        return returnSize + 10;
43958      };
43959
43960      var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
43961        var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
43962            middle = header[byteIndex + 4] << 3,
43963            highTwo = header[byteIndex + 3] & 0x3 << 11;
43964        return highTwo | middle | lowThree;
43965      };
43966
43967      var parseType = function parseType(header, byteIndex) {
43968        if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
43969          return 'timed-metadata';
43970        } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
43971          return 'audio';
43972        }
43973
43974        return null;
43975      };
43976
43977      var parseSampleRate = function parseSampleRate(packet) {
43978        var i = 0;
43979
43980        while (i + 5 < packet.length) {
43981          if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
43982            // If a valid header was not found,  jump one forward and attempt to
43983            // find a valid ADTS header starting at the next byte
43984            i++;
43985            continue;
43986          }
43987
43988          return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
43989        }
43990
43991        return null;
43992      };
43993
43994      var parseAacTimestamp = function parseAacTimestamp(packet) {
43995        var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
43996
43997        frameStart = 10;
43998
43999        if (packet[5] & 0x40) {
44000          // advance the frame start past the extended header
44001          frameStart += 4; // header size field
44002
44003          frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
44004        } // parse one or more ID3 frames
44005        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
44006
44007
44008        do {
44009          // determine the number of bytes in this frame
44010          frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
44011
44012          if (frameSize < 1) {
44013            return null;
44014          }
44015
44016          frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
44017
44018          if (frameHeader === 'PRIV') {
44019            frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
44020
44021            for (var i = 0; i < frame.byteLength; i++) {
44022              if (frame[i] === 0) {
44023                var owner = parseIso88591$1(frame, 
vendor: 5,833 bytes, lines 44023-44195
440230, i);
44024
44025                if (owner === 'com.apple.streaming.transportStreamTimestamp') {
44026                  var d = frame.subarray(i + 1);
44027                  var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
44028                  size *= 4;
44029                  size += d[7] & 0x03;
44030                  return size;
44031                }
44032
44033                break;
44034              }
44035            }
44036          }
44037
44038          frameStart += 10; // advance past the frame header
44039
44040          frameStart += frameSize; // advance past the frame body
44041        } while (frameStart < packet.byteLength);
44042
44043        return null;
44044      };
44045
44046      var utils = {
44047        isLikelyAacData: isLikelyAacData,
44048        parseId3TagSize: parseId3TagSize,
44049        parseAdtsSize: parseAdtsSize,
44050        parseType: parseType,
44051        parseSampleRate: parseSampleRate,
44052        parseAacTimestamp: parseAacTimestamp
44053      }; // Constants
44054
44055      var _AacStream;
44056      /**
44057       * Splits an incoming stream of binary data into ADTS and ID3 Frames.
44058       */
44059
44060
44061      _AacStream = function AacStream() {
44062        var everything = new Uint8Array(),
44063            timeStamp = 0;
44064
44065        _AacStream.prototype.init.call(this);
44066
44067        this.setTimestamp = function (timestamp) {
44068          timeStamp = timestamp;
44069        };
44070
44071        this.push = function (bytes) {
44072          var frameSize = 0,
44073              byteIndex = 0,
44074              bytesLeft,
44075              chunk,
44076              packet,
44077              tempLength; // If there are bytes remaining from the last segment, prepend them to the
44078          // bytes that were pushed in
44079
44080          if (everything.length) {
44081            tempLength = everything.length;
44082            everything = new Uint8Array(bytes.byteLength + tempLength);
44083            everything.set(everything.subarray(0, tempLength));
44084            everything.set(bytes, tempLength);
44085          } else {
44086            everything = bytes;
44087          }
44088
44089          while (everything.length - byteIndex >= 3) {
44090            if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
44091              // Exit early because we don't have enough to parse
44092              // the ID3 tag header
44093              if (everything.length - byteIndex < 10) {
44094                break;
44095              } // check framesize
44096
44097
44098              frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
44099              // to emit a full packet
44100              // Add to byteIndex to support multiple ID3 tags in sequence
44101
44102              if (byteIndex + frameSize > everything.length) {
44103                break;
44104              }
44105
44106              chunk = {
44107                type: 'timed-metadata',
44108                data: everything.subarray(byteIndex, byteIndex + frameSize)
44109              };
44110              this.trigger('data', chunk);
44111              byteIndex += frameSize;
44112              continue;
44113            } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
44114              // Exit early because we don't have enough to parse
44115              // the ADTS frame header
44116              if (everything.length - byteIndex < 7) {
44117                break;
44118              }
44119
44120              frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
44121              // to emit a full packet
44122
44123              if (byteIndex + frameSize > everything.length) {
44124                break;
44125              }
44126
44127              packet = {
44128                type: 'audio',
44129                data: everything.subarray(byteIndex, byteIndex + frameSize),
44130                pts: timeStamp,
44131                dts: timeStamp
44132              };
44133              this.trigger('data', packet);
44134              byteIndex += frameSize;
44135              continue;
44136            }
44137
44138            byteIndex++;
44139          }
44140
44141          bytesLeft = everything.length - byteIndex;
44142
44143          if (bytesLeft > 0) {
44144            everything = everything.subarray(byteIndex);
44145          } else {
44146            everything = new Uint8Array();
44147          }
44148        };
44149
44150        this.reset = function () {
44151          everything = new Uint8Array();
44152          this.trigger('reset');
44153        };
44154
44155        this.endTimeline = function () {
44156          everything = new Uint8Array();
44157          this.trigger('endedtimeline');
44158        };
44159      };
44160
44161      _AacStream.prototype = new stream();
44162      var aac = _AacStream;
44163      var H264Stream = h264.H264Stream;
44164      var isLikelyAacData$1 = utils.isLikelyAacData;
44165      var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // constants
44166
44167      var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
44168      var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; // object types
44169
44170      var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
44171      /**
44172       * Compare two arrays (even typed) for same-ness
44173       */
44174
44175
44176      var arrayEquals = function arrayEquals(a, b) {
44177        var i;
44178
44179        if (a.length !== b.length) {
44180          return false;
44181        } // compare the value of each element in the array
44182
44183
44184        for (i = 0; i < a.length; i++) {
44185          if (a[i] !== b[i]) {
44186            return false;
44187          }
44188        }
44189
44190        return true;
44191      };
44192
44193      var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
44194        var ptsOffsetFromDts = startPts - startDts,
44195            decodeDuration = endDts - startDts,
vendor: 6,058 bytes, lines 44196-44338
44196            presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
44197        // however, the player time values will reflect a start from the baseMediaDecodeTime.
44198        // In order to provide relevant values for the player times, base timing info on the
44199        // baseMediaDecodeTime and the DTS and PTS durations of the segment.
44200
44201        return {
44202          start: {
44203            dts: baseMediaDecodeTime,
44204            pts: baseMediaDecodeTime + ptsOffsetFromDts
44205          },
44206          end: {
44207            dts: baseMediaDecodeTime + decodeDuration,
44208            pts: baseMediaDecodeTime + presentationDuration
44209          },
44210          prependedContentDuration: prependedContentDuration,
44211          baseMediaDecodeTime: baseMediaDecodeTime
44212        };
44213      };
44214      /**
44215       * Constructs a single-track, ISO BMFF media segment from AAC data
44216       * events. The output of this stream can be fed to a SourceBuffer
44217       * configured with a suitable initialization segment.
44218       * @param track {object} track metadata configuration
44219       * @param options {object} transmuxer options object
44220       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
44221       *        in the source; false to adjust the first segment to start at 0.
44222       */
44223
44224
44225      _AudioSegmentStream = function AudioSegmentStream(track, options) {
44226        var adtsFrames = [],
44227            sequenceNumber = 0,
44228            earliestAllowedDts = 0,
44229            audioAppendStartTs = 0,
44230            videoBaseMediaDecodeTime = Infinity;
44231        options = options || {};
44232
44233        _AudioSegmentStream.prototype.init.call(this);
44234
44235        this.push = function (data) {
44236          trackDecodeInfo.collectDtsInfo(track, data);
44237
44238          if (track) {
44239            AUDIO_PROPERTIES.forEach(function (prop) {
44240              track[prop] = data[prop];
44241            });
44242          } // buffer audio data until end() is called
44243
44244
44245          adtsFrames.push(data);
44246        };
44247
44248        this.setEarliestDts = function (earliestDts) {
44249          earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
44250        };
44251
44252        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
44253          videoBaseMediaDecodeTime = baseMediaDecodeTime;
44254        };
44255
44256        this.setAudioAppendStart = function (timestamp) {
44257          audioAppendStartTs = timestamp;
44258        };
44259
44260        this.flush = function () {
44261          var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed
44262
44263          if (adtsFrames.length === 0) {
44264            this.trigger('done', 'AudioSegmentStream');
44265            return;
44266          }
44267
44268          frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
44269          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
44270          audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
44271          // samples (that is, adts frames) in the audio data
44272
44273          track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
44274
44275          mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
44276          adtsFrames = [];
44277          moof = mp4Generator.moof(sequenceNumber, [track]);
44278          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
44279
44280          sequenceNumber++;
44281          boxes.set(moof);
44282          boxes.set(mdat, moof.byteLength);
44283          trackDecodeInfo.clearDtsInfo(track);
44284          frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
44285          // tests) on adding the timingInfo event. However, it seems unlikely that there's a
44286          // valid use-case where an init segment/data should be triggered without associated
44287          // frames. Leaving for now, but should be looked into.
44288
44289          if (frames.length) {
44290            this.trigger('timingInfo', {
44291              start: frames[0].dts,
44292              end: frames[0].dts + frames.length * frameDuration
44293            });
44294          }
44295
44296          this.trigger('data', {
44297            track: track,
44298            boxes: boxes
44299          });
44300          this.trigger('done', 'AudioSegmentStream');
44301        };
44302
44303        this.reset = function () {
44304          trackDecodeInfo.clearDtsInfo(track);
44305          adtsFrames = [];
44306          this.trigger('reset');
44307        };
44308      };
44309
44310      _AudioSegmentStream.prototype = new stream();
44311      /**
44312       * Constructs a single-track, ISO BMFF media segment from H264 data
44313       * events. The output of this stream can be fed to a SourceBuffer
44314       * configured with a suitable initialization segment.
44315       * @param track {object} track metadata configuration
44316       * @param options {object} transmuxer options object
44317       * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
44318       *        gopsToAlignWith list when attempting to align gop pts
44319       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
44320       *        in the source; false to adjust the first segment to start at 0.
44321       */
44322
44323      _VideoSegmentStream = function VideoSegmentStream(track, options) {
44324        var sequenceNumber = 0,
44325            nalUnits = [],
44326            gopsToAlignWith = [],
44327            config,
44328            pps;
44329        options = options || {};
44330
44331        _VideoSegmentStream.prototype.init.call(this);
44332
44333        delete track.minPTS;
44334        this.gopCache_ = [];
44335        /**
44336          * Constructs a ISO BMFF segment given H264 nalUnits
44337          * @param {Object} nalUnit A data event representing a nalUnit
44338          * @param {String} nalUnit.nalUnitType
vendor: 12,823 bytes, lines 44339-44662
44339          * @param {Object} nalUnit.config Properties for a mp4 track
44340          * @param {Uint8Array} nalUnit.data The nalUnit bytes
44341          * @see lib/codecs/h264.js
44342         **/
44343
44344        this.push = function (nalUnit) {
44345          trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
44346
44347          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
44348            config = nalUnit.config;
44349            track.sps = [nalUnit.data];
44350            VIDEO_PROPERTIES.forEach(function (prop) {
44351              track[prop] = config[prop];
44352            }, this);
44353          }
44354
44355          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
44356            pps = nalUnit.data;
44357            track.pps = [nalUnit.data];
44358          } // buffer video until flush() is called
44359
44360
44361          nalUnits.push(nalUnit);
44362        };
44363        /**
44364          * Pass constructed ISO BMFF track and boxes on to the
44365          * next stream in the pipeline
44366         **/
44367
44368
44369        this.flush = function () {
44370          var frames,
44371              gopForFusion,
44372              gops,
44373              moof,
44374              mdat,
44375              boxes,
44376              prependedContentDuration = 0,
44377              firstGop,
44378              lastGop; // Throw away nalUnits at the start of the byte stream until
44379          // we find the first AUD
44380
44381          while (nalUnits.length) {
44382            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
44383              break;
44384            }
44385
44386            nalUnits.shift();
44387          } // Return early if no video data has been observed
44388
44389
44390          if (nalUnits.length === 0) {
44391            this.resetStream_();
44392            this.trigger('done', 'VideoSegmentStream');
44393            return;
44394          } // Organize the raw nal-units into arrays that represent
44395          // higher-level constructs such as frames and gops
44396          // (group-of-pictures)
44397
44398
44399          frames = frameUtils.groupNalsIntoFrames(nalUnits);
44400          gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
44401          // a problem since MSE (on Chrome) requires a leading keyframe.
44402          //
44403          // We have two approaches to repairing this situation:
44404          // 1) GOP-FUSION:
44405          //    This is where we keep track of the GOPS (group-of-pictures)
44406          //    from previous fragments and attempt to find one that we can
44407          //    prepend to the current fragment in order to create a valid
44408          //    fragment.
44409          // 2) KEYFRAME-PULLING:
44410          //    Here we search for the first keyframe in the fragment and
44411          //    throw away all the frames between the start of the fragment
44412          //    and that keyframe. We then extend the duration and pull the
44413          //    PTS of the keyframe forward so that it covers the time range
44414          //    of the frames that were disposed of.
44415          //
44416          // #1 is far prefereable over #2 which can cause "stuttering" but
44417          // requires more things to be just right.
44418
44419          if (!gops[0][0].keyFrame) {
44420            // Search for a gop for fusion from our gopCache
44421            gopForFusion = this.getGopForFusion_(nalUnits[0], track);
44422
44423            if (gopForFusion) {
44424              // in order to provide more accurate timing information about the segment, save
44425              // the number of seconds prepended to the original segment due to GOP fusion
44426              prependedContentDuration = gopForFusion.duration;
44427              gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
44428              // new gop at the beginning
44429
44430              gops.byteLength += gopForFusion.byteLength;
44431              gops.nalCount += gopForFusion.nalCount;
44432              gops.pts = gopForFusion.pts;
44433              gops.dts = gopForFusion.dts;
44434              gops.duration += gopForFusion.duration;
44435            } else {
44436              // If we didn't find a candidate gop fall back to keyframe-pulling
44437              gops = frameUtils.extendFirstKeyFrame(gops);
44438            }
44439          } // Trim gops to align with gopsToAlignWith
44440
44441
44442          if (gopsToAlignWith.length) {
44443            var alignedGops;
44444
44445            if (options.alignGopsAtEnd) {
44446              alignedGops = this.alignGopsAtEnd_(gops);
44447            } else {
44448              alignedGops = this.alignGopsAtStart_(gops);
44449            }
44450
44451            if (!alignedGops) {
44452              // save all the nals in the last GOP into the gop cache
44453              this.gopCache_.unshift({
44454                gop: gops.pop(),
44455                pps: track.pps,
44456                sps: track.sps
44457              }); // Keep a maximum of 6 GOPs in the cache
44458
44459              this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
44460
44461              nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
44462
44463              this.resetStream_();
44464              this.trigger('done', 'VideoSegmentStream');
44465              return;
44466            } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
44467            // when recalculated before sending off to CoalesceStream
44468
44469
44470            trackDecodeInfo.clearDtsInfo(track);
44471            gops = alignedGops;
44472          }
44473
44474          trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
44475          // samples (that is, frames) in the video data
44476
44477          track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
44478
44479          mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
44480          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
44481          this.trigger('processedGopsInfo', gops.map(function (gop) {
44482            return {
44483              pts: gop.pts,
44484              dts: gop.dts,
44485              byteLength: gop.byteLength
44486            };
44487          }));
44488          firstGop = gops[0];
44489          lastGop = gops[gops.length - 1];
44490          this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
44491          this.trigger('timingInfo', {
44492            start: gops[0].dts,
44493            end: gops[gops.length - 1].dts + gops[gops.length - 1].duration
44494          }); // save all the nals in the last GOP into the gop cache
44495
44496          this.gopCache_.unshift({
44497            gop: gops.pop(),
44498            pps: track.pps,
44499            sps: track.sps
44500          }); // Keep a maximum of 6 GOPs in the cache
44501
44502          this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
44503
44504          nalUnits = [];
44505          this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
44506          this.trigger('timelineStartInfo', track.timelineStartInfo);
44507          moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
44508          // throwing away hundreds of media segment fragments
44509
44510          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
44511
44512          sequenceNumber++;
44513          boxes.set(moof);
44514          boxes.set(mdat, moof.byteLength);
44515          this.trigger('data', {
44516            track: track,
44517            boxes: boxes
44518          });
44519          this.resetStream_(); // Continue with the flush process now
44520
44521          this.trigger('done', 'VideoSegmentStream');
44522        };
44523
44524        this.reset = function () {
44525          this.resetStream_();
44526          nalUnits = [];
44527          this.gopCache_.length = 0;
44528          gopsToAlignWith.length = 0;
44529          this.trigger('reset');
44530        };
44531
44532        this.resetStream_ = function () {
44533          trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
44534          // for instance, when we are rendition switching
44535
44536          config = undefined;
44537          pps = undefined;
44538        }; // Search for a candidate Gop for gop-fusion from the gop cache and
44539        // return it or return null if no good candidate was found
44540
44541
44542        this.getGopForFusion_ = function (nalUnit) {
44543          var halfSecond = 45000,
44544              // Half-a-second in a 90khz clock
44545          allowableOverlap = 10000,
44546              // About 3 frames @ 30fps
44547          nearestDistance = Infinity,
44548              dtsDistance,
44549              nearestGopObj,
44550              currentGop,
44551              currentGopObj,
44552              i; // Search for the GOP nearest to the beginning of this nal unit
44553
44554          for (i = 0; i < this.gopCache_.length; i++) {
44555            currentGopObj = this.gopCache_[i];
44556            currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
44557
44558            if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
44559              continue;
44560            } // Reject Gops that would require a negative baseMediaDecodeTime
44561
44562
44563            if (currentGop.dts < track.timelineStartInfo.dts) {
44564              continue;
44565            } // The distance between the end of the gop and the start of the nalUnit
44566
44567
44568            dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
44569            // a half-second of the nal unit
44570
44571            if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
44572              // Always use the closest GOP we found if there is more than
44573              // one candidate
44574              if (!nearestGopObj || nearestDistance > dtsDistance) {
44575                nearestGopObj = currentGopObj;
44576                nearestDistance = dtsDistance;
44577              }
44578            }
44579          }
44580
44581          if (nearestGopObj) {
44582            return nearestGopObj.gop;
44583          }
44584
44585          return null;
44586        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
44587        // of gopsToAlignWith starting from the START of the list
44588
44589
44590        this.alignGopsAtStart_ = function (gops) {
44591          var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
44592          byteLength = gops.byteLength;
44593          nalCount = gops.nalCount;
44594          duration = gops.duration;
44595          alignIndex = gopIndex = 0;
44596
44597          while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
44598            align = gopsToAlignWith[alignIndex];
44599            gop = gops[gopIndex];
44600
44601            if (align.pts === gop.pts) {
44602              break;
44603            }
44604
44605            if (gop.pts > align.pts) {
44606              // this current gop starts after the current gop we want to align on, so increment
44607              // align index
44608              alignIndex++;
44609              continue;
44610            } // current gop starts before the current gop we want to align on. so increment gop
44611            // index
44612
44613
44614            gopIndex++;
44615            byteLength -= gop.byteLength;
44616            nalCount -= gop.nalCount;
44617            duration -= gop.duration;
44618          }
44619
44620          if (gopIndex === 0) {
44621            // no gops to trim
44622            return gops;
44623          }
44624
44625          if (gopIndex === gops.length) {
44626            // all gops trimmed, skip appending all gops
44627            return null;
44628          }
44629
44630          alignedGops = gops.slice(gopIndex);
44631          alignedGops.byteLength = byteLength;
44632          alignedGops.duration = duration;
44633          alignedGops.nalCount = nalCount;
44634          alignedGops.pts = alignedGops[0].pts;
44635          alignedGops.dts = alignedGops[0].dts;
44636          return alignedGops;
44637        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
44638        // of gopsToAlignWith starting from the END of the list
44639
44640
44641        this.alignGopsAtEnd_ = function (gops) {
44642          var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
44643          alignIndex = gopsToAlignWith.length - 1;
44644          gopIndex = gops.length - 1;
44645          alignEndIndex = null;
44646          matchFound = false;
44647
44648          while (alignIndex >= 0 && gopIndex >= 0) {
44649            align = gopsToAlignWith[alignIndex];
44650            gop = gops[gopIndex];
44651
44652            if (align.pts === gop.pts) {
44653              matchFound = true;
44654              break;
44655            }
44656
44657            if (align.pts > gop.pts) {
44658              alignIndex--;
44659              continue;
44660            }
44661
44662            if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 4,564 bytes, lines 44663-44793
44663              // gop.pts is greater than the last alignment candidate. If no match is found
44664              // by the end of this loop, we still want to append gops that come after this
44665              // point
44666              alignEndIndex = gopIndex;
44667            }
44668
44669            gopIndex--;
44670          }
44671
44672          if (!matchFound && alignEndIndex === null) {
44673            return null;
44674          }
44675
44676          var trimIndex;
44677
44678          if (matchFound) {
44679            trimIndex = gopIndex;
44680          } else {
44681            trimIndex = alignEndIndex;
44682          }
44683
44684          if (trimIndex === 0) {
44685            return gops;
44686          }
44687
44688          var alignedGops = gops.slice(trimIndex);
44689          var metadata = alignedGops.reduce(function (total, gop) {
44690            total.byteLength += gop.byteLength;
44691            total.duration += gop.duration;
44692            total.nalCount += gop.nalCount;
44693            return total;
44694          }, {
44695            byteLength: 0,
44696            duration: 0,
44697            nalCount: 0
44698          });
44699          alignedGops.byteLength = metadata.byteLength;
44700          alignedGops.duration = metadata.duration;
44701          alignedGops.nalCount = metadata.nalCount;
44702          alignedGops.pts = alignedGops[0].pts;
44703          alignedGops.dts = alignedGops[0].dts;
44704          return alignedGops;
44705        };
44706
44707        this.alignGopsWith = function (newGopsToAlignWith) {
44708          gopsToAlignWith = newGopsToAlignWith;
44709        };
44710      };
44711
44712      _VideoSegmentStream.prototype = new stream();
44713      /**
44714       * A Stream that can combine multiple streams (ie. audio & video)
44715       * into a single output segment for MSE. Also supports audio-only
44716       * and video-only streams.
44717       * @param options {object} transmuxer options object
44718       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
44719       *        in the source; false to adjust the first segment to start at media timeline start.
44720       */
44721
44722      _CoalesceStream = function CoalesceStream(options, metadataStream) {
44723        // Number of Tracks per output segment
44724        // If greater than 1, we combine multiple
44725        // tracks into a single segment
44726        this.numberOfTracks = 0;
44727        this.metadataStream = metadataStream;
44728        options = options || {};
44729
44730        if (typeof options.remux !== 'undefined') {
44731          this.remuxTracks = !!options.remux;
44732        } else {
44733          this.remuxTracks = true;
44734        }
44735
44736        if (typeof options.keepOriginalTimestamps === 'boolean') {
44737          this.keepOriginalTimestamps = options.keepOriginalTimestamps;
44738        } else {
44739          this.keepOriginalTimestamps = false;
44740        }
44741
44742        this.pendingTracks = [];
44743        this.videoTrack = null;
44744        this.pendingBoxes = [];
44745        this.pendingCaptions = [];
44746        this.pendingMetadata = [];
44747        this.pendingBytes = 0;
44748        this.emittedTracks = 0;
44749
44750        _CoalesceStream.prototype.init.call(this); // Take output from multiple
44751
44752
44753        this.push = function (output) {
44754          // buffer incoming captions until the associated video segment
44755          // finishes
44756          if (output.text) {
44757            return this.pendingCaptions.push(output);
44758          } // buffer incoming id3 tags until the final flush
44759
44760
44761          if (output.frames) {
44762            return this.pendingMetadata.push(output);
44763          } // Add this track to the list of pending tracks and store
44764          // important information required for the construction of
44765          // the final segment
44766
44767
44768          this.pendingTracks.push(output.track);
44769          this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
44770          // We unshift audio and push video because
44771          // as of Chrome 75 when switching from
44772          // one init segment to another if the video
44773          // mdat does not appear after the audio mdat
44774          // only audio will play for the duration of our transmux.
44775
44776          if (output.track.type === 'video') {
44777            this.videoTrack = output.track;
44778            this.pendingBoxes.push(output.boxes);
44779          }
44780
44781          if (output.track.type === 'audio') {
44782            this.audioTrack = output.track;
44783            this.pendingBoxes.unshift(output.boxes);
44784          }
44785        };
44786      };
44787
44788      _CoalesceStream.prototype = new stream();
44789
44790      _CoalesceStream.prototype.flush = function (flushSource) {
44791        var offset = 0,
44792            event = {
44793          captions: [],
vendor: 4,830 bytes, lines 44794-44899
44794          captionStreams: {},
44795          metadata: [],
44796          info: {}
44797        },
44798            caption,
44799            id3,
44800            initSegment,
44801            timelineStartPts = 0,
44802            i;
44803
44804        if (this.pendingTracks.length < this.numberOfTracks) {
44805          if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
44806            // Return because we haven't received a flush from a data-generating
44807            // portion of the segment (meaning that we have only recieved meta-data
44808            // or captions.)
44809            return;
44810          } else if (this.remuxTracks) {
44811            // Return until we have enough tracks from the pipeline to remux (if we
44812            // are remuxing audio and video into a single MP4)
44813            return;
44814          } else if (this.pendingTracks.length === 0) {
44815            // In the case where we receive a flush without any data having been
44816            // received we consider it an emitted track for the purposes of coalescing
44817            // `done` events.
44818            // We do this for the case where there is an audio and video track in the
44819            // segment but no audio data. (seen in several playlists with alternate
44820            // audio tracks and no audio present in the main TS segments.)
44821            this.emittedTracks++;
44822
44823            if (this.emittedTracks >= this.numberOfTracks) {
44824              this.trigger('done');
44825              this.emittedTracks = 0;
44826            }
44827
44828            return;
44829          }
44830        }
44831
44832        if (this.videoTrack) {
44833          timelineStartPts = this.videoTrack.timelineStartInfo.pts;
44834          VIDEO_PROPERTIES.forEach(function (prop) {
44835            event.info[prop] = this.videoTrack[prop];
44836          }, this);
44837        } else if (this.audioTrack) {
44838          timelineStartPts = this.audioTrack.timelineStartInfo.pts;
44839          AUDIO_PROPERTIES.forEach(function (prop) {
44840            event.info[prop] = this.audioTrack[prop];
44841          }, this);
44842        }
44843
44844        if (this.videoTrack || this.audioTrack) {
44845          if (this.pendingTracks.length === 1) {
44846            event.type = this.pendingTracks[0].type;
44847          } else {
44848            event.type = 'combined';
44849          }
44850
44851          this.emittedTracks += this.pendingTracks.length;
44852          initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
44853
44854          event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
44855          // and track definitions
44856
44857          event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
44858
44859          event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
44860
44861          for (i = 0; i < this.pendingBoxes.length; i++) {
44862            event.data.set(this.pendingBoxes[i], offset);
44863            offset += this.pendingBoxes[i].byteLength;
44864          } // Translate caption PTS times into second offsets to match the
44865          // video timeline for the segment, and add track info
44866
44867
44868          for (i = 0; i < this.pendingCaptions.length; i++) {
44869            caption = this.pendingCaptions[i];
44870            caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
44871            caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
44872            event.captionStreams[caption.stream] = true;
44873            event.captions.push(caption);
44874          } // Translate ID3 frame PTS times into second offsets to match the
44875          // video timeline for the segment
44876
44877
44878          for (i = 0; i < this.pendingMetadata.length; i++) {
44879            id3 = this.pendingMetadata[i];
44880            id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
44881            event.metadata.push(id3);
44882          } // We add this to every single emitted segment even though we only need
44883          // it for the first
44884
44885
44886          event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
44887
44888          this.pendingTracks.length = 0;
44889          this.videoTrack = null;
44890          this.pendingBoxes.length = 0;
44891          this.pendingCaptions.length = 0;
44892          this.pendingBytes = 0;
44893          this.pendingMetadata.length = 0; // Emit the built segment
44894          // We include captions and ID3 tags for backwards compatibility,
44895          // ideally we should send only video and audio in the data event
44896
44897          this.trigger('data', event); // Emit each caption to the outside world
44898          // Ideally, this would happen immediately on parsing captions,
44899          // but we need to ensure that video data is sent back first
vendor: 46,856 bytes, lines 44900-46193
44900          // so that caption timing can be adjusted to match video timing
44901
44902          for (i = 0; i < event.captions.length; i++) {
44903            caption = event.captions[i];
44904            this.trigger('caption', caption);
44905          } // Emit each id3 tag to the outside world
44906          // Ideally, this would happen immediately on parsing the tag,
44907          // but we need to ensure that video data is sent back first
44908          // so that ID3 frame timing can be adjusted to match video timing
44909
44910
44911          for (i = 0; i < event.metadata.length; i++) {
44912            id3 = event.metadata[i];
44913            this.trigger('id3Frame', id3);
44914          }
44915        } // Only emit `done` if all tracks have been flushed and emitted
44916
44917
44918        if (this.emittedTracks >= this.numberOfTracks) {
44919          this.trigger('done');
44920          this.emittedTracks = 0;
44921        }
44922      };
44923
44924      _CoalesceStream.prototype.setRemux = function (val) {
44925        this.remuxTracks = val;
44926      };
44927      /**
44928       * A Stream that expects MP2T binary data as input and produces
44929       * corresponding media segments, suitable for use with Media Source
44930       * Extension (MSE) implementations that support the ISO BMFF byte
44931       * stream format, like Chrome.
44932       */
44933
44934
44935      _Transmuxer = function Transmuxer(options) {
44936        var self = this,
44937            hasFlushed = true,
44938            videoTrack,
44939            audioTrack;
44940
44941        _Transmuxer.prototype.init.call(this);
44942
44943        options = options || {};
44944        this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
44945        this.transmuxPipeline_ = {};
44946
44947        this.setupAacPipeline = function () {
44948          var pipeline = {};
44949          this.transmuxPipeline_ = pipeline;
44950          pipeline.type = 'aac';
44951          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
44952
44953          pipeline.aacStream = new aac();
44954          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
44955          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
44956          pipeline.adtsStream = new adts();
44957          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
44958          pipeline.headOfPipeline = pipeline.aacStream;
44959          pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
44960          pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
44961          pipeline.metadataStream.on('timestamp', function (frame) {
44962            pipeline.aacStream.setTimestamp(frame.timeStamp);
44963          });
44964          pipeline.aacStream.on('data', function (data) {
44965            if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
44966              audioTrack = audioTrack || {
44967                timelineStartInfo: {
44968                  baseMediaDecodeTime: self.baseMediaDecodeTime
44969                },
44970                codec: 'adts',
44971                type: 'audio'
44972              }; // hook up the audio segment stream to the first track with aac data
44973
44974              pipeline.coalesceStream.numberOfTracks++;
44975              pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
44976              pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
44977
44978              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
44979            } // emit pmt info
44980
44981
44982            self.trigger('trackinfo', {
44983              hasAudio: !!audioTrack,
44984              hasVideo: !!videoTrack
44985            });
44986          }); // Re-emit any data coming from the coalesce stream to the outside world
44987
44988          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
44989
44990          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
44991        };
44992
44993        this.setupTsPipeline = function () {
44994          var pipeline = {};
44995          this.transmuxPipeline_ = pipeline;
44996          pipeline.type = 'ts';
44997          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
44998
44999          pipeline.packetStream = new m2ts_1.TransportPacketStream();
45000          pipeline.parseStream = new m2ts_1.TransportParseStream();
45001          pipeline.elementaryStream = new m2ts_1.ElementaryStream();
45002          pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream();
45003          pipeline.adtsStream = new adts();
45004          pipeline.h264Stream = new H264Stream();
45005          pipeline.captionStream = new m2ts_1.CaptionStream();
45006          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
45007          pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
45008
45009          pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
45010          // demux the streams
45011
45012          pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
45013          pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
45014          pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
45015
45016          pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
45017          pipeline.elementaryStream.on('data', function (data) {
45018            var i;
45019
45020            if (data.type === 'metadata') {
45021              i = data.tracks.length; // scan the tracks listed in the metadata
45022
45023              while (i--) {
45024                if (!videoTrack && data.tracks[i].type === 'video') {
45025                  videoTrack = data.tracks[i];
45026                  videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
45027                } else if (!audioTrack && data.tracks[i].type === 'audio') {
45028                  audioTrack = data.tracks[i];
45029                  audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
45030                }
45031              } // hook up the video segment stream to the first track with h264 data
45032
45033
45034              if (videoTrack && !pipeline.videoSegmentStream) {
45035                pipeline.coalesceStream.numberOfTracks++;
45036                pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
45037                pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
45038                  // When video emits timelineStartInfo data after a flush, we forward that
45039                  // info to the AudioSegmentStream, if it exists, because video timeline
45040                  // data takes precedence.
45041                  if (audioTrack) {
45042                    audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
45043                    // very earliest DTS we have seen in video because Chrome will
45044                    // interpret any video track with a baseMediaDecodeTime that is
45045                    // non-zero as a gap.
45046
45047                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
45048                  }
45049                });
45050                pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
45051                pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
45052                pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
45053                  if (audioTrack) {
45054                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
45055                  }
45056                });
45057                pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
45058
45059                pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
45060              }
45061
45062              if (audioTrack && !pipeline.audioSegmentStream) {
45063                // hook up the audio segment stream to the first track with aac data
45064                pipeline.coalesceStream.numberOfTracks++;
45065                pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
45066                pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
45067
45068                pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
45069              } // emit pmt info
45070
45071
45072              self.trigger('trackinfo', {
45073                hasAudio: !!audioTrack,
45074                hasVideo: !!videoTrack
45075              });
45076            }
45077          }); // Re-emit any data coming from the coalesce stream to the outside world
45078
45079          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
45080          pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
45081            id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
45082            self.trigger('id3Frame', id3Frame);
45083          });
45084          pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
45085
45086          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
45087        }; // hook up the segment streams once track metadata is delivered
45088
45089
45090        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
45091          var pipeline = this.transmuxPipeline_;
45092
45093          if (!options.keepOriginalTimestamps) {
45094            this.baseMediaDecodeTime = baseMediaDecodeTime;
45095          }
45096
45097          if (audioTrack) {
45098            audioTrack.timelineStartInfo.dts = undefined;
45099            audioTrack.timelineStartInfo.pts = undefined;
45100            trackDecodeInfo.clearDtsInfo(audioTrack);
45101
45102            if (!options.keepOriginalTimestamps) {
45103              audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
45104            }
45105
45106            if (pipeline.audioTimestampRolloverStream) {
45107              pipeline.audioTimestampRolloverStream.discontinuity();
45108            }
45109          }
45110
45111          if (videoTrack) {
45112            if (pipeline.videoSegmentStream) {
45113              pipeline.videoSegmentStream.gopCache_ = [];
45114            }
45115
45116            videoTrack.timelineStartInfo.dts = undefined;
45117            videoTrack.timelineStartInfo.pts = undefined;
45118            trackDecodeInfo.clearDtsInfo(videoTrack);
45119            pipeline.captionStream.reset();
45120
45121            if (!options.keepOriginalTimestamps) {
45122              videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
45123            }
45124          }
45125
45126          if (pipeline.timestampRolloverStream) {
45127            pipeline.timestampRolloverStream.discontinuity();
45128          }
45129        };
45130
45131        this.setAudioAppendStart = function (timestamp) {
45132          if (audioTrack) {
45133            this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
45134          }
45135        };
45136
45137        this.setRemux = function (val) {
45138          var pipeline = this.transmuxPipeline_;
45139          options.remux = val;
45140
45141          if (pipeline && pipeline.coalesceStream) {
45142            pipeline.coalesceStream.setRemux(val);
45143          }
45144        };
45145
45146        this.alignGopsWith = function (gopsToAlignWith) {
45147          if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
45148            this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
45149          }
45150        }; // feed incoming data to the front of the parsing pipeline
45151
45152
45153        this.push = function (data) {
45154          if (hasFlushed) {
45155            var isAac = isLikelyAacData$1(data);
45156
45157            if (isAac && this.transmuxPipeline_.type !== 'aac') {
45158              this.setupAacPipeline();
45159            } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
45160              this.setupTsPipeline();
45161            }
45162
45163            hasFlushed = false;
45164          }
45165
45166          this.transmuxPipeline_.headOfPipeline.push(data);
45167        }; // flush any buffered data
45168
45169
45170        this.flush = function () {
45171          hasFlushed = true; // Start at the top of the pipeline and flush all pending work
45172
45173          this.transmuxPipeline_.headOfPipeline.flush();
45174        };
45175
45176        this.endTimeline = function () {
45177          this.transmuxPipeline_.headOfPipeline.endTimeline();
45178        };
45179
45180        this.reset = function () {
45181          if (this.transmuxPipeline_.headOfPipeline) {
45182            this.transmuxPipeline_.headOfPipeline.reset();
45183          }
45184        }; // Caption data has to be reset when seeking outside buffered range
45185
45186
45187        this.resetCaptions = function () {
45188          if (this.transmuxPipeline_.captionStream) {
45189            this.transmuxPipeline_.captionStream.reset();
45190          }
45191        };
45192      };
45193
45194      _Transmuxer.prototype = new stream();
45195      var transmuxer = {
45196        Transmuxer: _Transmuxer,
45197        VideoSegmentStream: _VideoSegmentStream,
45198        AudioSegmentStream: _AudioSegmentStream,
45199        AUDIO_PROPERTIES: AUDIO_PROPERTIES,
45200        VIDEO_PROPERTIES: VIDEO_PROPERTIES,
45201        // exported for testing
45202        generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
45203      };
45204
45205      var classCallCheck = function classCallCheck(instance, Constructor) {
45206        if (!(instance instanceof Constructor)) {
45207          throw new TypeError("Cannot call a class as a function");
45208        }
45209      };
45210
45211      var createClass = function () {
45212        function defineProperties(target, props) {
45213          for (var i = 0; i < props.length; i++) {
45214            var descriptor = props[i];
45215            descriptor.enumerable = descriptor.enumerable || false;
45216            descriptor.configurable = true;
45217            if ("value" in descriptor) descriptor.writable = true;
45218            Object.defineProperty(target, descriptor.key, descriptor);
45219          }
45220        }
45221
45222        return function (Constructor, protoProps, staticProps) {
45223          if (protoProps) defineProperties(Constructor.prototype, protoProps);
45224          if (staticProps) defineProperties(Constructor, staticProps);
45225          return Constructor;
45226        };
45227      }();
45228      /**
45229       * @file transmuxer-worker.js
45230       */
45231
45232      /**
45233       * Re-emits transmuxer events by converting them into messages to the
45234       * world outside the worker.
45235       *
45236       * @param {Object} transmuxer the transmuxer to wire events on
45237       * @private
45238       */
45239
45240
45241      var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer$$1) {
45242        transmuxer$$1.on('data', function (segment) {
45243          // transfer ownership of the underlying ArrayBuffer
45244          // instead of doing a copy to save memory
45245          // ArrayBuffers are transferable but generic TypedArrays are not
45246          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
45247          var initArray = segment.initSegment;
45248          segment.initSegment = {
45249            data: initArray.buffer,
45250            byteOffset: initArray.byteOffset,
45251            byteLength: initArray.byteLength
45252          };
45253          var typedArray = segment.data;
45254          segment.data = typedArray.buffer;
45255          self.postMessage({
45256            action: 'data',
45257            segment: segment,
45258            byteOffset: typedArray.byteOffset,
45259            byteLength: typedArray.byteLength
45260          }, [segment.data]);
45261        });
45262
45263        if (transmuxer$$1.captionStream) {
45264          transmuxer$$1.captionStream.on('data', function (caption) {
45265            self.postMessage({
45266              action: 'caption',
45267              data: caption
45268            });
45269          });
45270        }
45271
45272        transmuxer$$1.on('done', function (data) {
45273          self.postMessage({
45274            action: 'done'
45275          });
45276        });
45277        transmuxer$$1.on('gopInfo', function (gopInfo) {
45278          self.postMessage({
45279            action: 'gopInfo',
45280            gopInfo: gopInfo
45281          });
45282        });
45283        transmuxer$$1.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
45284          self.postMessage({
45285            action: 'videoSegmentTimingInfo',
45286            videoSegmentTimingInfo: videoSegmentTimingInfo
45287          });
45288        });
45289      };
45290      /**
45291       * All incoming messages route through this hash. If no function exists
45292       * to handle an incoming message, then we ignore the message.
45293       *
45294       * @class MessageHandlers
45295       * @param {Object} options the options to initialize with
45296       */
45297
45298
45299      var MessageHandlers = function () {
45300        function MessageHandlers(self, options) {
45301          classCallCheck(this, MessageHandlers);
45302          this.options = options || {};
45303          this.self = self;
45304          this.init();
45305        }
45306        /**
45307         * initialize our web worker and wire all the events.
45308         */
45309
45310
45311        createClass(MessageHandlers, [{
45312          key: 'init',
45313          value: function init() {
45314            if (this.transmuxer) {
45315              this.transmuxer.dispose();
45316            }
45317
45318            this.transmuxer = new transmuxer.Transmuxer(this.options);
45319            wireTransmuxerEvents(this.self, this.transmuxer);
45320          }
45321          /**
45322           * Adds data (a ts segment) to the start of the transmuxer pipeline for
45323           * processing.
45324           *
45325           * @param {ArrayBuffer} data data to push into the muxer
45326           */
45327
45328        }, {
45329          key: 'push',
45330          value: function push(data) {
45331            // Cast array buffer to correct type for transmuxer
45332            var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
45333            this.transmuxer.push(segment);
45334          }
45335          /**
45336           * Recreate the transmuxer so that the next segment added via `push`
45337           * start with a fresh transmuxer.
45338           */
45339
45340        }, {
45341          key: 'reset',
45342          value: function reset() {
45343            this.init();
45344          }
45345          /**
45346           * Set the value that will be used as the `baseMediaDecodeTime` time for the
45347           * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
45348           * set relative to the first based on the PTS values.
45349           *
45350           * @param {Object} data used to set the timestamp offset in the muxer
45351           */
45352
45353        }, {
45354          key: 'setTimestampOffset',
45355          value: function setTimestampOffset(data) {
45356            var timestampOffset = data.timestampOffset || 0;
45357            this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
45358          }
45359        }, {
45360          key: 'setAudioAppendStart',
45361          value: function setAudioAppendStart(data) {
45362            this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
45363          }
45364          /**
45365           * Forces the pipeline to finish processing the last segment and emit it's
45366           * results.
45367           *
45368           * @param {Object} data event data, not really used
45369           */
45370
45371        }, {
45372          key: 'flush',
45373          value: function flush(data) {
45374            this.transmuxer.flush();
45375          }
45376        }, {
45377          key: 'resetCaptions',
45378          value: function resetCaptions() {
45379            this.transmuxer.resetCaptions();
45380          }
45381        }, {
45382          key: 'alignGopsWith',
45383          value: function alignGopsWith(data) {
45384            this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
45385          }
45386        }]);
45387        return MessageHandlers;
45388      }();
45389      /**
45390       * Our web wroker interface so that things can talk to mux.js
45391       * that will be running in a web worker. the scope is passed to this by
45392       * webworkify.
45393       *
45394       * @param {Object} self the scope for the web worker
45395       */
45396
45397
45398      var TransmuxerWorker = function TransmuxerWorker(self) {
45399        self.onmessage = function (event) {
45400          if (event.data.action === 'init' && event.data.options) {
45401            this.messageHandlers = new MessageHandlers(self, event.data.options);
45402            return;
45403          }
45404
45405          if (!this.messageHandlers) {
45406            this.messageHandlers = new MessageHandlers(self);
45407          }
45408
45409          if (event.data && event.data.action && event.data.action !== 'init') {
45410            if (this.messageHandlers[event.data.action]) {
45411              this.messageHandlers[event.data.action](event.data);
45412            }
45413          }
45414        };
45415      };
45416
45417      var transmuxerWorker = new TransmuxerWorker(self);
45418      return transmuxerWorker;
45419    }();
45420  });
45421  /**
45422   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
45423   * codec strings, or translating codec strings into objects that can be examined.
45424   */
45425  // Default codec parameters if none were provided for video and/or audio
45426
45427  var defaultCodecs = {
45428    videoCodec: 'avc1',
45429    videoObjectTypeIndicator: '.4d400d',
45430    // AAC-LC
45431    audioProfile: '2'
45432  };
45433  /**
45434   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
45435   * `avc1.<hhhhhh>`
45436   *
45437   * @param {Array} codecs an array of codec strings to fix
45438   * @return {Array} the translated codec array
45439   * @private
45440   */
45441
45442  var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
45443    return codecs.map(function (codec) {
45444      return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
45445        var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
45446        var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
45447        return 'avc1.' + profileHex + '00' + avcLevelHex;
45448      });
45449    });
45450  };
45451  /**
45452   * Parses a codec string to retrieve the number of codecs specified,
45453   * the video codec and object type indicator, and the audio profile.
45454   */
45455
45456
45457  var parseCodecs = function parseCodecs() {
45458    var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
45459    var result = {
45460      codecCount: 0
45461    };
45462    var parsed = void 0;
45463    result.codecCount = codecs.split(',').length;
45464    result.codecCount = result.codecCount || 2; // parse the video codec
45465
45466    parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
45467
45468    if (parsed) {
45469      result.videoCodec = parsed[2];
45470      result.videoObjectTypeIndicator = parsed[3];
45471    } // parse the last field of the audio codec
45472
45473
45474    result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
45475    result.audioProfile = result.audioProfile && result.audioProfile[2];
45476    return result;
45477  };
45478  /**
45479   * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
45480   * standard `avc1.<hhhhhh>`.
45481   *
45482   * @param codecString {String} the codec string
45483   * @return {String} the codec string with old apple-style codecs replaced
45484   *
45485   * @private
45486   */
45487
45488
45489  var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
45490    return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
45491      return translateLegacyCodecs([match])[0];
45492    });
45493  };
45494  /**
45495   * Build a media mime-type string from a set of parameters
45496   * @param {String} type either 'audio' or 'video'
45497   * @param {String} container either 'mp2t' or 'mp4'
45498   * @param {Array} codecs an array of codec strings to add
45499   * @return {String} a valid media mime-type
45500   */
45501
45502
45503  var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
45504    // The codecs array is filtered so that falsey values are
45505    // dropped and don't cause Array#join to create spurious
45506    // commas
45507    return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
45508      return !!c;
45509    }).join(', ') + '"';
45510  };
45511  /**
45512   * Returns the type container based on information in the playlist
45513   * @param {Playlist} media the current media playlist
45514   * @return {String} a valid media container type
45515   */
45516
45517
45518  var getContainerType = function getContainerType(media) {
45519    // An initialization segment means the media playlist is an iframe
45520    // playlist or is using the mp4 container. We don't currently
45521    // support iframe playlists, so assume this is signalling mp4
45522    // fragments.
45523    if (media.segments && media.segments.length && media.segments[0].map) {
45524      return 'mp4';
45525    }
45526
45527    return 'mp2t';
45528  };
45529  /**
45530   * Returns a set of codec strings parsed from the playlist or the default
45531   * codec strings if no codecs were specified in the playlist
45532   * @param {Playlist} media the current media playlist
45533   * @return {Object} an object with the video and audio codecs
45534   */
45535
45536
45537  var getCodecs = function getCodecs(media) {
45538    // if the codecs were explicitly specified, use them instead of the
45539    // defaults
45540    var mediaAttributes = media.attributes || {};
45541
45542    if (mediaAttributes.CODECS) {
45543      return parseCodecs(mediaAttributes.CODECS);
45544    }
45545
45546    return defaultCodecs;
45547  };
45548
45549  var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
45550    if (!master.mediaGroups.AUDIO || !audioGroupId) {
45551      return null;
45552    }
45553
45554    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
45555
45556    if (!audioGroup) {
45557      return null;
45558    }
45559
45560    for (var name in audioGroup) {
45561      var audioType = audioGroup[name];
45562
45563      if (audioType["default"] && audioType.playlists) {
45564        // codec should be the same for all playlists within the audio type
45565        return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
45566      }
45567    }
45568
45569    return null;
45570  };
45571  /**
45572   * Calculates the MIME type strings for a working configuration of
45573   * SourceBuffers to play variant streams in a master playlist. If
45574   * there is no possible working configuration, an empty array will be
45575   * returned.
45576   *
45577   * @param master {Object} the m3u8 object for the master playlist
45578   * @param media {Object} the m3u8 object for the variant playlist
45579   * @return {Array} the MIME type strings. If the array has more than
45580   * one entry, the first element should be applied to the video
45581   * SourceBuffer and the second to the audio SourceBuffer.
45582   *
45583   * @private
45584   */
45585
45586
45587  var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
45588    var containerType = getContainerType(media);
45589    var codecInfo = getCodecs(media);
45590    var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
45591
45592    var isMuxed = true;
45593    var isMaat = false;
45594
45595    if (!media) {
45596      // Not enough information
45597      return [];
45598    }
45599
45600    if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
45601      var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
45602
45603      if (audioGroup) {
45604        isMaat = true; // Start with the everything demuxed then...
45605
45606        isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
45607
45608        for (var groupId in audioGroup) {
45609          // either a uri is present (if the case of HLS and an external playlist), or
45610          // playlists is present (in the case of DASH where we don't have external audio
45611          // playlists)
45612          if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
45613            isMuxed = true;
45614            break;
45615          }
45616        }
45617      }
45618    } // HLS with multiple-audio tracks must always get an audio codec.
45619    // Put another way, there is no way to have a video-only multiple-audio HLS!
45620
45621
45622    if (isMaat && !codecInfo.audioProfile) {
45623      if (!isMuxed) {
45624        // It is possible for codecs to be specified on the audio media group playlist but
45625        // not on the rendition playlist. This is mostly the case for DASH, where audio and
45626        // video are always separate (and separately specified).
45627        codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
45628      }
45629
45630      if (!codecInfo.audioProfile) {
45631        videojs$1.log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)');
45632        codecInfo.audioProfile = defaultCodecs.audioProfile;
45633      }
45634    } // Generate the final codec strings from the codec object generated above
45635
45636
45637    var codecStrings = {};
45638
45639    if (codecInfo.videoCodec) {
45640      codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
45641    }
45642
45643    if (codecInfo.audioProfile) {
45644      codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
45645    } // Finally, make and return an array with proper mime-types depending on
45646    // the configuration
45647
45648
45649    var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
45650    var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
45651    var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
45652
45653    if (isMaat) {
45654      if (!isMuxed && codecStrings.video) {
45655        return [justVideo, justAudio];
45656      }
45657
45658      if (!isMuxed && !codecStrings.video) {
45659        // There is no muxed content and no video codec string, so this is an audio only
45660        // stream with alternate audio.
45661        return [justAudio, justAudio];
45662      } // There exists the possiblity that this will return a `video/container`
45663      // mime-type for the first entry in the array even when there is only audio.
45664      // This doesn't appear to be a problem and simplifies the code.
45665
45666
45667      return [bothVideoAudio, justAudio];
45668    } // If there is no video codec at all, always just return a single
45669    // audio/<container> mime-type
45670
45671
45672    if (!codecStrings.video) {
45673      return [justAudio];
45674    } // When not using separate audio media groups, audio and video is
45675    // *always* muxed
45676
45677
45678    return [bothVideoAudio];
45679  };
45680  /**
45681   * Parse a content type header into a type and parameters
45682   * object
45683   *
45684   * @param {String} type the content type header
45685   * @return {Object} the parsed content-type
45686   * @private
45687   */
45688
45689
45690  var parseContentType = function parseContentType(type) {
45691    var object = {
45692      type: '',
45693      parameters: {}
45694    };
45695    var parameters = type.trim().split(';'); // first parameter should always be content-type
45696
45697    object.type = parameters.shift().trim();
45698    parameters.forEach(function (parameter) {
45699      var pair = parameter.trim().split('=');
45700
45701      if (pair.length > 1) {
45702        var name = pair[0].replace(/"/g, '').trim();
45703        var value = pair[1].replace(/"/g, '').trim();
45704        object.parameters[name] = value;
45705      }
45706    });
45707    return object;
45708  };
45709  /**
45710   * Check if a codec string refers to an audio codec.
45711   *
45712   * @param {String} codec codec string to check
45713   * @return {Boolean} if this is an audio codec
45714   * @private
45715   */
45716
45717
45718  var isAudioCodec = function isAudioCodec(codec) {
45719    return /mp4a\.\d+.\d+/i.test(codec);
45720  };
45721  /**
45722   * Check if a codec string refers to a video codec.
45723   *
45724   * @param {String} codec codec string to check
45725   * @return {Boolean} if this is a video codec
45726   * @private
45727   */
45728
45729
45730  var isVideoCodec = function isVideoCodec(codec) {
45731    return /avc1\.[\da-f]+/i.test(codec);
45732  };
45733  /**
45734   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
45735   * front of current time.
45736   *
45737   * @param {Array} buffer
45738   *        The current buffer of gop information
45739   * @param {Number} currentTime
45740   *        The current time
45741   * @param {Double} mapping
45742   *        Offset to map display time to stream presentation time
45743   * @return {Array}
45744   *         List of gops considered safe to append over
45745   */
45746
45747
45748  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
45749    if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
45750      return [];
45751    } // pts value for current time + 3 seconds to give a bit more wiggle room
45752
45753
45754    var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
45755    var i = void 0;
45756
45757    for (i = 0; i < buffer.length; i++) {
45758      if (buffer[i].pts > currentTimePts) {
45759        break;
45760      }
45761    }
45762
45763    return buffer.slice(i);
45764  };
45765  /**
45766   * Appends gop information (timing and byteLength) received by the transmuxer for the
45767   * gops appended in the last call to appendBuffer
45768   *
45769   * @param {Array} buffer
45770   *        The current buffer of gop information
45771   * @param {Array} gops
45772   *        List of new gop information
45773   * @param {boolean} replace
45774   *        If true, replace the buffer with the new gop information. If false, append the
45775   *        new gop information to the buffer in the right location of time.
45776   * @return {Array}
45777   *         Updated list of gop information
45778   */
45779
45780
45781  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
45782    if (!gops.length) {
45783      return buffer;
45784    }
45785
45786    if (replace) {
45787      // If we are in safe append mode, then completely overwrite the gop buffer
45788      // with the most recent appeneded data. This will make sure that when appending
45789      // future segments, we only try to align with gops that are both ahead of current
45790      // time and in the last segment appended.
45791      return gops.slice();
45792    }
45793
45794    var start = gops[0].pts;
45795    var i = 0;
45796
45797    for (i; i < buffer.length; i++) {
45798      if (buffer[i].pts >= start) {
45799        break;
45800      }
45801    }
45802
45803    return buffer.slice(0, i).concat(gops);
45804  };
45805  /**
45806   * Removes gop information in buffer that overlaps with provided start and end
45807   *
45808   * @param {Array} buffer
45809   *        The current buffer of gop information
45810   * @param {Double} start
45811   *        position to start the remove at
45812   * @param {Double} end
45813   *        position to end the remove at
45814   * @param {Double} mapping
45815   *        Offset to map display time to stream presentation time
45816   */
45817
45818
45819  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
45820    var startPts = Math.ceil((start - mapping) * 90000);
45821    var endPts = Math.ceil((end - mapping) * 90000);
45822    var updatedBuffer = buffer.slice();
45823    var i = buffer.length;
45824
45825    while (i--) {
45826      if (buffer[i].pts <= endPts) {
45827        break;
45828      }
45829    }
45830
45831    if (i === -1) {
45832      // no removal because end of remove range is before start of buffer
45833      return updatedBuffer;
45834    }
45835
45836    var j = i + 1;
45837
45838    while (j--) {
45839      if (buffer[j].pts <= startPts) {
45840        break;
45841      }
45842    } // clamp remove range start to 0 index
45843
45844
45845    j = Math.max(j, 0);
45846    updatedBuffer.splice(j, i - j + 1);
45847    return updatedBuffer;
45848  };
45849
45850  var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
45851    var start = null;
45852    var end = null;
45853    var arity = 0;
45854    var extents = [];
45855    var ranges = []; // neither buffer has been created yet
45856
45857    if (!videoBuffer && !audioBuffer) {
45858      return videojs$1.createTimeRange();
45859    } // only one buffer is configured
45860
45861
45862    if (!videoBuffer) {
45863      return audioBuffer.buffered;
45864    }
45865
45866    if (!audioBuffer) {
45867      return videoBuffer.buffered;
45868    } // both buffers are configured
45869
45870
45871    if (audioDisabled) {
45872      return videoBuffer.buffered;
45873    } // both buffers are empty
45874
45875
45876    if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
45877      return videojs$1.createTimeRange();
45878    } // Handle the case where we have both buffers and create an
45879    // intersection of the two
45880
45881
45882    var videoBuffered = videoBuffer.buffered;
45883    var audioBuffered = audioBuffer.buffered;
45884    var count = videoBuffered.length; // A) Gather up all start and end times
45885
45886    while (count--) {
45887      extents.push({
45888        time: videoBuffered.start(count),
45889        type: 'start'
45890      });
45891      extents.push({
45892        time: videoBuffered.end(count),
45893        type: 'end'
45894      });
45895    }
45896
45897    count = audioBuffered.length;
45898
45899    while (count--) {
45900      extents.push({
45901        time: audioBuffered.start(count),
45902        type: 'start'
45903      });
45904      extents.push({
45905        time: audioBuffered.end(count),
45906        type: 'end'
45907      });
45908    } // B) Sort them by time
45909
45910
45911    extents.sort(function (a, b) {
45912      return a.time - b.time;
45913    }); // C) Go along one by one incrementing arity for start and decrementing
45914    //    arity for ends
45915
45916    for (count = 0; count < extents.length; count++) {
45917      if (extents[count].type === 'start') {
45918        arity++; // D) If arity is ever incremented to 2 we are entering an
45919        //    overlapping range
45920
45921        if (arity === 2) {
45922          start = extents[count].time;
45923        }
45924      } else if (extents[count].type === 'end') {
45925        arity--; // E) If arity is ever decremented to 1 we leaving an
45926        //    overlapping range
45927
45928        if (arity === 1) {
45929          end = extents[count].time;
45930        }
45931      } // F) Record overlapping ranges
45932
45933
45934      if (start !== null && end !== null) {
45935        ranges.push([start, end]);
45936        start = null;
45937        end = null;
45938      }
45939    }
45940
45941    return videojs$1.createTimeRanges(ranges);
45942  };
45943  /**
45944   * @file virtual-source-buffer.js
45945   */
45946
45947
45948  var ONE_SECOND_IN_TS$2 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
45949  // state of the `updating` property manually. We have to do this because
45950  // Firefox changes `updating` to false long before triggering `updateend`
45951  // events and that was causing strange problems in videojs-contrib-hls
45952
45953  var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
45954    var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
45955    var wrapper = Object.create(null);
45956    wrapper.updating = false;
45957    wrapper.realBuffer_ = sourceBuffer;
45958
45959    var _loop = function _loop(key) {
45960      if (typeof sourceBuffer[key] === 'function') {
45961        wrapper[key] = function () {
45962          return sourceBuffer[key].apply(sourceBuffer, arguments);
45963        };
45964      } else if (typeof wrapper[key] === 'undefined') {
45965        Object.defineProperty(wrapper, key, {
45966          get: function get$$1() {
45967            return sourceBuffer[key];
45968          },
45969          set: function set$$1(v) {
45970            return sourceBuffer[key] = v;
45971          }
45972        });
45973      }
45974    };
45975
45976    for (var key in sourceBuffer) {
45977      _loop(key);
45978    }
45979
45980    return wrapper;
45981  };
45982  /**
45983   * VirtualSourceBuffers exist so that we can transmux non native formats
45984   * into a native format, but keep the same api as a native source buffer.
45985   * It creates a transmuxer, that works in its own thread (a web worker) and
45986   * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
45987   * then send all of that data to the naive sourcebuffer so that it is
45988   * indestinguishable from a natively supported format.
45989   *
45990   * @param {HtmlMediaSource} mediaSource the parent mediaSource
45991   * @param {Array} codecs array of codecs that we will be dealing with
45992   * @class VirtualSourceBuffer
45993   * @extends video.js.EventTarget
45994   */
45995
45996
45997  var VirtualSourceBuffer = function (_videojs$EventTarget) {
45998    inherits$2(VirtualSourceBuffer, _videojs$EventTarget);
45999
46000    function VirtualSourceBuffer(mediaSource, codecs) {
46001      classCallCheck$1(this, VirtualSourceBuffer);
46002
46003      var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
46004
46005      _this.timestampOffset_ = 0;
46006      _this.pendingBuffers_ = [];
46007      _this.bufferUpdating_ = false;
46008      _this.mediaSource_ = mediaSource;
46009      _this.codecs_ = codecs;
46010      _this.audioCodec_ = null;
46011      _this.videoCodec_ = null;
46012      _this.audioDisabled_ = false;
46013      _this.appendAudioInitSegment_ = true;
46014      _this.gopBuffer_ = [];
46015      _this.timeMapping_ = 0;
46016      _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
46017      var options = {
46018        remux: false,
46019        alignGopsAtEnd: _this.safeAppend_
46020      };
46021
46022      _this.codecs_.forEach(function (codec) {
46023        if (isAudioCodec(codec)) {
46024          _this.audioCodec_ = codec;
46025        } else if (isVideoCodec(codec)) {
46026          _this.videoCodec_ = codec;
46027        }
46028      }); // append muxed segments to their respective native buffers as
46029      // soon as they are available
46030
46031
46032      _this.transmuxer_ = new TransmuxWorker();
46033
46034      _this.transmuxer_.postMessage({
46035        action: 'init',
46036        options: options
46037      });
46038
46039      _this.transmuxer_.onmessage = function (event) {
46040        if (event.data.action === 'data') {
46041          return _this.data_(event);
46042        }
46043
46044        if (event.data.action === 'done') {
46045          return _this.done_(event);
46046        }
46047
46048        if (event.data.action === 'gopInfo') {
46049          return _this.appendGopInfo_(event);
46050        }
46051
46052        if (event.data.action === 'videoSegmentTimingInfo') {
46053          return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
46054        }
46055      }; // this timestampOffset is a property with the side-effect of resetting
46056      // baseMediaDecodeTime in the transmuxer on the setter
46057
46058
46059      Object.defineProperty(_this, 'timestampOffset', {
46060        get: function get$$1() {
46061          return this.timestampOffset_;
46062        },
46063        set: function set$$1(val) {
46064          if (typeof val === 'number' && val >= 0) {
46065            this.timestampOffset_ = val;
46066            this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
46067
46068            this.gopBuffer_.length = 0;
46069            this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
46070            // the desired timestampOffset for the next segment
46071
46072            this.transmuxer_.postMessage({
46073              action: 'setTimestampOffset',
46074              timestampOffset: val
46075            });
46076          }
46077        }
46078      }); // setting the append window affects both source buffers
46079
46080      Object.defineProperty(_this, 'appendWindowStart', {
46081        get: function get$$1() {
46082          return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
46083        },
46084        set: function set$$1(start) {
46085          if (this.videoBuffer_) {
46086            this.videoBuffer_.appendWindowStart = start;
46087          }
46088
46089          if (this.audioBuffer_) {
46090            this.audioBuffer_.appendWindowStart = start;
46091          }
46092        }
46093      }); // this buffer is "updating" if either of its native buffers are
46094
46095      Object.defineProperty(_this, 'updating', {
46096        get: function get$$1() {
46097          return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
46098        }
46099      }); // the buffered property is the intersection of the buffered
46100      // ranges of the native source buffers
46101
46102      Object.defineProperty(_this, 'buffered', {
46103        get: function get$$1() {
46104          return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
46105        }
46106      });
46107      return _this;
46108    }
46109    /**
46110     * When we get a data event from the transmuxer
46111     * we call this function and handle the data that
46112     * was sent to us
46113     *
46114     * @private
46115     * @param {Event} event the data event from the transmuxer
46116     */
46117
46118
46119    createClass$1(VirtualSourceBuffer, [{
46120      key: 'data_',
46121      value: function data_(event) {
46122        var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
46123
46124        segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
46125        segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
46126        createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
46127
46128        this.pendingBuffers_.push(segment);
46129        return;
46130      }
46131      /**
46132       * When we get a done event from the transmuxer
46133       * we call this function and we process all
46134       * of the pending data that we have been saving in the
46135       * data_ function
46136       *
46137       * @private
46138       * @param {Event} event the done event from the transmuxer
46139       */
46140
46141    }, {
46142      key: 'done_',
46143      value: function done_(event) {
46144        // Don't process and append data if the mediaSource is closed
46145        if (this.mediaSource_.readyState === 'closed') {
46146          this.pendingBuffers_.length = 0;
46147          return;
46148        } // All buffers should have been flushed from the muxer
46149        // start processing anything we have received
46150
46151
46152        this.processPendingSegments_();
46153        return;
46154      }
46155    }, {
46156      key: 'videoSegmentTimingInfo_',
46157      value: function videoSegmentTimingInfo_(timingInfo) {
46158        var timingInfoInSeconds = {
46159          start: {
46160            decode: timingInfo.start.dts / ONE_SECOND_IN_TS$2,
46161            presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$2
46162          },
46163          end: {
46164            decode: timingInfo.end.dts / ONE_SECOND_IN_TS$2,
46165            presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$2
46166          },
46167          baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$2
46168        };
46169
46170        if (timingInfo.prependedContentDuration) {
46171          timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$2;
46172        }
46173
46174        this.trigger({
46175          type: 'videoSegmentTimingInfo',
46176          videoSegmentTimingInfo: timingInfoInSeconds
46177        });
46178      }
46179      /**
46180       * Create our internal native audio/video source buffers and add
46181       * event handlers to them with the following conditions:
46182       * 1. they do not already exist on the mediaSource
46183       * 2. this VSB has a codec for them
46184       *
46185       * @private
46186       */
46187
46188    }, {
46189      key: 'createRealSourceBuffers_',
46190      value: function createRealSourceBuffers_() {
46191        var _this2 = this;
46192
46193        var types = ['audio', 'video'];
46194        types.forEach(function (type) {
46195          // Don't create a SourceBuffer of this type if we don't have a
46196          // codec for it
46197          if (!_this2[type + 'Codec_']) {
46198            return;
46199          } // Do nothing if a SourceBuffer of this type already exists
46200
46201
46202          if (_this2[type + 'Buffer_']) {
46203            return;
46204          }
46205
46206          var buffer = null; // If the mediasource already has a SourceBuffer for the codec
46207          // use that
46208
46209          if (_this2.mediaSource_[type + 'Buffer_']) {
46210            buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
46211            // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
46212            // than createRealSourceBuffers_ is called to create the second
46213            // VirtualSourceBuffer because that happens as a side-effect of
46214            // videojs-contrib-hls starting the audioSegmentLoader. As a result,
46215            // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 30,121 bytes, lines 46216-47041
46216            // the `updating` state back to false once the `updateend` event is received
46217            //
46218            // Setting `updating` to false manually will work around this
46219            // situation and allow work to continue
46220
46221            buffer.updating = false;
46222          } else {
46223            var codecProperty = type + 'Codec_';
46224            var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
46225            buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
46226            _this2.mediaSource_[type + 'Buffer_'] = buffer;
46227          }
46228
46229          _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
46230
46231          ['update', 'updatestart', 'updateend'].forEach(function (event) {
46232            buffer.addEventListener(event, function () {
46233              // if audio is disabled
46234              if (type === 'audio' && _this2.audioDisabled_) {
46235                return;
46236              }
46237
46238              if (event === 'updateend') {
46239                _this2[type + 'Buffer_'].updating = false;
46240              }
46241
46242              var shouldTrigger = types.every(function (t) {
46243                // skip checking audio's updating status if audio
46244                // is not enabled
46245                if (t === 'audio' && _this2.audioDisabled_) {
46246                  return true;
46247                } // if the other type is updating we don't trigger
46248
46249
46250                if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
46251                  return false;
46252                }
46253
46254                return true;
46255              });
46256
46257              if (shouldTrigger) {
46258                return _this2.trigger(event);
46259              }
46260            });
46261          });
46262        });
46263      }
46264      /**
46265       * Emulate the native mediasource function, but our function will
46266       * send all of the proposed segments to the transmuxer so that we
46267       * can transmux them before we append them to our internal
46268       * native source buffers in the correct format.
46269       *
46270       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
46271       * @param {Uint8Array} segment the segment to append to the buffer
46272       */
46273
46274    }, {
46275      key: 'appendBuffer',
46276      value: function appendBuffer(segment) {
46277        // Start the internal "updating" state
46278        this.bufferUpdating_ = true;
46279
46280        if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
46281          var audioBuffered = this.audioBuffer_.buffered;
46282          this.transmuxer_.postMessage({
46283            action: 'setAudioAppendStart',
46284            appendStart: audioBuffered.end(audioBuffered.length - 1)
46285          });
46286        }
46287
46288        if (this.videoBuffer_) {
46289          this.transmuxer_.postMessage({
46290            action: 'alignGopsWith',
46291            gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
46292          });
46293        }
46294
46295        this.transmuxer_.postMessage({
46296          action: 'push',
46297          // Send the typed-array of data as an ArrayBuffer so that
46298          // it can be sent as a "Transferable" and avoid the costly
46299          // memory copy
46300          data: segment.buffer,
46301          // To recreate the original typed-array, we need information
46302          // about what portion of the ArrayBuffer it was a view into
46303          byteOffset: segment.byteOffset,
46304          byteLength: segment.byteLength
46305        }, [segment.buffer]);
46306        this.transmuxer_.postMessage({
46307          action: 'flush'
46308        });
46309      }
46310      /**
46311       * Appends gop information (timing and byteLength) received by the transmuxer for the
46312       * gops appended in the last call to appendBuffer
46313       *
46314       * @param {Event} event
46315       *        The gopInfo event from the transmuxer
46316       * @param {Array} event.data.gopInfo
46317       *        List of gop info to append
46318       */
46319
46320    }, {
46321      key: 'appendGopInfo_',
46322      value: function appendGopInfo_(event) {
46323        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
46324      }
46325      /**
46326       * Emulate the native mediasource function and remove parts
46327       * of the buffer from any of our internal buffers that exist
46328       *
46329       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
46330       * @param {Double} start position to start the remove at
46331       * @param {Double} end position to end the remove at
46332       */
46333
46334    }, {
46335      key: 'remove',
46336      value: function remove(start, end) {
46337        if (this.videoBuffer_) {
46338          this.videoBuffer_.updating = true;
46339          this.videoBuffer_.remove(start, end);
46340          this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
46341        }
46342
46343        if (!this.audioDisabled_ && this.audioBuffer_) {
46344          this.audioBuffer_.updating = true;
46345          this.audioBuffer_.remove(start, end);
46346        } // Remove Metadata Cues (id3)
46347
46348
46349        removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
46350
46351        if (this.inbandTextTracks_) {
46352          for (var track in this.inbandTextTracks_) {
46353            removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
46354          }
46355        }
46356      }
46357      /**
46358       * Process any segments that the muxer has output
46359       * Concatenate segments together based on type and append them into
46360       * their respective sourceBuffers
46361       *
46362       * @private
46363       */
46364
46365    }, {
46366      key: 'processPendingSegments_',
46367      value: function processPendingSegments_() {
46368        var sortedSegments = {
46369          video: {
46370            segments: [],
46371            bytes: 0
46372          },
46373          audio: {
46374            segments: [],
46375            bytes: 0
46376          },
46377          captions: [],
46378          metadata: []
46379        };
46380
46381        if (!this.pendingBuffers_.length) {
46382          // We are no longer in the internal "updating" state
46383          this.trigger('updateend');
46384          this.bufferUpdating_ = false;
46385          return;
46386        } // Sort segments into separate video/audio arrays and
46387        // keep track of their total byte lengths
46388
46389
46390        sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
46391          var type = segment.type;
46392          var data = segment.data;
46393          var initSegment = segment.initSegment;
46394          segmentObj[type].segments.push(data);
46395          segmentObj[type].bytes += data.byteLength;
46396          segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
46397
46398          if (segment.captions) {
46399            segmentObj.captions = segmentObj.captions.concat(segment.captions);
46400          }
46401
46402          if (segment.info) {
46403            segmentObj[type].info = segment.info;
46404          } // Gather any metadata into a single array
46405
46406
46407          if (segment.metadata) {
46408            segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
46409          }
46410
46411          return segmentObj;
46412        }, sortedSegments); // Create the real source buffers if they don't exist by now since we
46413        // finally are sure what tracks are contained in the source
46414
46415        if (!this.videoBuffer_ && !this.audioBuffer_) {
46416          // Remove any codecs that may have been specified by default but
46417          // are no longer applicable now
46418          if (sortedSegments.video.bytes === 0) {
46419            this.videoCodec_ = null;
46420          }
46421
46422          if (sortedSegments.audio.bytes === 0) {
46423            this.audioCodec_ = null;
46424          }
46425
46426          this.createRealSourceBuffers_();
46427        }
46428
46429        if (sortedSegments.audio.info) {
46430          this.mediaSource_.trigger({
46431            type: 'audioinfo',
46432            info: sortedSegments.audio.info
46433          });
46434        }
46435
46436        if (sortedSegments.video.info) {
46437          this.mediaSource_.trigger({
46438            type: 'videoinfo',
46439            info: sortedSegments.video.info
46440          });
46441        }
46442
46443        if (this.appendAudioInitSegment_) {
46444          if (!this.audioDisabled_ && this.audioBuffer_) {
46445            sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
46446            sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
46447          }
46448
46449          this.appendAudioInitSegment_ = false;
46450        }
46451
46452        var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
46453
46454        if (this.videoBuffer_ && sortedSegments.video.bytes) {
46455          sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
46456          sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
46457          this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
46458        } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
46459          // The transmuxer did not return any bytes of video, meaning it was all trimmed
46460          // for gop alignment. Since we have a video buffer and audio is disabled, updateend
46461          // will never be triggered by this source buffer, which will cause contrib-hls
46462          // to be stuck forever waiting for updateend. If audio is not disabled, updateend
46463          // will be triggered by the audio buffer, which will be sent upwards since the video
46464          // buffer will not be in an updating state.
46465          triggerUpdateend = true;
46466        } // Add text-track data for all
46467
46468
46469        addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
46470
46471        if (!this.audioDisabled_ && this.audioBuffer_) {
46472          this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
46473        }
46474
46475        this.pendingBuffers_.length = 0;
46476
46477        if (triggerUpdateend) {
46478          this.trigger('updateend');
46479        } // We are no longer in the internal "updating" state
46480
46481
46482        this.bufferUpdating_ = false;
46483      }
46484      /**
46485       * Combine all segments into a single Uint8Array and then append them
46486       * to the destination buffer
46487       *
46488       * @param {Object} segmentObj
46489       * @param {SourceBuffer} destinationBuffer native source buffer to append data to
46490       * @private
46491       */
46492
46493    }, {
46494      key: 'concatAndAppendSegments_',
46495      value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
46496        var offset = 0;
46497        var tempBuffer = void 0;
46498
46499        if (segmentObj.bytes) {
46500          tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
46501
46502          segmentObj.segments.forEach(function (segment) {
46503            tempBuffer.set(segment, offset);
46504            offset += segment.byteLength;
46505          });
46506
46507          try {
46508            destinationBuffer.updating = true;
46509            destinationBuffer.appendBuffer(tempBuffer);
46510          } catch (error) {
46511            if (this.mediaSource_.player_) {
46512              this.mediaSource_.player_.error({
46513                code: -3,
46514                type: 'APPEND_BUFFER_ERR',
46515                message: error.message,
46516                originalError: error
46517              });
46518            }
46519          }
46520        }
46521      }
46522      /**
46523       * Emulate the native mediasource function. abort any soureBuffer
46524       * actions and throw out any un-appended data.
46525       *
46526       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
46527       */
46528
46529    }, {
46530      key: 'abort',
46531      value: function abort() {
46532        if (this.videoBuffer_) {
46533          this.videoBuffer_.abort();
46534        }
46535
46536        if (!this.audioDisabled_ && this.audioBuffer_) {
46537          this.audioBuffer_.abort();
46538        }
46539
46540        if (this.transmuxer_) {
46541          this.transmuxer_.postMessage({
46542            action: 'reset'
46543          });
46544        }
46545
46546        this.pendingBuffers_.length = 0;
46547        this.bufferUpdating_ = false;
46548      }
46549    }]);
46550    return VirtualSourceBuffer;
46551  }(videojs$1.EventTarget);
46552  /**
46553   * @file html-media-source.js
46554   */
46555
46556  /**
46557   * Our MediaSource implementation in HTML, mimics native
46558   * MediaSource where/if possible.
46559   *
46560   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
46561   * @class HtmlMediaSource
46562   * @extends videojs.EventTarget
46563   */
46564
46565
46566  var HtmlMediaSource = function (_videojs$EventTarget) {
46567    inherits$2(HtmlMediaSource, _videojs$EventTarget);
46568
46569    function HtmlMediaSource() {
46570      classCallCheck$1(this, HtmlMediaSource);
46571
46572      var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
46573
46574      var property = void 0;
46575      _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
46576
46577      for (property in _this.nativeMediaSource_) {
46578        if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
46579          _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
46580        }
46581      } // emulate `duration` and `seekable` until seeking can be
46582      // handled uniformly for live streams
46583      // see https://github.com/w3c/media-source/issues/5
46584
46585
46586      _this.duration_ = NaN;
46587      Object.defineProperty(_this, 'duration', {
46588        get: function get$$1() {
46589          if (this.duration_ === Infinity) {
46590            return this.duration_;
46591          }
46592
46593          return this.nativeMediaSource_.duration;
46594        },
46595        set: function set$$1(duration) {
46596          this.duration_ = duration;
46597
46598          if (duration !== Infinity) {
46599            this.nativeMediaSource_.duration = duration;
46600            return;
46601          }
46602        }
46603      });
46604      Object.defineProperty(_this, 'seekable', {
46605        get: function get$$1() {
46606          if (this.duration_ === Infinity) {
46607            return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
46608          }
46609
46610          return this.nativeMediaSource_.seekable;
46611        }
46612      });
46613      Object.defineProperty(_this, 'readyState', {
46614        get: function get$$1() {
46615          return this.nativeMediaSource_.readyState;
46616        }
46617      });
46618      Object.defineProperty(_this, 'activeSourceBuffers', {
46619        get: function get$$1() {
46620          return this.activeSourceBuffers_;
46621        }
46622      }); // the list of virtual and native SourceBuffers created by this
46623      // MediaSource
46624
46625      _this.sourceBuffers = [];
46626      _this.activeSourceBuffers_ = [];
46627      /**
46628       * update the list of active source buffers based upon various
46629       * imformation from HLS and video.js
46630       *
46631       * @private
46632       */
46633
46634      _this.updateActiveSourceBuffers_ = function () {
46635        // Retain the reference but empty the array
46636        _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
46637        // be disabled based on the codec of the source buffer
46638
46639        if (_this.sourceBuffers.length === 1) {
46640          var sourceBuffer = _this.sourceBuffers[0];
46641          sourceBuffer.appendAudioInitSegment_ = true;
46642          sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
46643
46644          _this.activeSourceBuffers_.push(sourceBuffer);
46645
46646          return;
46647        } // There are 2 source buffers, a combined (possibly video only) source buffer and
46648        // and an audio only source buffer.
46649        // By default, the audio in the combined virtual source buffer is enabled
46650        // and the audio-only source buffer (if it exists) is disabled.
46651
46652
46653        var disableCombined = false;
46654        var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
46655        // safari may do something like this
46656
46657        for (var i = 0; i < _this.player_.audioTracks().length; i++) {
46658          var track = _this.player_.audioTracks()[i];
46659
46660          if (track.enabled && track.kind !== 'main') {
46661            // The enabled track is an alternate audio track so disable the audio in
46662            // the combined source buffer and enable the audio-only source buffer.
46663            disableCombined = true;
46664            disableAudioOnly = false;
46665            break;
46666          }
46667        }
46668
46669        _this.sourceBuffers.forEach(function (sourceBuffer, index) {
46670          /* eslinst-disable */
46671          // TODO once codecs are required, we can switch to using the codecs to determine
46672          //      what stream is the video stream, rather than relying on videoTracks
46673
46674          /* eslinst-enable */
46675          sourceBuffer.appendAudioInitSegment_ = true;
46676
46677          if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
46678            // combined
46679            sourceBuffer.audioDisabled_ = disableCombined;
46680          } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
46681            // If the "combined" source buffer is video only, then we do not want
46682            // disable the audio-only source buffer (this is mostly for demuxed
46683            // audio and video hls)
46684            sourceBuffer.audioDisabled_ = true;
46685            disableAudioOnly = false;
46686          } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
46687            // audio only
46688            // In the case of audio only with alternate audio and disableAudioOnly is true
46689            // this means we want to disable the audio on the alternate audio sourcebuffer
46690            // but not the main "combined" source buffer. The "combined" source buffer is
46691            // always at index 0, so this ensures audio won't be disabled in both source
46692            // buffers.
46693            sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
46694
46695            if (sourceBuffer.audioDisabled_) {
46696              return;
46697            }
46698          }
46699
46700          _this.activeSourceBuffers_.push(sourceBuffer);
46701        });
46702      };
46703
46704      _this.onPlayerMediachange_ = function () {
46705        _this.sourceBuffers.forEach(function (sourceBuffer) {
46706          sourceBuffer.appendAudioInitSegment_ = true;
46707        });
46708      };
46709
46710      _this.onHlsReset_ = function () {
46711        _this.sourceBuffers.forEach(function (sourceBuffer) {
46712          if (sourceBuffer.transmuxer_) {
46713            sourceBuffer.transmuxer_.postMessage({
46714              action: 'resetCaptions'
46715            });
46716          }
46717        });
46718      };
46719
46720      _this.onHlsSegmentTimeMapping_ = function (event) {
46721        _this.sourceBuffers.forEach(function (buffer) {
46722          return buffer.timeMapping_ = event.mapping;
46723        });
46724      }; // Re-emit MediaSource events on the polyfill
46725
46726
46727      ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
46728        this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
46729      }, _this); // capture the associated player when the MediaSource is
46730      // successfully attached
46731
46732      _this.on('sourceopen', function (event) {
46733        // Get the player this MediaSource is attached to
46734        var video = document.querySelector('[src="' + _this.url_ + '"]');
46735
46736        if (!video) {
46737          return;
46738        }
46739
46740        _this.player_ = videojs$1(video.parentNode);
46741
46742        if (!_this.player_) {
46743          return;
46744        } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
46745        // resets its state and flushes the buffer
46746
46747
46748        _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
46749        // SegmentLoader inspects an MTS segment and has an accurate stream to display
46750        // time mapping
46751
46752
46753        _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
46754
46755        if (_this.player_.audioTracks && _this.player_.audioTracks()) {
46756          _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
46757
46758          _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
46759
46760          _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
46761        }
46762
46763        _this.player_.on('mediachange', _this.onPlayerMediachange_);
46764      });
46765
46766      _this.on('sourceended', function (event) {
46767        var duration = durationOfVideo(_this.duration);
46768
46769        for (var i = 0; i < _this.sourceBuffers.length; i++) {
46770          var sourcebuffer = _this.sourceBuffers[i];
46771          var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
46772
46773          if (cues && cues.length) {
46774            cues[cues.length - 1].endTime = duration;
46775          }
46776        }
46777      }); // explicitly terminate any WebWorkers that were created
46778      // by SourceHandlers
46779
46780
46781      _this.on('sourceclose', function (event) {
46782        this.sourceBuffers.forEach(function (sourceBuffer) {
46783          if (sourceBuffer.transmuxer_) {
46784            sourceBuffer.transmuxer_.terminate();
46785          }
46786        });
46787        this.sourceBuffers.length = 0;
46788
46789        if (!this.player_) {
46790          return;
46791        }
46792
46793        if (this.player_.audioTracks && this.player_.audioTracks()) {
46794          this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
46795          this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
46796          this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
46797        } // We can only change this if the player hasn't been disposed of yet
46798        // because `off` eventually tries to use the el_ property. If it has
46799        // been disposed of, then don't worry about it because there are no
46800        // event handlers left to unbind anyway
46801
46802
46803        if (this.player_.el_) {
46804          this.player_.off('mediachange', this.onPlayerMediachange_);
46805        }
46806
46807        if (this.player_.tech_ && this.player_.tech_.el_) {
46808          this.player_.tech_.off('hls-reset', this.onHlsReset_);
46809          this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
46810        }
46811      });
46812
46813      return _this;
46814    }
46815    /**
46816     * Add a range that that can now be seeked to.
46817     *
46818     * @param {Double} start where to start the addition
46819     * @param {Double} end where to end the addition
46820     * @private
46821     */
46822
46823
46824    createClass$1(HtmlMediaSource, [{
46825      key: 'addSeekableRange_',
46826      value: function addSeekableRange_(start, end) {
46827        var error = void 0;
46828
46829        if (this.duration !== Infinity) {
46830          error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
46831          error.name = 'InvalidStateError';
46832          error.code = 11;
46833          throw error;
46834        }
46835
46836        if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
46837          this.nativeMediaSource_.duration = end;
46838        }
46839      }
46840      /**
46841       * Add a source buffer to the media source.
46842       *
46843       * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
46844       * @param {String} type the content-type of the content
46845       * @return {Object} the created source buffer
46846       */
46847
46848    }, {
46849      key: 'addSourceBuffer',
46850      value: function addSourceBuffer(type) {
46851        var buffer = void 0;
46852        var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
46853        // stream segments into fragmented MP4s
46854
46855        if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
46856          var codecs = [];
46857
46858          if (parsedType.parameters && parsedType.parameters.codecs) {
46859            codecs = parsedType.parameters.codecs.split(',');
46860            codecs = translateLegacyCodecs(codecs);
46861            codecs = codecs.filter(function (codec) {
46862              return isAudioCodec(codec) || isVideoCodec(codec);
46863            });
46864          }
46865
46866          if (codecs.length === 0) {
46867            codecs = ['avc1.4d400d', 'mp4a.40.2'];
46868          }
46869
46870          buffer = new VirtualSourceBuffer(this, codecs);
46871
46872          if (this.sourceBuffers.length !== 0) {
46873            // If another VirtualSourceBuffer already exists, then we are creating a
46874            // SourceBuffer for an alternate audio track and therefore we know that
46875            // the source has both an audio and video track.
46876            // That means we should trigger the manual creation of the real
46877            // SourceBuffers instead of waiting for the transmuxer to return data
46878            this.sourceBuffers[0].createRealSourceBuffers_();
46879            buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
46880            // a second source buffer is ever created
46881
46882            this.sourceBuffers[0].audioDisabled_ = true;
46883          }
46884        } else {
46885          // delegate to the native implementation
46886          buffer = this.nativeMediaSource_.addSourceBuffer(type);
46887        }
46888
46889        this.sourceBuffers.push(buffer);
46890        return buffer;
46891      }
46892    }]);
46893    return HtmlMediaSource;
46894  }(videojs$1.EventTarget);
46895  /**
46896   * @file videojs-contrib-media-sources.js
46897   */
46898
46899
46900  var urlCount = 0; // ------------
46901  // Media Source
46902  // ------------
46903  // store references to the media sources so they can be connected
46904  // to a video element (a swf object)
46905  // TODO: can we store this somewhere local to this module?
46906
46907  videojs$1.mediaSources = {};
46908  /**
46909   * Provide a method for a swf object to notify JS that a
46910   * media source is now open.
46911   *
46912   * @param {String} msObjectURL string referencing the MSE Object URL
46913   * @param {String} swfId the swf id
46914   */
46915
46916  var open = function open(msObjectURL, swfId) {
46917    var mediaSource = videojs$1.mediaSources[msObjectURL];
46918
46919    if (mediaSource) {
46920      mediaSource.trigger({
46921        type: 'sourceopen',
46922        swfId: swfId
46923      });
46924    } else {
46925      throw new Error('Media Source not found (Video.js)');
46926    }
46927  };
46928  /**
46929   * Check to see if the native MediaSource object exists and supports
46930   * an MP4 container with both H.264 video and AAC-LC audio.
46931   *
46932   * @return {Boolean} if  native media sources are supported
46933   */
46934
46935
46936  var supportsNativeMediaSources = function supportsNativeMediaSources() {
46937    return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
46938  };
46939  /**
46940   * An emulation of the MediaSource API so that we can support
46941   * native and non-native functionality. returns an instance of
46942   * HtmlMediaSource.
46943   *
46944   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
46945   */
46946
46947
46948  var MediaSource = function MediaSource() {
46949    this.MediaSource = {
46950      open: open,
46951      supportsNativeMediaSources: supportsNativeMediaSources
46952    };
46953
46954    if (supportsNativeMediaSources()) {
46955      return new HtmlMediaSource();
46956    }
46957
46958    throw new Error('Cannot use create a virtual MediaSource for this video');
46959  };
46960
46961  MediaSource.open = open;
46962  MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
46963  /**
46964   * A wrapper around the native URL for our MSE object
46965   * implementation, this object is exposed under videojs.URL
46966   *
46967   * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
46968   */
46969
46970  var URL$1 = {
46971    /**
46972     * A wrapper around the native createObjectURL for our objects.
46973     * This function maps a native or emulated mediaSource to a blob
46974     * url so that it can be loaded into video.js
46975     *
46976     * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
46977     * @param {MediaSource} object the object to create a blob url to
46978     */
46979    createObjectURL: function createObjectURL(object) {
46980      var objectUrlPrefix = 'blob:vjs-media-source/';
46981      var url = void 0; // use the native MediaSource to generate an object URL
46982
46983      if (object instanceof HtmlMediaSource) {
46984        url = window$1.URL.createObjectURL(object.nativeMediaSource_);
46985        object.url_ = url;
46986        return url;
46987      } // if the object isn't an emulated MediaSource, delegate to the
46988      // native implementation
46989
46990
46991      if (!(object instanceof HtmlMediaSource)) {
46992        url = window$1.URL.createObjectURL(object);
46993        object.url_ = url;
46994        return url;
46995      } // build a URL that can be used to map back to the emulated
46996      // MediaSource
46997
46998
46999      url = objectUrlPrefix + urlCount;
47000      urlCount++; // setup the mapping back to object
47001
47002      videojs$1.mediaSources[url] = object;
47003      return url;
47004    }
47005  };
47006  videojs$1.MediaSource = MediaSource;
47007  videojs$1.URL = URL$1;
47008  var EventTarget$1$1 = videojs$1.EventTarget,
47009      mergeOptions$2 = videojs$1.mergeOptions;
47010  /**
47011   * Returns a new master manifest that is the result of merging an updated master manifest
47012   * into the original version.
47013   *
47014   * @param {Object} oldMaster
47015   *        The old parsed mpd object
47016   * @param {Object} newMaster
47017   *        The updated parsed mpd object
47018   * @return {Object}
47019   *         A new object representing the original master manifest with the updated media
47020   *         playlists merged in
47021   */
47022
47023  var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
47024    var noChanges = void 0;
47025    var update = mergeOptions$2(oldMaster, {
47026      // These are top level properties that can be updated
47027      duration: newMaster.duration,
47028      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
47029    }); // First update the playlists in playlist list
47030
47031    for (var i = 0; i < newMaster.playlists.length; i++) {
47032      var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
47033
47034      if (playlistUpdate) {
47035        update = playlistUpdate;
47036      } else {
47037        noChanges = true;
47038      }
47039    } // Then update media group playlists
47040
47041
47042    forEachMediaGroup(newMaster, function (properties, type, group, label) {
47043      if (properties.playlists && properties.playlists.length) {
47044        var uri = properties.playlists[0].uri;
47045
47046        var _playlistUpdate = updateMaster(update, properties.playlists[0]);
47047
47048        if (_playlistUpdate) {
47049          update = _playlistUpdate; // update the playlist reference within media groups
47050
47051          update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
47052          noChanges = false;
47053        }
47054      }
47055    });
47056
47057    if (noChanges) {
47058      return null;
47059    }
47060
47061    return update;
47062  };
47063
47064  var generateSidxKey = function generateSidxKey(sidxInfo) {
47065    // should be non-inclusive
47066    var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
47067    return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
47068  }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
47069  // If the SIDXs have maps, the two maps should match,
47070  // both `a` and `b` missing SIDXs is considered matching.
47071  // If `a` or `b` but not both have a map, they aren't matching.
47072
47073
47074  var equivalentSidx = function equivalentSidx(a, b) {
47075    var neitherMap = Boolean(!a.map && !b.map);
47076    var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
47077    return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
47078  }; // exported for testing
47079
47080
47081  var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
47082    var newSidxMapping = {};
47083
47084    for (var uri in playlists) {
47085      var playlist = playlists[uri];
47086      var currentSidxInfo = playlist.sidx;
47087
47088      if (currentSidxInfo) {
47089        var key = generateSidxKey(currentSidxInfo);
47090
47091        if (!oldSidxMapping[key]) {
47092          break;
47093        }
47094
47095        var savedSidxInfo = oldSidxMapping[key].sidxInfo;
47096
47097        if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
47098          newSidxMapping[key] = oldSidxMapping[key];
47099        }
47100      }
47101    }
47102
47103    return newSidxMapping;
47104  };
47105  /**
47106   *  A function that filters out changed items as they need to be requested separately.
47107   *
47108   *  The method is exported for testing
47109   *
47110   *  @param {Object} masterXml the mpd XML
47111   *  @param {string} srcUrl the mpd url
47112   *  @param {Date} clientOffset a time difference between server and client (passed through and not used)
47113   *  @param {Object} oldSidxMapping the SIDX to compare against
47114   */
47115
47116
47117  var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
47118    // Don't pass current sidx mapping
47119    var master = parse(masterXml, {
47120      manifestUri: srcUrl,
47121      clientOffset: clientOffset
47122    });
47123    var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
47124    var mediaGroupSidx = videoSidx;
47125    forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
47126      if (properties.playlists && properties.playlists.length) {
47127        var playlists = properties.playlists;
47128        mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
47129      }
47130    });
47131    return mediaGroupSidx;
47132  }; // exported for testing
47133
47134
47135  var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) {
47136    var sidxInfo = {
47137      // resolve the segment URL relative to the playlist
47138      uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
47139      // resolvedUri: sidxRange.resolvedUri,
47140      byterange: sidxRange.byterange,
47141      // the segment's playlist
47142      playlist: playlist
47143    };
47144    var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
47145      responseType: 'arraybuffer',
47146      headers: segmentXhrHeaders(sidxInfo)
47147    });
47148    return xhr(sidxRequestOptions, finishProcessingFn);
47149  };
47150
47151  var DashPlaylistLoader = function (_EventTarget) {
47152    inherits$2(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
47153    // playlist loader setups from media groups will expect to be able to pass a playlist
47154    // (since there aren't external URLs to media playlists with DASH)
47155
47156    function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
47157      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
47158      var masterPlaylistLoader = arguments[3];
47159      classCallCheck$1(this, DashPlaylistLoader);
47160
47161      var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
47162
47163      var _options$withCredenti = options.withCredentials,
47164          withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
47165          _options$handleManife = options.handleManifestRedirects,
47166          handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
47167      _this.hls_ = hls;
47168      _this.withCredentials = withCredentials;
47169      _this.handleManifestRedirects = handleManifestRedirects;
47170
47171      if (!srcUrlOrPlaylist) {
47172        throw new Error('A non-empty playlist URL or playlist is required');
47173      } // event naming?
47174
47175
47176      _this.on('minimumUpdatePeriod', function () {
47177        _this.refreshXml_();
47178      }); // live playlist staleness timeout
47179
47180
47181      _this.on('mediaupdatetimeout', function () {
47182        _this.refreshMedia_(_this.media().uri);
47183      });
47184
47185      _this.state = 'HAVE_NOTHING';
47186      _this.loadedPlaylists_ = {}; // initialize the loader state
47187      // The masterPlaylistLoader will be created with a string
47188
47189      if (typeof srcUrlOrPlaylist === 'string') {
47190        _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
47191        // once multi-period is refactored
47192
47193        _this.sidxMapping_ = {};
47194        return possibleConstructorReturn$1(_this);
47195      }
47196
47197      _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
47198
47199      return _this;
47200    }
47201
47202    createClass$1(DashPlaylistLoader, [{
47203      key: 'setupChildLoader',
47204      value: function setupChildLoader(masterPlaylistLoader, playlist) {
47205        this.masterPlaylistLoader_ = masterPlaylistLoader;
47206        this.childPlaylist_ = playlist;
47207      }
47208    }, {
47209      key: 'dispose',
47210      value: function dispose() {
47211        this.stopRequest();
47212        this.loadedPlaylists_ = {};
47213        window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
47214        window$1.clearTimeout(this.mediaRequest_);
47215        window$1.clearTimeout(this.mediaUpdateTimeout);
47216      }
47217    }, {
47218      key: 'hasPendingRequest',
47219      value: function hasPendingRequest() {
47220        return this.request || this.mediaRequest_;
47221      }
47222    }, {
47223      key: 'stopRequest',
47224      value: function stopRequest() {
47225        if (this.request) {
47226          var oldRequest = this.request;
47227          this.request = null;
47228          oldRequest.onreadystatechange = null;
47229          oldRequest.abort();
47230        }
47231      }
47232    }, {
47233      key: 'sidxRequestFinished_',
47234      value: function sidxRequestFinished_(playlist, master, startingState, doneFn) {
47235        var _this2 = this;
47236
47237        return function (err, request) {
47238          // disposed
47239          if (!_this2.request) {
47240            return;
47241          } // pending request is cleared
47242
47243
47244          _this2.request = null;
47245
47246          if (err) {
47247            _this2.error = {
47248              status: request.status,
47249              message: 'DASH playlist request error at URL: ' + playlist.uri,
47250              response: request.response,
47251              // MEDIA_ERR_NETWORK
47252              code: 2
47253            };
47254
47255            if (startingState) {
47256              _this2.state = startingState;
47257            }
47258
47259            _this2.trigger('error');
47260
47261            return doneFn(master, null);
47262          }
47263
47264          var bytes = new Uint8Array(request.response);
47265          var sidx = mp4Inspector.parseSidx(bytes.subarray(8));
47266          return doneFn(master, sidx);
47267        };
47268      }
47269    }, {
47270      key: 'media',
47271      value: function media(playlist) {
47272        var _this3 = this; // getter
47273
47274
47275        if (!playlist) {
47276          return this.media_;
47277        } // setter
47278
47279
47280        if (this.state === 'HAVE_NOTHING') {
47281          throw new Error('Cannot switch media playlist from ' + this.state);
47282        }
47283
47284        var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
47285
47286        if (typeof playlist === 'string') {
47287          if (!this.master.playlists[playlist]) {
47288            throw new Error('Unknown playlist URI: ' + playlist);
47289          }
47290
47291          playlist = this.master.playlists[playlist];
47292        }
47293
47294        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
47295
47296        if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
47297          this.state = 'HAVE_METADATA';
47298          this.media_ = playlist; // trigger media change if the active media has been updated
47299
47300          if (mediaChange) {
47301            this.trigger('mediachanging');
47302            this.trigger('mediachange');
47303          }
47304
47305          return;
47306        } // switching to the active playlist is a no-op
47307
47308
47309        if (!mediaChange) {
47310          return;
47311        } // switching from an already loaded playlist
47312
47313
47314        if (this.media_) {
47315          this.trigger('mediachanging');
47316        }
47317
47318        if (!playlist.sidx) {
47319          // Continue asynchronously if there is no sidx
47320          // wait one tick to allow haveMaster to run first on a child loader
47321          this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
47322            startingState: startingState,
47323            playlist: playlist
47324          }), 0); // exit early and don't do sidx work
47325
47326          return;
47327        } // we have sidx mappings
47328
47329
47330        var oldMaster = void 0;
47331        var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store
47332        // it on the masterPlaylistLoader
47333
47334        if (this.masterPlaylistLoader_) {
47335          oldMaster = this.masterPlaylistLoader_.master;
47336          sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
47337        } else {
47338          oldMaster = this.master;
47339          sidxMapping = this.sidxMapping_;
47340        }
47341
47342        var sidxKey = generateSidxKey(playlist.sidx);
47343        sidxMapping[sidxKey] = {
47344          sidxInfo: playlist.sidx
47345        };
47346        this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, {
47347          handleManifestRedirects: this.handleManifestRedirects
47348        }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
47349          if (!newMaster || !sidx) {
47350            throw new Error('failed to request sidx');
47351          } // update loader's sidxMapping with parsed sidx box
47352
47353
47354          sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
47355
47356          _this3.haveMetadata({
47357            startingState: startingState,
47358            playlist: newMaster.playlists[playlist.uri]
47359          });
47360        }));
47361      }
47362    }, {
47363      key: 'haveMetadata',
47364      value: function haveMetadata(_ref) {
47365        var startingState = _ref.startingState,
47366            playlist = _ref.playlist;
47367        this.state = 'HAVE_METADATA';
47368        this.loadedPlaylists_[playlist.uri] = playlist;
47369        this.mediaRequest_ = null; // This will trigger loadedplaylist
47370
47371        this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
47372        // to resolve setup of media groups
47373
47374        if (startingState === 'HAVE_MASTER') {
47375          this.trigger('loadedmetadata');
47376        } else {
47377          // trigger media change if the active media has been updated
47378          this.trigger('mediachange');
47379        }
47380      }
47381    }, {
47382      key: 'pause',
47383      value: function pause() {
47384        this.stopRequest();
47385        window$1.clearTimeout(this.mediaUpdateTimeout);
47386        window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
47387
47388        if (this.state === 'HAVE_NOTHING') {
47389          // If we pause the loader before any data has been retrieved, its as if we never
47390          // started, so reset to an unstarted state.
47391          this.started = false;
47392        }
47393      }
47394    }, {
47395      key: 'load',
47396      value: function load(isFinalRendition) {
47397        var _this4 = this;
47398
47399        window$1.clearTimeout(this.mediaUpdateTimeout);
47400        window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
47401        var media = this.media();
47402
47403        if (isFinalRendition) {
47404          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
47405          this.mediaUpdateTimeout = window$1.setTimeout(function () {
47406            return _this4.load();
47407          }, delay);
47408          return;
47409        } // because the playlists are internal to the manifest, load should either load the
47410        // main manifest, or do nothing but trigger an event
47411
47412
47413        if (!this.started) {
47414          this.start();
47415          return;
47416        }
47417
47418        this.trigger('loadedplaylist');
47419      }
47420      /**
47421       * Parses the master xml string and updates playlist uri references
47422       *
47423       * @return {Object}
47424       *         The parsed mpd manifest object
47425       */
47426
47427    }, {
47428      key: 'parseMasterXml',
47429      value: function parseMasterXml() {
47430        var master = parse(this.masterXml_, {
47431          manifestUri: this.srcUrl,
47432          clientOffset: this.clientOffset_,
47433          sidxMapping: this.sidxMapping_
47434        });
47435        master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
47436        // but reference playlists by their URI throughout the project
47437        // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
47438
47439        for (var i = 0; i < master.playlists.length; i++) {
47440          var phonyUri = 'placeholder-uri-' + i;
47441          master.playlists[i].uri = phonyUri; // set up by URI references
47442
47443          master.playlists[phonyUri] = master.playlists[i];
47444        } // set up phony URIs for the media group playlists since we won't have external
47445        // URIs for DASH but reference playlists by their URI throughout the project
47446
47447
vendor: 25,700 bytes, lines 47448-48218
47448        forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
47449          if (properties.playlists && properties.playlists.length) {
47450            var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
47451
47452            properties.playlists[0].uri = _phonyUri; // setup URI references
47453
47454            master.playlists[_phonyUri] = properties.playlists[0];
47455          }
47456        });
47457        setupMediaPlaylists(master);
47458        resolveMediaGroupUris(master);
47459        return master;
47460      }
47461    }, {
47462      key: 'start',
47463      value: function start() {
47464        var _this5 = this;
47465
47466        this.started = true; // We don't need to request the master manifest again
47467        // Call this asynchronously to match the xhr request behavior below
47468
47469        if (this.masterPlaylistLoader_) {
47470          this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
47471          return;
47472        } // request the specified URL
47473
47474
47475        this.request = this.hls_.xhr({
47476          uri: this.srcUrl,
47477          withCredentials: this.withCredentials
47478        }, function (error, req) {
47479          // disposed
47480          if (!_this5.request) {
47481            return;
47482          } // clear the loader's request reference
47483
47484
47485          _this5.request = null;
47486
47487          if (error) {
47488            _this5.error = {
47489              status: req.status,
47490              message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
47491              responseText: req.responseText,
47492              // MEDIA_ERR_NETWORK
47493              code: 2
47494            };
47495
47496            if (_this5.state === 'HAVE_NOTHING') {
47497              _this5.started = false;
47498            }
47499
47500            return _this5.trigger('error');
47501          }
47502
47503          _this5.masterXml_ = req.responseText;
47504
47505          if (req.responseHeaders && req.responseHeaders.date) {
47506            _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
47507          } else {
47508            _this5.masterLoaded_ = Date.now();
47509          }
47510
47511          _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
47512
47513          _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
47514        });
47515      }
47516      /**
47517       * Parses the master xml for UTCTiming node to sync the client clock to the server
47518       * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
47519       *
47520       * @param {Function} done
47521       *        Function to call when clock sync has completed
47522       */
47523
47524    }, {
47525      key: 'syncClientServerClock_',
47526      value: function syncClientServerClock_(done) {
47527        var _this6 = this;
47528
47529        var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
47530        // server clock
47531
47532        if (utcTiming === null) {
47533          this.clientOffset_ = this.masterLoaded_ - Date.now();
47534          return done();
47535        }
47536
47537        if (utcTiming.method === 'DIRECT') {
47538          this.clientOffset_ = utcTiming.value - Date.now();
47539          return done();
47540        }
47541
47542        this.request = this.hls_.xhr({
47543          uri: resolveUrl$1(this.srcUrl, utcTiming.value),
47544          method: utcTiming.method,
47545          withCredentials: this.withCredentials
47546        }, function (error, req) {
47547          // disposed
47548          if (!_this6.request) {
47549            return;
47550          }
47551
47552          if (error) {
47553            // sync request failed, fall back to using date header from mpd
47554            // TODO: log warning
47555            _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
47556            return done();
47557          }
47558
47559          var serverTime = void 0;
47560
47561          if (utcTiming.method === 'HEAD') {
47562            if (!req.responseHeaders || !req.responseHeaders.date) {
47563              // expected date header not preset, fall back to using date header from mpd
47564              // TODO: log warning
47565              serverTime = _this6.masterLoaded_;
47566            } else {
47567              serverTime = Date.parse(req.responseHeaders.date);
47568            }
47569          } else {
47570            serverTime = Date.parse(req.responseText);
47571          }
47572
47573          _this6.clientOffset_ = serverTime - Date.now();
47574          done();
47575        });
47576      }
47577    }, {
47578      key: 'haveMaster_',
47579      value: function haveMaster_() {
47580        this.state = 'HAVE_MASTER'; // clear media request
47581
47582        this.mediaRequest_ = null;
47583
47584        if (!this.masterPlaylistLoader_) {
47585          this.master = this.parseMasterXml(); // We have the master playlist at this point, so
47586          // trigger this to allow MasterPlaylistController
47587          // to make an initial playlist selection
47588
47589          this.trigger('loadedplaylist');
47590        } else if (!this.media_) {
47591          // no media playlist was specifically selected so select
47592          // the one the child playlist loader was created with
47593          this.media(this.childPlaylist_);
47594        }
47595      }
47596      /**
47597       * Handler for after client/server clock synchronization has happened. Sets up
47598       * xml refresh timer if specificed by the manifest.
47599       */
47600
47601    }, {
47602      key: 'onClientServerClockSync_',
47603      value: function onClientServerClockSync_() {
47604        var _this7 = this;
47605
47606        this.haveMaster_();
47607
47608        if (!this.hasPendingRequest() && !this.media_) {
47609          this.media(this.master.playlists[0]);
47610        } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
47611        // be refreshed when this is the case. The inter-op guide says that when the
47612        // minimumUpdatePeriod is 0, the manifest should outline all currently available
47613        // segments, but future segments may require an update. I think a good solution
47614        // would be to update the manifest at the same rate that the media playlists
47615        // are "refreshed", i.e. every targetDuration.
47616
47617
47618        if (this.master && this.master.minimumUpdatePeriod) {
47619          this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
47620            _this7.trigger('minimumUpdatePeriod');
47621          }, this.master.minimumUpdatePeriod);
47622        }
47623      }
47624      /**
47625       * Sends request to refresh the master xml and updates the parsed master manifest
47626       * TODO: Does the client offset need to be recalculated when the xml is refreshed?
47627       */
47628
47629    }, {
47630      key: 'refreshXml_',
47631      value: function refreshXml_() {
47632        var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
47633        // sidx is implemented
47634
47635
47636        this.request = this.hls_.xhr({
47637          uri: this.srcUrl,
47638          withCredentials: this.withCredentials
47639        }, function (error, req) {
47640          // disposed
47641          if (!_this8.request) {
47642            return;
47643          } // clear the loader's request reference
47644
47645
47646          _this8.request = null;
47647
47648          if (error) {
47649            _this8.error = {
47650              status: req.status,
47651              message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
47652              responseText: req.responseText,
47653              // MEDIA_ERR_NETWORK
47654              code: 2
47655            };
47656
47657            if (_this8.state === 'HAVE_NOTHING') {
47658              _this8.started = false;
47659            }
47660
47661            return _this8.trigger('error');
47662          }
47663
47664          _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
47665
47666          _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
47667
47668          var master = _this8.parseMasterXml();
47669
47670          var updatedMaster = updateMaster$1(_this8.master, master);
47671
47672          var currentSidxInfo = _this8.media().sidx;
47673
47674          if (updatedMaster) {
47675            if (currentSidxInfo) {
47676              var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed
47677
47678              if (!_this8.sidxMapping_[sidxKey]) {
47679                var playlist = _this8.media();
47680
47681                _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, {
47682                  handleManifestRedirects: _this8.handleManifestRedirects
47683                }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
47684                  if (!newMaster || !sidx) {
47685                    throw new Error('failed to request sidx on minimumUpdatePeriod');
47686                  } // update loader's sidxMapping with parsed sidx box
47687
47688
47689                  _this8.sidxMapping_[sidxKey].sidx = sidx;
47690                  _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
47691                    _this8.trigger('minimumUpdatePeriod');
47692                  }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist?
47693
47694                  _this8.refreshMedia_(_this8.media().uri);
47695
47696                  return;
47697                }));
47698              }
47699            } else {
47700              _this8.master = updatedMaster;
47701            }
47702          }
47703
47704          _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
47705            _this8.trigger('minimumUpdatePeriod');
47706          }, _this8.master.minimumUpdatePeriod);
47707        });
47708      }
47709      /**
47710       * Refreshes the media playlist by re-parsing the master xml and updating playlist
47711       * references. If this is an alternate loader, the updated parsed manifest is retrieved
47712       * from the master loader.
47713       */
47714
47715    }, {
47716      key: 'refreshMedia_',
47717      value: function refreshMedia_(mediaUri) {
47718        var _this9 = this;
47719
47720        if (!mediaUri) {
47721          throw new Error('refreshMedia_ must take a media uri');
47722        }
47723
47724        var oldMaster = void 0;
47725        var newMaster = void 0;
47726
47727        if (this.masterPlaylistLoader_) {
47728          oldMaster = this.masterPlaylistLoader_.master;
47729          newMaster = this.masterPlaylistLoader_.parseMasterXml();
47730        } else {
47731          oldMaster = this.master;
47732          newMaster = this.parseMasterXml();
47733        }
47734
47735        var updatedMaster = updateMaster$1(oldMaster, newMaster);
47736
47737        if (updatedMaster) {
47738          if (this.masterPlaylistLoader_) {
47739            this.masterPlaylistLoader_.master = updatedMaster;
47740          } else {
47741            this.master = updatedMaster;
47742          }
47743
47744          this.media_ = updatedMaster.playlists[mediaUri];
47745        } else {
47746          this.media_ = newMaster.playlists[mediaUri];
47747          this.trigger('playlistunchanged');
47748        }
47749
47750        if (!this.media().endList) {
47751          this.mediaUpdateTimeout = window$1.setTimeout(function () {
47752            _this9.trigger('mediaupdatetimeout');
47753          }, refreshDelay(this.media(), !!updatedMaster));
47754        }
47755
47756        this.trigger('loadedplaylist');
47757      }
47758    }]);
47759    return DashPlaylistLoader;
47760  }(EventTarget$1$1);
47761
47762  var logger = function logger(source) {
47763    if (videojs$1.log.debug) {
47764      return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
47765    }
47766
47767    return function () {};
47768  };
47769
47770  function noop$1() {}
47771  /**
47772   * @file source-updater.js
47773   */
47774
47775  /**
47776   * A queue of callbacks to be serialized and applied when a
47777   * MediaSource and its associated SourceBuffers are not in the
47778   * updating state. It is used by the segment loader to update the
47779   * underlying SourceBuffers when new data is loaded, for instance.
47780   *
47781   * @class SourceUpdater
47782   * @param {MediaSource} mediaSource the MediaSource to create the
47783   * SourceBuffer from
47784   * @param {String} mimeType the desired MIME type of the underlying
47785   * SourceBuffer
47786   * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
47787   * added to the media source
47788   */
47789
47790
47791  var SourceUpdater = function () {
47792    function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
47793      classCallCheck$1(this, SourceUpdater);
47794      this.callbacks_ = [];
47795      this.pendingCallback_ = null;
47796      this.timestampOffset_ = 0;
47797      this.mediaSource = mediaSource;
47798      this.processedAppend_ = false;
47799      this.type_ = type;
47800      this.mimeType_ = mimeType;
47801      this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
47802
47803      if (mediaSource.readyState === 'closed') {
47804        mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
47805      } else {
47806        this.createSourceBuffer_(mimeType, sourceBufferEmitter);
47807      }
47808    }
47809
47810    createClass$1(SourceUpdater, [{
47811      key: 'createSourceBuffer_',
47812      value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
47813        var _this = this;
47814
47815        this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
47816        this.logger_('created SourceBuffer');
47817
47818        if (sourceBufferEmitter) {
47819          sourceBufferEmitter.trigger('sourcebufferadded');
47820
47821          if (this.mediaSource.sourceBuffers.length < 2) {
47822            // There's another source buffer we must wait for before we can start updating
47823            // our own (or else we can get into a bad state, i.e., appending video/audio data
47824            // before the other video/audio source buffer is available and leading to a video
47825            // or audio only buffer).
47826            sourceBufferEmitter.on('sourcebufferadded', function () {
47827              _this.start_();
47828            });
47829            return;
47830          }
47831        }
47832
47833        this.start_();
47834      }
47835    }, {
47836      key: 'start_',
47837      value: function start_() {
47838        var _this2 = this;
47839
47840        this.started_ = true; // run completion handlers and process callbacks as updateend
47841        // events fire
47842
47843        this.onUpdateendCallback_ = function () {
47844          var pendingCallback = _this2.pendingCallback_;
47845          _this2.pendingCallback_ = null;
47846          _this2.sourceBuffer_.removing = false;
47847
47848          _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
47849
47850          if (pendingCallback) {
47851            pendingCallback();
47852          }
47853
47854          _this2.runCallback_();
47855        };
47856
47857        this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
47858        this.runCallback_();
47859      }
47860      /**
47861       * Aborts the current segment and resets the segment parser.
47862       *
47863       * @param {Function} done function to call when done
47864       * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
47865       */
47866
47867    }, {
47868      key: 'abort',
47869      value: function abort(done) {
47870        var _this3 = this;
47871
47872        if (this.processedAppend_) {
47873          this.queueCallback_(function () {
47874            _this3.sourceBuffer_.abort();
47875          }, done);
47876        }
47877      }
47878      /**
47879       * Queue an update to append an ArrayBuffer.
47880       *
47881       * @param {ArrayBuffer} bytes
47882       * @param {Function} done the function to call when done
47883       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
47884       */
47885
47886    }, {
47887      key: 'appendBuffer',
47888      value: function appendBuffer(config, done) {
47889        var _this4 = this;
47890
47891        this.processedAppend_ = true;
47892        this.queueCallback_(function () {
47893          if (config.videoSegmentTimingInfoCallback) {
47894            _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
47895          }
47896
47897          _this4.sourceBuffer_.appendBuffer(config.bytes);
47898        }, function () {
47899          if (config.videoSegmentTimingInfoCallback) {
47900            _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
47901          }
47902
47903          done();
47904        });
47905      }
47906      /**
47907       * Indicates what TimeRanges are buffered in the managed SourceBuffer.
47908       *
47909       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
47910       */
47911
47912    }, {
47913      key: 'buffered',
47914      value: function buffered() {
47915        if (!this.sourceBuffer_) {
47916          return videojs$1.createTimeRanges();
47917        }
47918
47919        return this.sourceBuffer_.buffered;
47920      }
47921      /**
47922       * Queue an update to remove a time range from the buffer.
47923       *
47924       * @param {Number} start where to start the removal
47925       * @param {Number} end where to end the removal
47926       * @param {Function} [done=noop] optional callback to be executed when the remove
47927       * operation is complete
47928       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
47929       */
47930
47931    }, {
47932      key: 'remove',
47933      value: function remove(start, end) {
47934        var _this5 = this;
47935
47936        var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
47937
47938        if (this.processedAppend_) {
47939          this.queueCallback_(function () {
47940            _this5.logger_('remove [' + start + ' => ' + end + ']');
47941
47942            _this5.sourceBuffer_.removing = true;
47943
47944            _this5.sourceBuffer_.remove(start, end);
47945          }, done);
47946        }
47947      }
47948      /**
47949       * Whether the underlying sourceBuffer is updating or not
47950       *
47951       * @return {Boolean} the updating status of the SourceBuffer
47952       */
47953
47954    }, {
47955      key: 'updating',
47956      value: function updating() {
47957        // we are updating if the sourcebuffer is updating or
47958        return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
47959        !!this.pendingCallback_ && this.pendingCallback_ !== noop$1;
47960      }
47961      /**
47962       * Set/get the timestampoffset on the SourceBuffer
47963       *
47964       * @return {Number} the timestamp offset
47965       */
47966
47967    }, {
47968      key: 'timestampOffset',
47969      value: function timestampOffset(offset) {
47970        var _this6 = this;
47971
47972        if (typeof offset !== 'undefined') {
47973          this.queueCallback_(function () {
47974            _this6.sourceBuffer_.timestampOffset = offset;
47975
47976            _this6.runCallback_();
47977          });
47978          this.timestampOffset_ = offset;
47979        }
47980
47981        return this.timestampOffset_;
47982      }
47983      /**
47984       * Queue a callback to run
47985       */
47986
47987    }, {
47988      key: 'queueCallback_',
47989      value: function queueCallback_(callback, done) {
47990        this.callbacks_.push([callback.bind(this), done]);
47991        this.runCallback_();
47992      }
47993      /**
47994       * Run a queued callback
47995       */
47996
47997    }, {
47998      key: 'runCallback_',
47999      value: function runCallback_() {
48000        var callbacks = void 0;
48001
48002        if (!this.updating() && this.callbacks_.length && this.started_) {
48003          callbacks = this.callbacks_.shift();
48004          this.pendingCallback_ = callbacks[1];
48005          callbacks[0]();
48006        }
48007      }
48008      /**
48009       * dispose of the source updater and the underlying sourceBuffer
48010       */
48011
48012    }, {
48013      key: 'dispose',
48014      value: function dispose() {
48015        var _this7 = this;
48016
48017        var disposeFn = function disposeFn() {
48018          if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') {
48019            _this7.sourceBuffer_.abort();
48020          }
48021
48022          _this7.sourceBuffer_.removeEventListener('updateend', disposeFn);
48023        };
48024
48025        this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
48026
48027        if (this.sourceBuffer_.removing) {
48028          this.sourceBuffer_.addEventListener('updateend', disposeFn);
48029        } else {
48030          disposeFn();
48031        }
48032      }
48033    }]);
48034    return SourceUpdater;
48035  }();
48036
48037  var Config = {
48038    GOAL_BUFFER_LENGTH: 30,
48039    MAX_GOAL_BUFFER_LENGTH: 60,
48040    GOAL_BUFFER_LENGTH_RATE: 1,
48041    // 0.5 MB/s
48042    INITIAL_BANDWIDTH: 4194304,
48043    // A fudge factor to apply to advertised playlist bitrates to account for
48044    // temporary flucations in client bandwidth
48045    BANDWIDTH_VARIANCE: 1.2,
48046    // How much of the buffer must be filled before we consider upswitching
48047    BUFFER_LOW_WATER_LINE: 0,
48048    MAX_BUFFER_LOW_WATER_LINE: 30,
48049    BUFFER_LOW_WATER_LINE_RATE: 1
48050  };
48051  var REQUEST_ERRORS = {
48052    FAILURE: 2,
48053    TIMEOUT: -101,
48054    ABORTED: -102
48055  };
48056  /**
48057   * Abort all requests
48058   *
48059   * @param {Object} activeXhrs - an object that tracks all XHR requests
48060   */
48061
48062  var abortAll = function abortAll(activeXhrs) {
48063    activeXhrs.forEach(function (xhr) {
48064      xhr.abort();
48065    });
48066  };
48067  /**
48068   * Gather important bandwidth stats once a request has completed
48069   *
48070   * @param {Object} request - the XHR request from which to gather stats
48071   */
48072
48073
48074  var getRequestStats = function getRequestStats(request) {
48075    return {
48076      bandwidth: request.bandwidth,
48077      bytesReceived: request.bytesReceived || 0,
48078      roundTripTime: request.roundTripTime || 0
48079    };
48080  };
48081  /**
48082   * If possible gather bandwidth stats as a request is in
48083   * progress
48084   *
48085   * @param {Event} progressEvent - an event object from an XHR's progress event
48086   */
48087
48088
48089  var getProgressStats = function getProgressStats(progressEvent) {
48090    var request = progressEvent.target;
48091    var roundTripTime = Date.now() - request.requestTime;
48092    var stats = {
48093      bandwidth: Infinity,
48094      bytesReceived: 0,
48095      roundTripTime: roundTripTime || 0
48096    };
48097    stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
48098    // because we should only use bandwidth stats on progress to determine when
48099    // abort a request early due to insufficient bandwidth
48100
48101    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
48102    return stats;
48103  };
48104  /**
48105   * Handle all error conditions in one place and return an object
48106   * with all the information
48107   *
48108   * @param {Error|null} error - if non-null signals an error occured with the XHR
48109   * @param {Object} request -  the XHR request that possibly generated the error
48110   */
48111
48112
48113  var handleErrors = function handleErrors(error, request) {
48114    if (request.timedout) {
48115      return {
48116        status: request.status,
48117        message: 'HLS request timed-out at URL: ' + request.uri,
48118        code: REQUEST_ERRORS.TIMEOUT,
48119        xhr: request
48120      };
48121    }
48122
48123    if (request.aborted) {
48124      return {
48125        status: request.status,
48126        message: 'HLS request aborted at URL: ' + request.uri,
48127        code: REQUEST_ERRORS.ABORTED,
48128        xhr: request
48129      };
48130    }
48131
48132    if (error) {
48133      return {
48134        status: request.status,
48135        message: 'HLS request errored at URL: ' + request.uri,
48136        code: REQUEST_ERRORS.FAILURE,
48137        xhr: request
48138      };
48139    }
48140
48141    return null;
48142  };
48143  /**
48144   * Handle responses for key data and convert the key data to the correct format
48145   * for the decryption step later
48146   *
48147   * @param {Object} segment - a simplified copy of the segmentInfo object
48148   *                           from SegmentLoader
48149   * @param {Function} finishProcessingFn - a callback to execute to continue processing
48150   *                                        this request
48151   */
48152
48153
48154  var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
48155    return function (error, request) {
48156      var response = request.response;
48157      var errorObj = handleErrors(error, request);
48158
48159      if (errorObj) {
48160        return finishProcessingFn(errorObj, segment);
48161      }
48162
48163      if (response.byteLength !== 16) {
48164        return finishProcessingFn({
48165          status: request.status,
48166          message: 'Invalid HLS key at URL: ' + request.uri,
48167          code: REQUEST_ERRORS.FAILURE,
48168          xhr: request
48169        }, segment);
48170      }
48171
48172      var view = new DataView(response);
48173      segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
48174      return finishProcessingFn(null, segment);
48175    };
48176  };
48177  /**
48178   * Handle init-segment responses
48179   *
48180   * @param {Object} segment - a simplified copy of the segmentInfo object
48181   *                           from SegmentLoader
48182   * @param {Function} finishProcessingFn - a callback to execute to continue processing
48183   *                                        this request
48184   */
48185
48186
48187  var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
48188    return function (error, request) {
48189      var response = request.response;
48190      var errorObj = handleErrors(error, request);
48191
48192      if (errorObj) {
48193        return finishProcessingFn(errorObj, segment);
48194      } // stop processing if received empty content
48195
48196
48197      if (response.byteLength === 0) {
48198        return finishProcessingFn({
48199          status: request.status,
48200          message: 'Empty HLS segment content at URL: ' + request.uri,
48201          code: REQUEST_ERRORS.FAILURE,
48202          xhr: request
48203        }, segment);
48204      }
48205
48206      segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
48207
48208      if (captionParser && !captionParser.isInitialized()) {
48209        captionParser.init();
48210      }
48211
48212      segment.map.timescales = probe.timescale(segment.map.bytes);
48213      segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes);
48214      return finishProcessingFn(null, segment);
48215    };
48216  };
48217  /**
48218   * Response handler for segment-requests being sure to set the correct
48219   * property depending on whether the segment is encryped or not
48220   * Also records and keeps track of stats that are used for ABR purposes
48221   *
48222   * @param {Object} segment - a simplified copy of the segmentInfo object
48223   *                           from SegmentLoader
48224   * @param {Function} finishProcessingFn - a callback to execute to continue processing
48225   *                                        this request
48226   */
48227
48228
48229  var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
48230    return function (error, request) {
48231      var response = request.response;
48232      var errorObj = handleErrors(error, request);
48233      var parsed = void 0;
48234
48235      if (errorObj) {
48236        return finishProcessingFn(errorObj, segment);
48237      } // stop processing if received empty content
48238
48239
48240      if (response.byteLength === 0) {
48241        return finishProcessingFn({
48242          status: request.status,
48243          message: 'Empty HLS segment content at URL: ' + request.uri,
48244          code: REQUEST_ERRORS.FAILURE,
48245          xhr: request
48246        }, segment);
48247      }
48248
48249      segment.stats = getRequestStats(request);
48250
48251      if (segment.key) {
48252        segment.encryptedBytes = new Uint8Array(request.response);
48253      } else {
48254        segment.bytes = new Uint8Array(request.response);
48255      } // This is likely an FMP4 and has the init segment.
48256      // Run through the CaptionParser in case there are captions.
48257
48258
48259      if (captionParser && segment.map && segment.map.bytes) {
48260        // Initialize CaptionParser if it hasn't been yet
48261        if (!captionParser.isInitialized()) {
48262          captionParser.init();
48263        }
48264
48265        parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
48266
48267        if (parsed && parsed.captions) {
48268          segment.captionStreams = parsed.captionStreams;
48269          segment.fmp4Captions = parsed.captions;
48270        }
48271      }
48272
48273      return finishProcessingFn(null, segment);
48274    };
48275  };
48276  /**
48277   * Decrypt the segment via the decryption web worker
48278   *
48279   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
48280   * @param {Object} segment - a simplified copy of the segmentInfo object
48281   *                           from SegmentLoader
48282   * @param {Function} doneFn - a callback that is executed after decryption has completed
48283   */
48284
48285
48286  var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
48287    var decryptionHandler = function decryptionHandler(event) {
48288      if (event.data.source === segment.requestId) {
48289        decrypter.removeEventListener('message', decryptionHandler);
48290        var decrypted = event.data.decrypted;
48291        segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
48292        return doneFn(null, segment);
48293      }
48294    };
48295
48296    decrypter.addEventListener('message', decryptionHandler);
48297    var keyBytes = void 0;
48298
48299    if (segment.key.bytes.slice) {
48300      keyBytes = segment.key.bytes.slice();
48301    } else {
48302      keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes));
48303    } // this is an encrypted segment
48304    // incrementally decrypt the segment
48305
48306
48307    decrypter.postMessage(createTransferableMessage({
48308      source: segment.requestId,
48309      encrypted: segment.encryptedBytes,
48310      key: keyBytes,
48311      iv: segment.key.iv
48312    }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
48313  };
48314  /**
48315   * This function waits for all XHRs to finish (with either success or failure)
48316   * before continueing processing via it's callback. The function gathers errors
48317   * from each request into a single errors array so that the error status for
48318   * each request can be examined later.
48319   *
48320   * @param {Object} activeXhrs - an object that tracks all XHR requests
48321   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
48322   * @param {Function} doneFn - a callback that is executed after all resources have been
48323   *                            downloaded and any decryption completed
48324   */
48325
48326
48327  var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
48328    var count = 0;
48329    var didError = false;
48330    return function (error, segment) {
48331      if (didError) {
48332        return;
48333      }
48334
48335      if (error) {
48336        didError = true; // If there are errors, we have to abort any outstanding requests
48337
48338        abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
48339        // handle the aborted events from those requests, there are some cases where we may
48340        // never get an aborted event. For instance, if the network connection is lost and
vendor: 199,163 bytes, lines 48341-53660
48341        // there were two requests, the first may have triggered an error immediately, while
48342        // the second request remains unsent. In that case, the aborted algorithm will not
48343        // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
48344        //
48345        // We also can't rely on the ready state of the XHR, since the request that
48346        // triggered the connection error may also show as a ready state of 0 (unsent).
48347        // Therefore, we have to finish this group of requests immediately after the first
48348        // seen error.
48349
48350        return doneFn(error, segment);
48351      }
48352
48353      count += 1;
48354
48355      if (count === activeXhrs.length) {
48356        // Keep track of when *all* of the requests have completed
48357        segment.endOfAllRequests = Date.now();
48358
48359        if (segment.encryptedBytes) {
48360          return decryptSegment(decrypter, segment, doneFn);
48361        } // Otherwise, everything is ready just continue
48362
48363
48364        return doneFn(null, segment);
48365      }
48366    };
48367  };
48368  /**
48369   * Simple progress event callback handler that gathers some stats before
48370   * executing a provided callback with the `segment` object
48371   *
48372   * @param {Object} segment - a simplified copy of the segmentInfo object
48373   *                           from SegmentLoader
48374   * @param {Function} progressFn - a callback that is executed each time a progress event
48375   *                                is received
48376   * @param {Event} event - the progress event object from XMLHttpRequest
48377   */
48378
48379
48380  var handleProgress = function handleProgress(segment, progressFn) {
48381    return function (event) {
48382      segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
48383
48384      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
48385        segment.stats.firstBytesReceivedAt = Date.now();
48386      }
48387
48388      return progressFn(event, segment);
48389    };
48390  };
48391  /**
48392   * Load all resources and does any processing necessary for a media-segment
48393   *
48394   * Features:
48395   *   decrypts the media-segment if it has a key uri and an iv
48396   *   aborts *all* requests if *any* one request fails
48397   *
48398   * The segment object, at minimum, has the following format:
48399   * {
48400   *   resolvedUri: String,
48401   *   [byterange]: {
48402   *     offset: Number,
48403   *     length: Number
48404   *   },
48405   *   [key]: {
48406   *     resolvedUri: String
48407   *     [byterange]: {
48408   *       offset: Number,
48409   *       length: Number
48410   *     },
48411   *     iv: {
48412   *       bytes: Uint32Array
48413   *     }
48414   *   },
48415   *   [map]: {
48416   *     resolvedUri: String,
48417   *     [byterange]: {
48418   *       offset: Number,
48419   *       length: Number
48420   *     },
48421   *     [bytes]: Uint8Array
48422   *   }
48423   * }
48424   * ...where [name] denotes optional properties
48425   *
48426   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
48427   * @param {Object} xhrOptions - the base options to provide to all xhr requests
48428   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
48429   *                                       decryption routines
48430   * @param {Object} segment - a simplified copy of the segmentInfo object
48431   *                           from SegmentLoader
48432   * @param {Function} progressFn - a callback that receives progress events from the main
48433   *                                segment's xhr request
48434   * @param {Function} doneFn - a callback that is executed only once all requests have
48435   *                            succeeded or failed
48436   * @returns {Function} a function that, when invoked, immediately aborts all
48437   *                     outstanding requests
48438   */
48439
48440
48441  var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
48442    var activeXhrs = [];
48443    var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
48444
48445    if (segment.key && !segment.key.bytes) {
48446      var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
48447        uri: segment.key.resolvedUri,
48448        responseType: 'arraybuffer'
48449      });
48450      var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
48451      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
48452      activeXhrs.push(keyXhr);
48453    } // optionally, request the associated media init segment
48454
48455
48456    if (segment.map && !segment.map.bytes) {
48457      var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
48458        uri: segment.map.resolvedUri,
48459        responseType: 'arraybuffer',
48460        headers: segmentXhrHeaders(segment.map)
48461      });
48462      var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
48463      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
48464      activeXhrs.push(initSegmentXhr);
48465    }
48466
48467    var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
48468      uri: segment.resolvedUri,
48469      responseType: 'arraybuffer',
48470      headers: segmentXhrHeaders(segment)
48471    });
48472    var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
48473    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
48474    segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
48475    activeXhrs.push(segmentXhr);
48476    return function () {
48477      return abortAll(activeXhrs);
48478    };
48479  }; // Utilities
48480
48481  /**
48482   * Returns the CSS value for the specified property on an element
48483   * using `getComputedStyle`. Firefox has a long-standing issue where
48484   * getComputedStyle() may return null when running in an iframe with
48485   * `display: none`.
48486   *
48487   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
48488   * @param {HTMLElement} el the htmlelement to work on
48489   * @param {string} the proprety to get the style for
48490   */
48491
48492
48493  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
48494    var result = void 0;
48495
48496    if (!el) {
48497      return '';
48498    }
48499
48500    result = window$1.getComputedStyle(el);
48501
48502    if (!result) {
48503      return '';
48504    }
48505
48506    return result[property];
48507  };
48508  /**
48509   * Resuable stable sort function
48510   *
48511   * @param {Playlists} array
48512   * @param {Function} sortFn Different comparators
48513   * @function stableSort
48514   */
48515
48516
48517  var stableSort = function stableSort(array, sortFn) {
48518    var newArray = array.slice();
48519    array.sort(function (left, right) {
48520      var cmp = sortFn(left, right);
48521
48522      if (cmp === 0) {
48523        return newArray.indexOf(left) - newArray.indexOf(right);
48524      }
48525
48526      return cmp;
48527    });
48528  };
48529  /**
48530   * A comparator function to sort two playlist object by bandwidth.
48531   *
48532   * @param {Object} left a media playlist object
48533   * @param {Object} right a media playlist object
48534   * @return {Number} Greater than zero if the bandwidth attribute of
48535   * left is greater than the corresponding attribute of right. Less
48536   * than zero if the bandwidth of right is greater than left and
48537   * exactly zero if the two are equal.
48538   */
48539
48540
48541  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
48542    var leftBandwidth = void 0;
48543    var rightBandwidth = void 0;
48544
48545    if (left.attributes.BANDWIDTH) {
48546      leftBandwidth = left.attributes.BANDWIDTH;
48547    }
48548
48549    leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
48550
48551    if (right.attributes.BANDWIDTH) {
48552      rightBandwidth = right.attributes.BANDWIDTH;
48553    }
48554
48555    rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
48556    return leftBandwidth - rightBandwidth;
48557  };
48558  /**
48559   * A comparator function to sort two playlist object by resolution (width).
48560   * @param {Object} left a media playlist object
48561   * @param {Object} right a media playlist object
48562   * @return {Number} Greater than zero if the resolution.width attribute of
48563   * left is greater than the corresponding attribute of right. Less
48564   * than zero if the resolution.width of right is greater than left and
48565   * exactly zero if the two are equal.
48566   */
48567
48568
48569  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
48570    var leftWidth = void 0;
48571    var rightWidth = void 0;
48572
48573    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
48574      leftWidth = left.attributes.RESOLUTION.width;
48575    }
48576
48577    leftWidth = leftWidth || window$1.Number.MAX_VALUE;
48578
48579    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
48580      rightWidth = right.attributes.RESOLUTION.width;
48581    }
48582
48583    rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
48584    // have the same media dimensions/ resolution
48585
48586    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
48587      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
48588    }
48589
48590    return leftWidth - rightWidth;
48591  };
48592  /**
48593   * Chooses the appropriate media playlist based on bandwidth and player size
48594   *
48595   * @param {Object} master
48596   *        Object representation of the master manifest
48597   * @param {Number} playerBandwidth
48598   *        Current calculated bandwidth of the player
48599   * @param {Number} playerWidth
48600   *        Current width of the player element
48601   * @param {Number} playerHeight
48602   *        Current height of the player element
48603   * @param {Boolean} limitRenditionByPlayerDimensions
48604   *        True if the player width and height should be used during the selection, false otherwise
48605   * @return {Playlist} the highest bitrate playlist less than the
48606   * currently detected bandwidth, accounting for some amount of
48607   * bandwidth variance
48608   */
48609
48610
48611  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
48612    // convert the playlists to an intermediary representation to make comparisons easier
48613    var sortedPlaylistReps = master.playlists.map(function (playlist) {
48614      var width = void 0;
48615      var height = void 0;
48616      var bandwidth = void 0;
48617      width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
48618      height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
48619      bandwidth = playlist.attributes.BANDWIDTH;
48620      bandwidth = bandwidth || window$1.Number.MAX_VALUE;
48621      return {
48622        bandwidth: bandwidth,
48623        width: width,
48624        height: height,
48625        playlist: playlist
48626      };
48627    });
48628    stableSort(sortedPlaylistReps, function (left, right) {
48629      return left.bandwidth - right.bandwidth;
48630    }); // filter out any playlists that have been excluded due to
48631    // incompatible configurations
48632
48633    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
48634      return !Playlist.isIncompatible(rep.playlist);
48635    }); // filter out any playlists that have been disabled manually through the representations
48636    // api or blacklisted temporarily due to playback errors.
48637
48638    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
48639      return Playlist.isEnabled(rep.playlist);
48640    });
48641
48642    if (!enabledPlaylistReps.length) {
48643      // if there are no enabled playlists, then they have all been blacklisted or disabled
48644      // by the user through the representations api. In this case, ignore blacklisting and
48645      // fallback to what the user wants by using playlists the user has not disabled.
48646      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
48647        return !Playlist.isDisabled(rep.playlist);
48648      });
48649    } // filter out any variant that has greater effective bitrate
48650    // than the current estimated bandwidth
48651
48652
48653    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
48654      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
48655    });
48656    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
48657    // and then taking the very first element
48658
48659    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
48660      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
48661    })[0]; // if we're not going to limit renditions by player size, make an early decision.
48662
48663    if (limitRenditionByPlayerDimensions === false) {
48664      var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
48665
48666      return _chosenRep ? _chosenRep.playlist : null;
48667    } // filter out playlists without resolution information
48668
48669
48670    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
48671      return rep.width && rep.height;
48672    }); // sort variants by resolution
48673
48674    stableSort(haveResolution, function (left, right) {
48675      return left.width - right.width;
48676    }); // if we have the exact resolution as the player use it
48677
48678    var resolutionBestRepList = haveResolution.filter(function (rep) {
48679      return rep.width === playerWidth && rep.height === playerHeight;
48680    });
48681    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
48682
48683    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
48684      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
48685    })[0];
48686    var resolutionPlusOneList = void 0;
48687    var resolutionPlusOneSmallest = void 0;
48688    var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
48689    // if there is no match of exact resolution
48690
48691    if (!resolutionBestRep) {
48692      resolutionPlusOneList = haveResolution.filter(function (rep) {
48693        return rep.width > playerWidth || rep.height > playerHeight;
48694      }); // find all the variants have the same smallest resolution
48695
48696      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
48697        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
48698      }); // ensure that we also pick the highest bandwidth variant that
48699      // is just-larger-than the video player
48700
48701      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
48702      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
48703        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
48704      })[0];
48705    } // fallback chain of variants
48706
48707
48708    var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
48709    return chosenRep ? chosenRep.playlist : null;
48710  }; // Playlist Selectors
48711
48712  /**
48713   * Chooses the appropriate media playlist based on the most recent
48714   * bandwidth estimate and the player size.
48715   *
48716   * Expects to be called within the context of an instance of HlsHandler
48717   *
48718   * @return {Playlist} the highest bitrate playlist less than the
48719   * currently detected bandwidth, accounting for some amount of
48720   * bandwidth variance
48721   */
48722
48723
48724  var lastBandwidthSelector = function lastBandwidthSelector() {
48725    return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
48726  };
48727  /**
48728   * Chooses the appropriate media playlist based on the potential to rebuffer
48729   *
48730   * @param {Object} settings
48731   *        Object of information required to use this selector
48732   * @param {Object} settings.master
48733   *        Object representation of the master manifest
48734   * @param {Number} settings.currentTime
48735   *        The current time of the player
48736   * @param {Number} settings.bandwidth
48737   *        Current measured bandwidth
48738   * @param {Number} settings.duration
48739   *        Duration of the media
48740   * @param {Number} settings.segmentDuration
48741   *        Segment duration to be used in round trip time calculations
48742   * @param {Number} settings.timeUntilRebuffer
48743   *        Time left in seconds until the player has to rebuffer
48744   * @param {Number} settings.currentTimeline
48745   *        The current timeline segments are being loaded from
48746   * @param {SyncController} settings.syncController
48747   *        SyncController for determining if we have a sync point for a given playlist
48748   * @return {Object|null}
48749   *         {Object} return.playlist
48750   *         The highest bandwidth playlist with the least amount of rebuffering
48751   *         {Number} return.rebufferingImpact
48752   *         The amount of time in seconds switching to this playlist will rebuffer. A
48753   *         negative value means that switching will cause zero rebuffering.
48754   */
48755
48756
48757  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
48758    var master = settings.master,
48759        currentTime = settings.currentTime,
48760        bandwidth = settings.bandwidth,
48761        duration$$1 = settings.duration,
48762        segmentDuration = settings.segmentDuration,
48763        timeUntilRebuffer = settings.timeUntilRebuffer,
48764        currentTimeline = settings.currentTimeline,
48765        syncController = settings.syncController; // filter out any playlists that have been excluded due to
48766    // incompatible configurations
48767
48768    var compatiblePlaylists = master.playlists.filter(function (playlist) {
48769      return !Playlist.isIncompatible(playlist);
48770    }); // filter out any playlists that have been disabled manually through the representations
48771    // api or blacklisted temporarily due to playback errors.
48772
48773    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
48774
48775    if (!enabledPlaylists.length) {
48776      // if there are no enabled playlists, then they have all been blacklisted or disabled
48777      // by the user through the representations api. In this case, ignore blacklisting and
48778      // fallback to what the user wants by using playlists the user has not disabled.
48779      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
48780        return !Playlist.isDisabled(playlist);
48781      });
48782    }
48783
48784    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
48785    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
48786      var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
48787      // sync request first. This will double the request time
48788
48789      var numRequests = syncPoint ? 1 : 2;
48790      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
48791      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
48792      return {
48793        playlist: playlist,
48794        rebufferingImpact: rebufferingImpact
48795      };
48796    });
48797    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
48798      return estimate.rebufferingImpact <= 0;
48799    }); // Sort by bandwidth DESC
48800
48801    stableSort(noRebufferingPlaylists, function (a, b) {
48802      return comparePlaylistBandwidth(b.playlist, a.playlist);
48803    });
48804
48805    if (noRebufferingPlaylists.length) {
48806      return noRebufferingPlaylists[0];
48807    }
48808
48809    stableSort(rebufferingEstimates, function (a, b) {
48810      return a.rebufferingImpact - b.rebufferingImpact;
48811    });
48812    return rebufferingEstimates[0] || null;
48813  };
48814  /**
48815   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
48816   * one with video.  If no renditions with video exist, return the lowest audio rendition.
48817   *
48818   * Expects to be called within the context of an instance of HlsHandler
48819   *
48820   * @return {Object|null}
48821   *         {Object} return.playlist
48822   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
48823   *         exists pick the lowest audio rendition.
48824   */
48825
48826
48827  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
48828    // filter out any playlists that have been excluded due to
48829    // incompatible configurations or playback errors
48830    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
48831
48832    stableSort(playlists, function (a, b) {
48833      return comparePlaylistBandwidth(a, b);
48834    }); // Parse and assume that playlists with no video codec have no video
48835    // (this is not necessarily true, although it is generally true).
48836    //
48837    // If an entire manifest has no valid videos everything will get filtered
48838    // out.
48839
48840    var playlistsWithVideo = playlists.filter(function (playlist) {
48841      return parseCodecs(playlist.attributes.CODECS).videoCodec;
48842    });
48843    return playlistsWithVideo[0] || null;
48844  };
48845  /**
48846   * Create captions text tracks on video.js if they do not exist
48847   *
48848   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
48849   * @param {Object} tech the video.js tech
48850   * @param {Object} captionStreams the caption streams to create
48851   * @private
48852   */
48853
48854
48855  var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
48856    for (var trackId in captionStreams) {
48857      if (!inbandTextTracks[trackId]) {
48858        tech.trigger({
48859          type: 'usage',
48860          name: 'hls-608'
48861        });
48862        var track = tech.textTracks().getTrackById(trackId);
48863
48864        if (track) {
48865          // Resuse an existing track with a CC# id because this was
48866          // very likely created by videojs-contrib-hls from information
48867          // in the m3u8 for us to use
48868          inbandTextTracks[trackId] = track;
48869        } else {
48870          // Otherwise, create a track with the default `CC#` label and
48871          // without a language
48872          inbandTextTracks[trackId] = tech.addRemoteTextTrack({
48873            kind: 'captions',
48874            id: trackId,
48875            label: trackId
48876          }, false).track;
48877        }
48878      }
48879    }
48880  };
48881
48882  var addCaptionData = function addCaptionData(_ref) {
48883    var inbandTextTracks = _ref.inbandTextTracks,
48884        captionArray = _ref.captionArray,
48885        timestampOffset = _ref.timestampOffset;
48886
48887    if (!captionArray) {
48888      return;
48889    }
48890
48891    var Cue = window.WebKitDataCue || window.VTTCue;
48892    captionArray.forEach(function (caption) {
48893      var track = caption.stream;
48894      var startTime = caption.startTime;
48895      var endTime = caption.endTime;
48896
48897      if (!inbandTextTracks[track]) {
48898        return;
48899      }
48900
48901      startTime += timestampOffset;
48902      endTime += timestampOffset;
48903      inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
48904    });
48905  };
48906  /**
48907   * @file segment-loader.js
48908   */
48909  // in ms
48910
48911
48912  var CHECK_BUFFER_DELAY = 500;
48913  /**
48914   * Determines if we should call endOfStream on the media source based
48915   * on the state of the buffer or if appened segment was the final
48916   * segment in the playlist.
48917   *
48918   * @param {Object} playlist a media playlist object
48919   * @param {Object} mediaSource the MediaSource object
48920   * @param {Number} segmentIndex the index of segment we last appended
48921   * @returns {Boolean} do we need to call endOfStream on the MediaSource
48922   */
48923
48924  var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
48925    if (!playlist || !mediaSource) {
48926      return false;
48927    }
48928
48929    var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
48930    // to read
48931
48932    var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
48933    // so that MediaSources can trigger the `ended` event when it runs out of
48934    // buffered data instead of waiting for me
48935
48936    return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
48937  };
48938
48939  var finite = function finite(num) {
48940    return typeof num === 'number' && isFinite(num);
48941  };
48942
48943  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
48944    // Although these checks should most likely cover non 'main' types, for now it narrows
48945    // the scope of our checks.
48946    if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
48947      return null;
48948    }
48949
48950    if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
48951      return 'Neither audio nor video found in segment.';
48952    }
48953
48954    if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
48955      return 'Only audio found in segment when we expected video.' + ' We can\'t switch to audio only from a stream that had video.' + ' To get rid of this message, please add codec information to the manifest.';
48956    }
48957
48958    if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
48959      return 'Video found in segment when we expected only audio.' + ' We can\'t switch to a stream with video from an audio only stream.' + ' To get rid of this message, please add codec information to the manifest.';
48960    }
48961
48962    return null;
48963  };
48964  /**
48965   * Calculates a time value that is safe to remove from the back buffer without interupting
48966   * playback.
48967   *
48968   * @param {TimeRange} seekable
48969   *        The current seekable range
48970   * @param {Number} currentTime
48971   *        The current time of the player
48972   * @param {Number} targetDuration
48973   *        The target duration of the current playlist
48974   * @return {Number}
48975   *         Time that is safe to remove from the back buffer without interupting playback
48976   */
48977
48978
48979  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
48980    var removeToTime = void 0;
48981
48982    if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
48983      // If we have a seekable range use that as the limit for what can be removed safely
48984      removeToTime = seekable$$1.start(0);
48985    } else {
48986      // otherwise remove anything older than 30 seconds before the current play head
48987      removeToTime = currentTime - 30;
48988    } // Don't allow removing from the buffer within target duration of current time
48989    // to avoid the possibility of removing the GOP currently being played which could
48990    // cause playback stalls.
48991
48992
48993    return Math.min(removeToTime, currentTime - targetDuration);
48994  };
48995
48996  var segmentInfoString = function segmentInfoString(segmentInfo) {
48997    var _segmentInfo$segment = segmentInfo.segment,
48998        start = _segmentInfo$segment.start,
48999        end = _segmentInfo$segment.end,
49000        _segmentInfo$playlist = segmentInfo.playlist,
49001        seq = _segmentInfo$playlist.mediaSequence,
49002        id = _segmentInfo$playlist.id,
49003        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
49004        segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
49005        index = segmentInfo.mediaIndex,
49006        timeline = segmentInfo.timeline;
49007    return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
49008  };
49009  /**
49010   * An object that manages segment loading and appending.
49011   *
49012   * @class SegmentLoader
49013   * @param {Object} options required and optional options
49014   * @extends videojs.EventTarget
49015   */
49016
49017
49018  var SegmentLoader = function (_videojs$EventTarget) {
49019    inherits$2(SegmentLoader, _videojs$EventTarget);
49020
49021    function SegmentLoader(settings) {
49022      classCallCheck$1(this, SegmentLoader); // check pre-conditions
49023
49024      var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
49025
49026      if (!settings) {
49027        throw new TypeError('Initialization settings are required');
49028      }
49029
49030      if (typeof settings.currentTime !== 'function') {
49031        throw new TypeError('No currentTime getter specified');
49032      }
49033
49034      if (!settings.mediaSource) {
49035        throw new TypeError('No MediaSource specified');
49036      } // public properties
49037
49038
49039      _this.bandwidth = settings.bandwidth;
49040      _this.throughput = {
49041        rate: 0,
49042        count: 0
49043      };
49044      _this.roundTrip = NaN;
49045
49046      _this.resetStats_();
49047
49048      _this.mediaIndex = null; // private settings
49049
49050      _this.hasPlayed_ = settings.hasPlayed;
49051      _this.currentTime_ = settings.currentTime;
49052      _this.seekable_ = settings.seekable;
49053      _this.seeking_ = settings.seeking;
49054      _this.duration_ = settings.duration;
49055      _this.mediaSource_ = settings.mediaSource;
49056      _this.hls_ = settings.hls;
49057      _this.loaderType_ = settings.loaderType;
49058      _this.startingMedia_ = void 0;
49059      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
49060      _this.goalBufferLength_ = settings.goalBufferLength;
49061      _this.sourceType_ = settings.sourceType;
49062      _this.inbandTextTracks_ = settings.inbandTextTracks;
49063      _this.state_ = 'INIT'; // private instance variables
49064
49065      _this.checkBufferTimeout_ = null;
49066      _this.error_ = void 0;
49067      _this.currentTimeline_ = -1;
49068      _this.pendingSegment_ = null;
49069      _this.mimeType_ = null;
49070      _this.sourceUpdater_ = null;
49071      _this.xhrOptions_ = null; // Fragmented mp4 playback
49072
49073      _this.activeInitSegmentId_ = null;
49074      _this.initSegments_ = {}; // HLSe playback
49075
49076      _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
49077      _this.keyCache_ = {}; // Fmp4 CaptionParser
49078
49079      if (_this.loaderType_ === 'main') {
49080        _this.captionParser_ = new captionParser();
49081      } else {
49082        _this.captionParser_ = null;
49083      }
49084
49085      _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
49086      // between a time in the display time and a segment index within
49087      // a playlist
49088
49089      _this.syncController_ = settings.syncController;
49090      _this.syncPoint_ = {
49091        segmentIndex: 0,
49092        time: 0
49093      };
49094
49095      _this.syncController_.on('syncinfoupdate', function () {
49096        return _this.trigger('syncinfoupdate');
49097      });
49098
49099      _this.mediaSource_.addEventListener('sourceopen', function () {
49100        return _this.ended_ = false;
49101      }); // ...for determining the fetch location
49102
49103
49104      _this.fetchAtBuffer_ = false;
49105      _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
49106      Object.defineProperty(_this, 'state', {
49107        get: function get$$1() {
49108          return this.state_;
49109        },
49110        set: function set$$1(newState) {
49111          if (newState !== this.state_) {
49112            this.logger_(this.state_ + ' -> ' + newState);
49113            this.state_ = newState;
49114          }
49115        }
49116      });
49117      return _this;
49118    }
49119    /**
49120     * reset all of our media stats
49121     *
49122     * @private
49123     */
49124
49125
49126    createClass$1(SegmentLoader, [{
49127      key: 'resetStats_',
49128      value: function resetStats_() {
49129        this.mediaBytesTransferred = 0;
49130        this.mediaRequests = 0;
49131        this.mediaRequestsAborted = 0;
49132        this.mediaRequestsTimedout = 0;
49133        this.mediaRequestsErrored = 0;
49134        this.mediaTransferDuration = 0;
49135        this.mediaSecondsLoaded = 0;
49136      }
49137      /**
49138       * dispose of the SegmentLoader and reset to the default state
49139       */
49140
49141    }, {
49142      key: 'dispose',
49143      value: function dispose() {
49144        this.state = 'DISPOSED';
49145        this.pause();
49146        this.abort_();
49147
49148        if (this.sourceUpdater_) {
49149          this.sourceUpdater_.dispose();
49150        }
49151
49152        this.resetStats_();
49153
49154        if (this.captionParser_) {
49155          this.captionParser_.reset();
49156        }
49157      }
49158      /**
49159       * abort anything that is currently doing on with the SegmentLoader
49160       * and reset to a default state
49161       */
49162
49163    }, {
49164      key: 'abort',
49165      value: function abort() {
49166        if (this.state !== 'WAITING') {
49167          if (this.pendingSegment_) {
49168            this.pendingSegment_ = null;
49169          }
49170
49171          return;
49172        }
49173
49174        this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
49175        // since we are no longer "waiting" on any requests. XHR callback is not always run
49176        // when the request is aborted. This will prevent the loader from being stuck in the
49177        // WAITING state indefinitely.
49178
49179        this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
49180        // next segment
49181
49182        if (!this.paused()) {
49183          this.monitorBuffer_();
49184        }
49185      }
49186      /**
49187       * abort all pending xhr requests and null any pending segements
49188       *
49189       * @private
49190       */
49191
49192    }, {
49193      key: 'abort_',
49194      value: function abort_() {
49195        if (this.pendingSegment_) {
49196          this.pendingSegment_.abortRequests();
49197        } // clear out the segment being processed
49198
49199
49200        this.pendingSegment_ = null;
49201      }
49202      /**
49203       * set an error on the segment loader and null out any pending segements
49204       *
49205       * @param {Error} error the error to set on the SegmentLoader
49206       * @return {Error} the error that was set or that is currently set
49207       */
49208
49209    }, {
49210      key: 'error',
49211      value: function error(_error) {
49212        if (typeof _error !== 'undefined') {
49213          this.error_ = _error;
49214        }
49215
49216        this.pendingSegment_ = null;
49217        return this.error_;
49218      }
49219    }, {
49220      key: 'endOfStream',
49221      value: function endOfStream() {
49222        this.ended_ = true;
49223        this.pause();
49224        this.trigger('ended');
49225      }
49226      /**
49227       * Indicates which time ranges are buffered
49228       *
49229       * @return {TimeRange}
49230       *         TimeRange object representing the current buffered ranges
49231       */
49232
49233    }, {
49234      key: 'buffered_',
49235      value: function buffered_() {
49236        if (!this.sourceUpdater_) {
49237          return videojs$1.createTimeRanges();
49238        }
49239
49240        return this.sourceUpdater_.buffered();
49241      }
49242      /**
49243       * Gets and sets init segment for the provided map
49244       *
49245       * @param {Object} map
49246       *        The map object representing the init segment to get or set
49247       * @param {Boolean=} set
49248       *        If true, the init segment for the provided map should be saved
49249       * @return {Object}
49250       *         map object for desired init segment
49251       */
49252
49253    }, {
49254      key: 'initSegment',
49255      value: function initSegment(map) {
49256        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
49257
49258        if (!map) {
49259          return null;
49260        }
49261
49262        var id = initSegmentId(map);
49263        var storedMap = this.initSegments_[id];
49264
49265        if (set$$1 && !storedMap && map.bytes) {
49266          this.initSegments_[id] = storedMap = {
49267            resolvedUri: map.resolvedUri,
49268            byterange: map.byterange,
49269            bytes: map.bytes,
49270            timescales: map.timescales,
49271            videoTrackIds: map.videoTrackIds
49272          };
49273        }
49274
49275        return storedMap || map;
49276      }
49277      /**
49278       * Gets and sets key for the provided key
49279       *
49280       * @param {Object} key
49281       *        The key object representing the key to get or set
49282       * @param {Boolean=} set
49283       *        If true, the key for the provided key should be saved
49284       * @return {Object}
49285       *         Key object for desired key
49286       */
49287
49288    }, {
49289      key: 'segmentKey',
49290      value: function segmentKey(key) {
49291        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
49292
49293        if (!key) {
49294          return null;
49295        }
49296
49297        var id = segmentKeyId(key);
49298        var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
49299        // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
49300
49301        if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) {
49302          this.keyCache_[id] = storedKey = {
49303            resolvedUri: key.resolvedUri,
49304            bytes: key.bytes
49305          };
49306        }
49307
49308        var result = {
49309          resolvedUri: (storedKey || key).resolvedUri
49310        };
49311
49312        if (storedKey) {
49313          result.bytes = storedKey.bytes;
49314        }
49315
49316        return result;
49317      }
49318      /**
49319       * Returns true if all configuration required for loading is present, otherwise false.
49320       *
49321       * @return {Boolean} True if the all configuration is ready for loading
49322       * @private
49323       */
49324
49325    }, {
49326      key: 'couldBeginLoading_',
49327      value: function couldBeginLoading_() {
49328        return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
49329        // source updater or being in the INIT state with a mimeType is enough
49330        // to say we have all the needed configuration to start loading.
49331        this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
49332      }
49333      /**
49334       * load a playlist and start to fill the buffer
49335       */
49336
49337    }, {
49338      key: 'load',
49339      value: function load() {
49340        // un-pause
49341        this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
49342        // specified
49343
49344        if (!this.playlist_) {
49345          return;
49346        } // not sure if this is the best place for this
49347
49348
49349        this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
49350
49351        if (this.state === 'INIT' && this.couldBeginLoading_()) {
49352          return this.init_();
49353        } // if we're in the middle of processing a segment already, don't
49354        // kick off an additional segment request
49355
49356
49357        if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
49358          return;
49359        }
49360
49361        this.state = 'READY';
49362      }
49363      /**
49364       * Once all the starting parameters have been specified, begin
49365       * operation. This method should only be invoked from the INIT
49366       * state.
49367       *
49368       * @private
49369       */
49370
49371    }, {
49372      key: 'init_',
49373      value: function init_() {
49374        this.state = 'READY';
49375        this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
49376        this.resetEverything();
49377        return this.monitorBuffer_();
49378      }
49379      /**
49380       * set a playlist on the segment loader
49381       *
49382       * @param {PlaylistLoader} media the playlist to set on the segment loader
49383       */
49384
49385    }, {
49386      key: 'playlist',
49387      value: function playlist(newPlaylist) {
49388        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
49389
49390        if (!newPlaylist) {
49391          return;
49392        }
49393
49394        var oldPlaylist = this.playlist_;
49395        var segmentInfo = this.pendingSegment_;
49396        this.playlist_ = newPlaylist;
49397        this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
49398        // is always our zero-time so force a sync update each time the playlist
49399        // is refreshed from the server
49400
49401        if (!this.hasPlayed_()) {
49402          newPlaylist.syncInfo = {
49403            mediaSequence: newPlaylist.mediaSequence,
49404            time: 0
49405          };
49406        }
49407
49408        var oldId = null;
49409
49410        if (oldPlaylist) {
49411          if (oldPlaylist.id) {
49412            oldId = oldPlaylist.id;
49413          } else if (oldPlaylist.uri) {
49414            oldId = oldPlaylist.uri;
49415          }
49416        }
49417
49418        this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
49419        // in LIVE, we always want to update with new playlists (including refreshes)
49420
49421        this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
49422        // buffering now
49423
49424        if (this.state === 'INIT' && this.couldBeginLoading_()) {
49425          return this.init_();
49426        }
49427
49428        if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
49429          if (this.mediaIndex !== null) {
49430            // we must "resync" the segment loader when we switch renditions and
49431            // the segment loader is already synced to the previous rendition
49432            this.resyncLoader();
49433          } // the rest of this function depends on `oldPlaylist` being defined
49434
49435
49436          return;
49437        } // we reloaded the same playlist so we are in a live scenario
49438        // and we will likely need to adjust the mediaIndex
49439
49440
49441        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
49442        this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
49443        // this is important because we can abort a request and this value must be
49444        // equal to the last appended mediaIndex
49445
49446        if (this.mediaIndex !== null) {
49447          this.mediaIndex -= mediaSequenceDiff;
49448        } // update the mediaIndex on the SegmentInfo object
49449        // this is important because we will update this.mediaIndex with this value
49450        // in `handleUpdateEnd_` after the segment has been successfully appended
49451
49452
49453        if (segmentInfo) {
49454          segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
49455          // saved for the new playlist's segment, however, if the segment fell off the
49456          // playlist, we can leave the old reference and just lose the timing info
49457
49458          if (segmentInfo.mediaIndex >= 0) {
49459            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
49460          }
49461        }
49462
49463        this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
49464      }
49465      /**
49466       * Prevent the loader from fetching additional segments. If there
49467       * is a segment request outstanding, it will finish processing
49468       * before the loader halts. A segment loader can be unpaused by
49469       * calling load().
49470       */
49471
49472    }, {
49473      key: 'pause',
49474      value: function pause() {
49475        if (this.checkBufferTimeout_) {
49476          window$1.clearTimeout(this.checkBufferTimeout_);
49477          this.checkBufferTimeout_ = null;
49478        }
49479      }
49480      /**
49481       * Returns whether the segment loader is fetching additional
49482       * segments when given the opportunity. This property can be
49483       * modified through calls to pause() and load().
49484       */
49485
49486    }, {
49487      key: 'paused',
49488      value: function paused() {
49489        return this.checkBufferTimeout_ === null;
49490      }
49491      /**
49492       * create/set the following mimetype on the SourceBuffer through a
49493       * SourceUpdater
49494       *
49495       * @param {String} mimeType the mime type string to use
49496       * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
49497       * is added to the media source
49498       */
49499
49500    }, {
49501      key: 'mimeType',
49502      value: function mimeType(_mimeType, sourceBufferEmitter) {
49503        if (this.mimeType_) {
49504          return;
49505        }
49506
49507        this.mimeType_ = _mimeType;
49508        this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
49509        // buffering now
49510
49511        if (this.state === 'INIT' && this.couldBeginLoading_()) {
49512          this.init_();
49513        }
49514      }
49515      /**
49516       * Delete all the buffered data and reset the SegmentLoader
49517       * @param {Function} [done] an optional callback to be executed when the remove
49518       * operation is complete
49519       */
49520
49521    }, {
49522      key: 'resetEverything',
49523      value: function resetEverything(done) {
49524        this.ended_ = false;
49525        this.resetLoader();
49526        this.remove(0, this.duration_(), done); // clears fmp4 captions
49527
49528        if (this.captionParser_) {
49529          this.captionParser_.clearAllCaptions();
49530        }
49531
49532        this.trigger('reseteverything');
49533      }
49534      /**
49535       * Force the SegmentLoader to resync and start loading around the currentTime instead
49536       * of starting at the end of the buffer
49537       *
49538       * Useful for fast quality changes
49539       */
49540
49541    }, {
49542      key: 'resetLoader',
49543      value: function resetLoader() {
49544        this.fetchAtBuffer_ = false;
49545        this.resyncLoader();
49546      }
49547      /**
49548       * Force the SegmentLoader to restart synchronization and make a conservative guess
49549       * before returning to the simple walk-forward method
49550       */
49551
49552    }, {
49553      key: 'resyncLoader',
49554      value: function resyncLoader() {
49555        this.mediaIndex = null;
49556        this.syncPoint_ = null;
49557        this.abort();
49558      }
49559      /**
49560       * Remove any data in the source buffer between start and end times
49561       * @param {Number} start - the start time of the region to remove from the buffer
49562       * @param {Number} end - the end time of the region to remove from the buffer
49563       * @param {Function} [done] - an optional callback to be executed when the remove
49564       * operation is complete
49565       */
49566
49567    }, {
49568      key: 'remove',
49569      value: function remove(start, end, done) {
49570        if (this.sourceUpdater_) {
49571          this.sourceUpdater_.remove(start, end, done);
49572        }
49573
49574        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
49575
49576        if (this.inbandTextTracks_) {
49577          for (var id in this.inbandTextTracks_) {
49578            removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
49579          }
49580        }
49581      }
49582      /**
49583       * (re-)schedule monitorBufferTick_ to run as soon as possible
49584       *
49585       * @private
49586       */
49587
49588    }, {
49589      key: 'monitorBuffer_',
49590      value: function monitorBuffer_() {
49591        if (this.checkBufferTimeout_) {
49592          window$1.clearTimeout(this.checkBufferTimeout_);
49593        }
49594
49595        this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
49596      }
49597      /**
49598       * As long as the SegmentLoader is in the READY state, periodically
49599       * invoke fillBuffer_().
49600       *
49601       * @private
49602       */
49603
49604    }, {
49605      key: 'monitorBufferTick_',
49606      value: function monitorBufferTick_() {
49607        if (this.state === 'READY') {
49608          this.fillBuffer_();
49609        }
49610
49611        if (this.checkBufferTimeout_) {
49612          window$1.clearTimeout(this.checkBufferTimeout_);
49613        }
49614
49615        this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
49616      }
49617      /**
49618       * fill the buffer with segements unless the sourceBuffers are
49619       * currently updating
49620       *
49621       * Note: this function should only ever be called by monitorBuffer_
49622       * and never directly
49623       *
49624       * @private
49625       */
49626
49627    }, {
49628      key: 'fillBuffer_',
49629      value: function fillBuffer_() {
49630        if (this.sourceUpdater_.updating()) {
49631          return;
49632        }
49633
49634        if (!this.syncPoint_) {
49635          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
49636        } // see if we need to begin loading immediately
49637
49638
49639        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
49640
49641        if (!segmentInfo) {
49642          return;
49643        }
49644
49645        if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
49646          this.endOfStream();
49647          return;
49648        }
49649
49650        if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
49651          return;
49652        } // We will need to change timestampOffset of the sourceBuffer if:
49653        // - The segment.timeline !== this.currentTimeline
49654        //   (we are crossing a discontinuity somehow)
49655        // - The "timestampOffset" for the start of this segment is less than
49656        //   the currently set timestampOffset
49657        // Also, clear captions if we are crossing a discontinuity boundary
49658        // Previously, we changed the timestampOffset if the start of this segment
49659        // is less than the currently set timestampOffset but this isn't wanted
49660        // as it can produce bad behavior, especially around long running
49661        // live streams
49662
49663
49664        if (segmentInfo.timeline !== this.currentTimeline_) {
49665          this.syncController_.reset();
49666          segmentInfo.timestampOffset = segmentInfo.startOfSegment;
49667
49668          if (this.captionParser_) {
49669            this.captionParser_.clearAllCaptions();
49670          }
49671        }
49672
49673        this.loadSegment_(segmentInfo);
49674      }
49675      /**
49676       * Determines if this segment loader is at the end of it's stream.
49677       *
49678       * @param {Number} mediaIndex the index of segment we last appended
49679       * @param {Object} [playlist=this.playlist_] a media playlist object
49680       * @returns {Boolean} true if at end of stream, false otherwise.
49681       */
49682
49683    }, {
49684      key: 'isEndOfStream_',
49685      value: function isEndOfStream_(mediaIndex) {
49686        var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
49687        return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
49688      }
49689      /**
49690       * Determines what segment request should be made, given current playback
49691       * state.
49692       *
49693       * @param {TimeRanges} buffered - the state of the buffer
49694       * @param {Object} playlist - the playlist object to fetch segments from
49695       * @param {Number} mediaIndex - the previous mediaIndex fetched or null
49696       * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
49697       * @param {Number} currentTime - the playback position in seconds
49698       * @param {Object} syncPoint - a segment info object that describes the
49699       * @returns {Object} a segment request object that describes the segment to load
49700       */
49701
49702    }, {
49703      key: 'checkBuffer_',
49704      value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
49705        var lastBufferedEnd = 0;
49706        var startOfSegment = void 0;
49707
49708        if (buffered.length) {
49709          lastBufferedEnd = buffered.end(buffered.length - 1);
49710        }
49711
49712        var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
49713
49714        if (!playlist.segments.length) {
49715          return null;
49716        } // if there is plenty of content buffered, and the video has
49717        // been played before relax for awhile
49718
49719
49720        if (bufferedTime >= this.goalBufferLength_()) {
49721          return null;
49722        } // if the video has not yet played once, and we already have
49723        // one segment downloaded do nothing
49724
49725
49726        if (!hasPlayed && bufferedTime >= 1) {
49727          return null;
49728        } // When the syncPoint is null, there is no way of determining a good
49729        // conservative segment index to fetch from
49730        // The best thing to do here is to get the kind of sync-point data by
49731        // making a request
49732
49733
49734        if (syncPoint === null) {
49735          mediaIndex = this.getSyncSegmentCandidate_(playlist);
49736          return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
49737        } // Under normal playback conditions fetching is a simple walk forward
49738
49739
49740        if (mediaIndex !== null) {
49741          var segment = playlist.segments[mediaIndex];
49742          startOfSegment = lastBufferedEnd;
49743          return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
49744        } // There is a sync-point but the lack of a mediaIndex indicates that
49745        // we need to make a good conservative guess about which segment to
49746        // fetch
49747
49748
49749        if (this.fetchAtBuffer_) {
49750          // Find the segment containing the end of the buffer
49751          var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
49752          mediaIndex = mediaSourceInfo.mediaIndex;
49753          startOfSegment = mediaSourceInfo.startTime;
49754        } else {
49755          // Find the segment containing currentTime
49756          var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
49757
49758          mediaIndex = _mediaSourceInfo.mediaIndex;
49759          startOfSegment = _mediaSourceInfo.startTime;
49760        }
49761
49762        return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
49763      }
49764      /**
49765       * The segment loader has no recourse except to fetch a segment in the
49766       * current playlist and use the internal timestamps in that segment to
49767       * generate a syncPoint. This function returns a good candidate index
49768       * for that process.
49769       *
49770       * @param {Object} playlist - the playlist object to look for a
49771       * @returns {Number} An index of a segment from the playlist to load
49772       */
49773
49774    }, {
49775      key: 'getSyncSegmentCandidate_',
49776      value: function getSyncSegmentCandidate_(playlist) {
49777        var _this2 = this;
49778
49779        if (this.currentTimeline_ === -1) {
49780          return 0;
49781        }
49782
49783        var segmentIndexArray = playlist.segments.map(function (s, i) {
49784          return {
49785            timeline: s.timeline,
49786            segmentIndex: i
49787          };
49788        }).filter(function (s) {
49789          return s.timeline === _this2.currentTimeline_;
49790        });
49791
49792        if (segmentIndexArray.length) {
49793          return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
49794        }
49795
49796        return Math.max(playlist.segments.length - 1, 0);
49797      }
49798    }, {
49799      key: 'generateSegmentInfo_',
49800      value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
49801        if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
49802          return null;
49803        }
49804
49805        var segment = playlist.segments[mediaIndex];
49806        return {
49807          requestId: 'segment-loader-' + Math.random(),
49808          // resolve the segment URL relative to the playlist
49809          uri: segment.resolvedUri,
49810          // the segment's mediaIndex at the time it was requested
49811          mediaIndex: mediaIndex,
49812          // whether or not to update the SegmentLoader's state with this
49813          // segment's mediaIndex
49814          isSyncRequest: isSyncRequest,
49815          startOfSegment: startOfSegment,
49816          // the segment's playlist
49817          playlist: playlist,
49818          // unencrypted bytes of the segment
49819          bytes: null,
49820          // when a key is defined for this segment, the encrypted bytes
49821          encryptedBytes: null,
49822          // The target timestampOffset for this segment when we append it
49823          // to the source buffer
49824          timestampOffset: null,
49825          // The timeline that the segment is in
49826          timeline: segment.timeline,
49827          // The expected duration of the segment in seconds
49828          duration: segment.duration,
49829          // retain the segment in case the playlist updates while doing an async process
49830          segment: segment
49831        };
49832      }
49833      /**
49834       * Determines if the network has enough bandwidth to complete the current segment
49835       * request in a timely manner. If not, the request will be aborted early and bandwidth
49836       * updated to trigger a playlist switch.
49837       *
49838       * @param {Object} stats
49839       *        Object containing stats about the request timing and size
49840       * @return {Boolean} True if the request was aborted, false otherwise
49841       * @private
49842       */
49843
49844    }, {
49845      key: 'abortRequestEarly_',
49846      value: function abortRequestEarly_(stats) {
49847        if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
49848        // TODO: Replace using timeout with a boolean indicating whether this playlist is
49849        //       the lowestEnabledRendition.
49850        !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
49851        !this.playlist_.attributes.BANDWIDTH) {
49852          return false;
49853        } // Wait at least 1 second since the first byte of data has been received before
49854        // using the calculated bandwidth from the progress event to allow the bitrate
49855        // to stabilize
49856
49857
49858        if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
49859          return false;
49860        }
49861
49862        var currentTime = this.currentTime_();
49863        var measuredBandwidth = stats.bandwidth;
49864        var segmentDuration = this.pendingSegment_.duration;
49865        var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
49866        // if we are only left with less than 1 second when the request completes.
49867        // A negative timeUntilRebuffering indicates we are already rebuffering
49868
49869        var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
49870        // is larger than the estimated time until the player runs out of forward buffer
49871
49872        if (requestTimeRemaining <= timeUntilRebuffer$$1) {
49873          return false;
49874        }
49875
49876        var switchCandidate = minRebufferMaxBandwidthSelector({
49877          master: this.hls_.playlists.master,
49878          currentTime: currentTime,
49879          bandwidth: measuredBandwidth,
49880          duration: this.duration_(),
49881          segmentDuration: segmentDuration,
49882          timeUntilRebuffer: timeUntilRebuffer$$1,
49883          currentTimeline: this.currentTimeline_,
49884          syncController: this.syncController_
49885        });
49886
49887        if (!switchCandidate) {
49888          return;
49889        }
49890
49891        var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
49892        var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
49893        var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
49894        // potential round trip time of the new request so that we are not too aggressive
49895        // with switching to a playlist that might save us a fraction of a second.
49896
49897        if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
49898          minimumTimeSaving = 1;
49899        }
49900
49901        if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
49902          return false;
49903        } // set the bandwidth to that of the desired playlist being sure to scale by
49904        // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
49905        // don't trigger a bandwidthupdate as the bandwidth is artifial
49906
49907
49908        this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
49909        this.abort();
49910        this.trigger('earlyabort');
49911        return true;
49912      }
49913      /**
49914       * XHR `progress` event handler
49915       *
49916       * @param {Event}
49917       *        The XHR `progress` event
49918       * @param {Object} simpleSegment
49919       *        A simplified segment object copy
49920       * @private
49921       */
49922
49923    }, {
49924      key: 'handleProgress_',
49925      value: function handleProgress_(event, simpleSegment) {
49926        if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
49927          return;
49928        }
49929
49930        this.trigger('progress');
49931      }
49932      /**
49933       * load a specific segment from a request into the buffer
49934       *
49935       * @private
49936       */
49937
49938    }, {
49939      key: 'loadSegment_',
49940      value: function loadSegment_(segmentInfo) {
49941        this.state = 'WAITING';
49942        this.pendingSegment_ = segmentInfo;
49943        this.trimBackBuffer_(segmentInfo);
49944        segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
49945        this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
49946      }
49947      /**
49948       * trim the back buffer so that we don't have too much data
49949       * in the source buffer
49950       *
49951       * @private
49952       *
49953       * @param {Object} segmentInfo - the current segment
49954       */
49955
49956    }, {
49957      key: 'trimBackBuffer_',
49958      value: function trimBackBuffer_(segmentInfo) {
49959        var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
49960        // buffer and a very conservative "garbage collector"
49961        // We manually clear out the old buffer to ensure
49962        // we don't trigger the QuotaExceeded error
49963        // on the source buffer during subsequent appends
49964
49965        if (removeToTime > 0) {
49966          this.remove(0, removeToTime);
49967        }
49968      }
49969      /**
49970       * created a simplified copy of the segment object with just the
49971       * information necessary to perform the XHR and decryption
49972       *
49973       * @private
49974       *
49975       * @param {Object} segmentInfo - the current segment
49976       * @returns {Object} a simplified segment object copy
49977       */
49978
49979    }, {
49980      key: 'createSimplifiedSegmentObj_',
49981      value: function createSimplifiedSegmentObj_(segmentInfo) {
49982        var segment = segmentInfo.segment;
49983        var simpleSegment = {
49984          resolvedUri: segment.resolvedUri,
49985          byterange: segment.byterange,
49986          requestId: segmentInfo.requestId
49987        };
49988
49989        if (segment.key) {
49990          // if the media sequence is greater than 2^32, the IV will be incorrect
49991          // assuming 10s segments, that would be about 1300 years
49992          var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
49993          simpleSegment.key = this.segmentKey(segment.key);
49994          simpleSegment.key.iv = iv;
49995        }
49996
49997        if (segment.map) {
49998          simpleSegment.map = this.initSegment(segment.map);
49999        }
50000
50001        return simpleSegment;
50002      }
50003      /**
50004       * Handle the callback from the segmentRequest function and set the
50005       * associated SegmentLoader state and errors if necessary
50006       *
50007       * @private
50008       */
50009
50010    }, {
50011      key: 'segmentRequestFinished_',
50012      value: function segmentRequestFinished_(error, simpleSegment) {
50013        // every request counts as a media request even if it has been aborted
50014        // or canceled due to a timeout
50015        this.mediaRequests += 1;
50016
50017        if (simpleSegment.stats) {
50018          this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
50019          this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
50020        } // The request was aborted and the SegmentLoader has already been reset
50021
50022
50023        if (!this.pendingSegment_) {
50024          this.mediaRequestsAborted += 1;
50025          return;
50026        } // the request was aborted and the SegmentLoader has already started
50027        // another request. this can happen when the timeout for an aborted
50028        // request triggers due to a limitation in the XHR library
50029        // do not count this as any sort of request or we risk double-counting
50030
50031
50032        if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
50033          return;
50034        } // an error occurred from the active pendingSegment_ so reset everything
50035
50036
50037        if (error) {
50038          this.pendingSegment_ = null;
50039          this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
50040          // this is not a true error condition and nothing corrective needs
50041          // to be done
50042
50043          if (error.code === REQUEST_ERRORS.ABORTED) {
50044            this.mediaRequestsAborted += 1;
50045            return;
50046          }
50047
50048          this.pause(); // the error is really just that at least one of the requests timed-out
50049          // set the bandwidth to a very low value and trigger an ABR switch to
50050          // take emergency action
50051
50052          if (error.code === REQUEST_ERRORS.TIMEOUT) {
50053            this.mediaRequestsTimedout += 1;
50054            this.bandwidth = 1;
50055            this.roundTrip = NaN;
50056            this.trigger('bandwidthupdate');
50057            return;
50058          } // if control-flow has arrived here, then the error is real
50059          // emit an error event to blacklist the current playlist
50060
50061
50062          this.mediaRequestsErrored += 1;
50063          this.error(error);
50064          this.trigger('error');
50065          return;
50066        } // the response was a success so set any bandwidth stats the request
50067        // generated for ABR purposes
50068
50069
50070        this.bandwidth = simpleSegment.stats.bandwidth;
50071        this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
50072        // to the initSegment cache
50073
50074        if (simpleSegment.map) {
50075          simpleSegment.map = this.initSegment(simpleSegment.map, true);
50076        } // if this request included a segment key, save that data in the cache
50077
50078
50079        if (simpleSegment.key) {
50080          this.segmentKey(simpleSegment.key, true);
50081        }
50082
50083        this.processSegmentResponse_(simpleSegment);
50084      }
50085      /**
50086       * Move any important data from the simplified segment object
50087       * back to the real segment object for future phases
50088       *
50089       * @private
50090       */
50091
50092    }, {
50093      key: 'processSegmentResponse_',
50094      value: function processSegmentResponse_(simpleSegment) {
50095        var segmentInfo = this.pendingSegment_;
50096        segmentInfo.bytes = simpleSegment.bytes;
50097
50098        if (simpleSegment.map) {
50099          segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
50100        }
50101
50102        segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
50103
50104        if (simpleSegment.fmp4Captions) {
50105          createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
50106          addCaptionData({
50107            inbandTextTracks: this.inbandTextTracks_,
50108            captionArray: simpleSegment.fmp4Captions,
50109            // fmp4s will not have a timestamp offset
50110            timestampOffset: 0
50111          }); // Reset stored captions since we added parsed
50112          // captions to a text track at this point
50113
50114          if (this.captionParser_) {
50115            this.captionParser_.clearParsedCaptions();
50116          }
50117        }
50118
50119        this.handleSegment_();
50120      }
50121      /**
50122       * append a decrypted segement to the SourceBuffer through a SourceUpdater
50123       *
50124       * @private
50125       */
50126
50127    }, {
50128      key: 'handleSegment_',
50129      value: function handleSegment_() {
50130        var _this3 = this;
50131
50132        if (!this.pendingSegment_) {
50133          this.state = 'READY';
50134          return;
50135        }
50136
50137        var segmentInfo = this.pendingSegment_;
50138        var segment = segmentInfo.segment;
50139        var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
50140        // dealing with. Although we're maintaining extra state, it helps to preserve the
50141        // separation of segment loader from the actual source buffers.
50142
50143        if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
50144        // certain that all streams will provide it.
50145        timingInfo.containsAudio || timingInfo.containsVideo)) {
50146          this.startingMedia_ = {
50147            containsAudio: timingInfo.containsAudio,
50148            containsVideo: timingInfo.containsVideo
50149          };
50150        }
50151
50152        var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
50153
50154        if (illegalMediaSwitchError) {
50155          this.error({
50156            message: illegalMediaSwitchError,
50157            blacklistDuration: Infinity
50158          });
50159          this.trigger('error');
50160          return;
50161        }
50162
50163        if (segmentInfo.isSyncRequest) {
50164          this.trigger('syncinfoupdate');
50165          this.pendingSegment_ = null;
50166          this.state = 'READY';
50167          return;
50168        }
50169
50170        if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
50171          // Subtract any difference between the PTS and DTS times of the first frame
50172          // from the timeStampOffset (which currently equals the buffered.end) to prevent
50173          // creating any gaps in the buffer
50174          if (timingInfo && timingInfo.segmentTimestampInfo) {
50175            var ptsStartTime = timingInfo.segmentTimestampInfo[0].ptsTime;
50176            var dtsStartTime = timingInfo.segmentTimestampInfo[0].dtsTime;
50177            segmentInfo.timestampOffset -= ptsStartTime - dtsStartTime;
50178          }
50179
50180          this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
50181
50182          this.trigger('timestampoffset');
50183        }
50184
50185        var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
50186
50187        if (timelineMapping !== null) {
50188          this.trigger({
50189            type: 'segmenttimemapping',
50190            mapping: timelineMapping
50191          });
50192        }
50193
50194        this.state = 'APPENDING'; // if the media initialization segment is changing, append it
50195        // before the content segment
50196
50197        if (segment.map) {
50198          var initId = initSegmentId(segment.map);
50199
50200          if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
50201            var initSegment = this.initSegment(segment.map);
50202            this.sourceUpdater_.appendBuffer({
50203              bytes: initSegment.bytes
50204            }, function () {
50205              _this3.activeInitSegmentId_ = initId;
50206            });
50207          }
50208        }
50209
50210        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
50211
50212        if (typeof segment.start === 'number' && typeof segment.end === 'number') {
50213          this.mediaSecondsLoaded += segment.end - segment.start;
50214        } else {
50215          this.mediaSecondsLoaded += segment.duration;
50216        }
50217
50218        this.logger_(segmentInfoString(segmentInfo));
50219        this.sourceUpdater_.appendBuffer({
50220          bytes: segmentInfo.bytes,
50221          videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
50222        }, this.handleUpdateEnd_.bind(this));
50223      }
50224    }, {
50225      key: 'handleVideoSegmentTimingInfo_',
50226      value: function handleVideoSegmentTimingInfo_(requestId, event) {
50227        if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
50228          return;
50229        }
50230
50231        var segment = this.pendingSegment_.segment;
50232
50233        if (!segment.videoTimingInfo) {
50234          segment.videoTimingInfo = {};
50235        }
50236
50237        segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
50238        segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
50239        segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
50240
50241        segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
50242      }
50243      /**
50244       * callback to run when appendBuffer is finished. detects if we are
50245       * in a good state to do things with the data we got, or if we need
50246       * to wait for more
50247       *
50248       * @private
50249       */
50250
50251    }, {
50252      key: 'handleUpdateEnd_',
50253      value: function handleUpdateEnd_() {
50254        if (!this.pendingSegment_) {
50255          this.state = 'READY';
50256
50257          if (!this.paused()) {
50258            this.monitorBuffer_();
50259          }
50260
50261          return;
50262        }
50263
50264        var segmentInfo = this.pendingSegment_;
50265        var segment = segmentInfo.segment;
50266        var isWalkingForward = this.mediaIndex !== null;
50267        this.pendingSegment_ = null;
50268        this.recordThroughput_(segmentInfo);
50269        this.addSegmentMetadataCue_(segmentInfo);
50270        this.state = 'READY';
50271        this.mediaIndex = segmentInfo.mediaIndex;
50272        this.fetchAtBuffer_ = true;
50273        this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
50274        // the following conditional otherwise it may consider this a bad "guess"
50275        // and attempt to resync when the post-update seekable window and live
50276        // point would mean that this was the perfect segment to fetch
50277
50278        this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
50279        // the currentTime_ that means that our conservative guess was too conservative.
50280        // In that case, reset the loader state so that we try to use any information gained
50281        // from the previous request to create a new, more accurate, sync-point.
50282
50283        if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
50284          this.resetEverything();
50285          return;
50286        } // Don't do a rendition switch unless we have enough time to get a sync segment
50287        // and conservatively guess
50288
50289
50290        if (isWalkingForward) {
50291          this.trigger('bandwidthupdate');
50292        }
50293
50294        this.trigger('progress'); // any time an update finishes and the last segment is in the
50295        // buffer, end the stream. this ensures the "ended" event will
50296        // fire if playback reaches that point.
50297
50298        if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
50299          this.endOfStream();
50300        }
50301
50302        if (!this.paused()) {
50303          this.monitorBuffer_();
50304        }
50305      }
50306      /**
50307       * Records the current throughput of the decrypt, transmux, and append
50308       * portion of the semgment pipeline. `throughput.rate` is a the cumulative
50309       * moving average of the throughput. `throughput.count` is the number of
50310       * data points in the average.
50311       *
50312       * @private
50313       * @param {Object} segmentInfo the object returned by loadSegment
50314       */
50315
50316    }, {
50317      key: 'recordThroughput_',
50318      value: function recordThroughput_(segmentInfo) {
50319        var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
50320        // by zero in the case where the throughput is ridiculously high
50321
50322        var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
50323
50324        var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
50325        //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
50326
50327        this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
50328      }
50329      /**
50330       * Adds a cue to the segment-metadata track with some metadata information about the
50331       * segment
50332       *
50333       * @private
50334       * @param {Object} segmentInfo
50335       *        the object returned by loadSegment
50336       * @method addSegmentMetadataCue_
50337       */
50338
50339    }, {
50340      key: 'addSegmentMetadataCue_',
50341      value: function addSegmentMetadataCue_(segmentInfo) {
50342        if (!this.segmentMetadataTrack_) {
50343          return;
50344        }
50345
50346        var segment = segmentInfo.segment;
50347        var start = segment.start;
50348        var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
50349
50350        if (!finite(start) || !finite(end)) {
50351          return;
50352        }
50353
50354        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
50355        var Cue = window$1.WebKitDataCue || window$1.VTTCue;
50356        var value = {
50357          custom: segment.custom,
50358          dateTimeObject: segment.dateTimeObject,
50359          dateTimeString: segment.dateTimeString,
50360          bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
50361          resolution: segmentInfo.playlist.attributes.RESOLUTION,
50362          codecs: segmentInfo.playlist.attributes.CODECS,
50363          byteLength: segmentInfo.byteLength,
50364          uri: segmentInfo.uri,
50365          timeline: segmentInfo.timeline,
50366          playlist: segmentInfo.playlist.uri,
50367          start: start,
50368          end: end
50369        };
50370        var data = JSON.stringify(value);
50371        var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
50372        // the differences of WebKitDataCue in safari and VTTCue in other browsers
50373
50374        cue.value = value;
50375        this.segmentMetadataTrack_.addCue(cue);
50376      }
50377    }]);
50378    return SegmentLoader;
50379  }(videojs$1.EventTarget);
50380
50381  var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
50382    return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
50383  };
50384  /**
50385   * @file vtt-segment-loader.js
50386   */
50387
50388
50389  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) {
50390    return _char2.charCodeAt(0);
50391  }));
50392  /**
50393   * An object that manages segment loading and appending.
50394   *
50395   * @class VTTSegmentLoader
50396   * @param {Object} options required and optional options
50397   * @extends videojs.EventTarget
50398   */
50399
50400  var VTTSegmentLoader = function (_SegmentLoader) {
50401    inherits$2(VTTSegmentLoader, _SegmentLoader);
50402
50403    function VTTSegmentLoader(settings) {
50404      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
50405      classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
50406      // however, VTTSegmentLoader has no need of a media source, so delete the reference
50407
50408      var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
50409
50410      _this.mediaSource_ = null;
50411      _this.subtitlesTrack_ = null;
50412      return _this;
50413    }
50414    /**
50415     * Indicates which time ranges are buffered
50416     *
50417     * @return {TimeRange}
50418     *         TimeRange object representing the current buffered ranges
50419     */
50420
50421
50422    createClass$1(VTTSegmentLoader, [{
50423      key: 'buffered_',
50424      value: function buffered_() {
50425        if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
50426          return videojs$1.createTimeRanges();
50427        }
50428
50429        var cues = this.subtitlesTrack_.cues;
50430        var start = cues[0].startTime;
50431        var end = cues[cues.length - 1].startTime;
50432        return videojs$1.createTimeRanges([[start, end]]);
50433      }
50434      /**
50435       * Gets and sets init segment for the provided map
50436       *
50437       * @param {Object} map
50438       *        The map object representing the init segment to get or set
50439       * @param {Boolean=} set
50440       *        If true, the init segment for the provided map should be saved
50441       * @return {Object}
50442       *         map object for desired init segment
50443       */
50444
50445    }, {
50446      key: 'initSegment',
50447      value: function initSegment(map) {
50448        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
50449
50450        if (!map) {
50451          return null;
50452        }
50453
50454        var id = initSegmentId(map);
50455        var storedMap = this.initSegments_[id];
50456
50457        if (set$$1 && !storedMap && map.bytes) {
50458          // append WebVTT line terminators to the media initialization segment if it exists
50459          // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
50460          // requires two or more WebVTT line terminators between the WebVTT header and the
50461          // rest of the file
50462          var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
50463          var combinedSegment = new Uint8Array(combinedByteLength);
50464          combinedSegment.set(map.bytes);
50465          combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
50466          this.initSegments_[id] = storedMap = {
50467            resolvedUri: map.resolvedUri,
50468            byterange: map.byterange,
50469            bytes: combinedSegment
50470          };
50471        }
50472
50473        return storedMap || map;
50474      }
50475      /**
50476       * Returns true if all configuration required for loading is present, otherwise false.
50477       *
50478       * @return {Boolean} True if the all configuration is ready for loading
50479       * @private
50480       */
50481
50482    }, {
50483      key: 'couldBeginLoading_',
50484      value: function couldBeginLoading_() {
50485        return this.playlist_ && this.subtitlesTrack_ && !this.paused();
50486      }
50487      /**
50488       * Once all the starting parameters have been specified, begin
50489       * operation. This method should only be invoked from the INIT
50490       * state.
50491       *
50492       * @private
50493       */
50494
50495    }, {
50496      key: 'init_',
50497      value: function init_() {
50498        this.state = 'READY';
50499        this.resetEverything();
50500        return this.monitorBuffer_();
50501      }
50502      /**
50503       * Set a subtitle track on the segment loader to add subtitles to
50504       *
50505       * @param {TextTrack=} track
50506       *        The text track to add loaded subtitles to
50507       * @return {TextTrack}
50508       *        Returns the subtitles track
50509       */
50510
50511    }, {
50512      key: 'track',
50513      value: function track(_track) {
50514        if (typeof _track === 'undefined') {
50515          return this.subtitlesTrack_;
50516        }
50517
50518        this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
50519        // buffering now
50520
50521        if (this.state === 'INIT' && this.couldBeginLoading_()) {
50522          this.init_();
50523        }
50524
50525        return this.subtitlesTrack_;
50526      }
50527      /**
50528       * Remove any data in the source buffer between start and end times
50529       * @param {Number} start - the start time of the region to remove from the buffer
50530       * @param {Number} end - the end time of the region to remove from the buffer
50531       */
50532
50533    }, {
50534      key: 'remove',
50535      value: function remove(start, end) {
50536        removeCuesFromTrack(start, end, this.subtitlesTrack_);
50537      }
50538      /**
50539       * fill the buffer with segements unless the sourceBuffers are
50540       * currently updating
50541       *
50542       * Note: this function should only ever be called by monitorBuffer_
50543       * and never directly
50544       *
50545       * @private
50546       */
50547
50548    }, {
50549      key: 'fillBuffer_',
50550      value: function fillBuffer_() {
50551        var _this2 = this;
50552
50553        if (!this.syncPoint_) {
50554          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
50555        } // see if we need to begin loading immediately
50556
50557
50558        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
50559        segmentInfo = this.skipEmptySegments_(segmentInfo);
50560
50561        if (!segmentInfo) {
50562          return;
50563        }
50564
50565        if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
50566          // We don't have the timestamp offset that we need to sync subtitles.
50567          // Rerun on a timestamp offset or user interaction.
50568          var checkTimestampOffset = function checkTimestampOffset() {
50569            _this2.state = 'READY';
50570
50571            if (!_this2.paused()) {
50572              // if not paused, queue a buffer check as soon as possible
50573              _this2.monitorBuffer_();
50574            }
50575          };
50576
50577          this.syncController_.one('timestampoffset', checkTimestampOffset);
50578          this.state = 'WAITING_ON_TIMELINE';
50579          return;
50580        }
50581
50582        this.loadSegment_(segmentInfo);
50583      }
50584      /**
50585       * Prevents the segment loader from requesting segments we know contain no subtitles
50586       * by walking forward until we find the next segment that we don't know whether it is
50587       * empty or not.
50588       *
50589       * @param {Object} segmentInfo
50590       *        a segment info object that describes the current segment
50591       * @return {Object}
50592       *         a segment info object that describes the current segment
50593       */
50594
50595    }, {
50596      key: 'skipEmptySegments_',
50597      value: function skipEmptySegments_(segmentInfo) {
50598        while (segmentInfo && segmentInfo.segment.empty) {
50599          segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
50600        }
50601
50602        return segmentInfo;
50603      }
50604      /**
50605       * append a decrypted segement to the SourceBuffer through a SourceUpdater
50606       *
50607       * @private
50608       */
50609
50610    }, {
50611      key: 'handleSegment_',
50612      value: function handleSegment_() {
50613        var _this3 = this;
50614
50615        if (!this.pendingSegment_ || !this.subtitlesTrack_) {
50616          this.state = 'READY';
50617          return;
50618        }
50619
50620        this.state = 'APPENDING';
50621        var segmentInfo = this.pendingSegment_;
50622        var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
50623
50624        if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
50625          var loadHandler = void 0;
50626
50627          var errorHandler = function errorHandler() {
50628            _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
50629
50630            _this3.error({
50631              message: 'Error loading vtt.js'
50632            });
50633
50634            _this3.state = 'READY';
50635
50636            _this3.pause();
50637
50638            _this3.trigger('error');
50639          };
50640
50641          loadHandler = function loadHandler() {
50642            _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler);
50643
50644            _this3.handleSegment_();
50645          };
50646
50647          this.state = 'WAITING_ON_VTTJS';
50648          this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
50649          this.subtitlesTrack_.tech_.one('vttjserror', errorHandler);
50650          return;
50651        }
50652
50653        segment.requested = true;
50654
50655        try {
50656          this.parseVTTCues_(segmentInfo);
50657        } catch (e) {
50658          this.error({
50659            message: e.message
50660          });
50661          this.state = 'READY';
50662          this.pause();
50663          return this.trigger('error');
50664        }
50665
50666        this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
50667
50668        if (segmentInfo.isSyncRequest) {
50669          this.trigger('syncinfoupdate');
50670          this.pendingSegment_ = null;
50671          this.state = 'READY';
50672          return;
50673        }
50674
50675        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
50676        this.mediaSecondsLoaded += segment.duration;
50677
50678        if (segmentInfo.cues.length) {
50679          // remove any overlapping cues to prevent doubling
50680          this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
50681        }
50682
50683        segmentInfo.cues.forEach(function (cue) {
50684          _this3.subtitlesTrack_.addCue(cue);
50685        });
50686        this.handleUpdateEnd_();
50687      }
50688      /**
50689       * Uses the WebVTT parser to parse the segment response
50690       *
50691       * @param {Object} segmentInfo
50692       *        a segment info object that describes the current segment
50693       * @private
50694       */
50695
50696    }, {
50697      key: 'parseVTTCues_',
50698      value: function parseVTTCues_(segmentInfo) {
50699        var decoder = void 0;
50700        var decodeBytesToString = false;
50701
50702        if (typeof window$1.TextDecoder === 'function') {
50703          decoder = new window$1.TextDecoder('utf8');
50704        } else {
50705          decoder = window$1.WebVTT.StringDecoder();
50706          decodeBytesToString = true;
50707        }
50708
50709        var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
50710        segmentInfo.cues = [];
50711        segmentInfo.timestampmap = {
50712          MPEGTS: 0,
50713          LOCAL: 0
50714        };
50715        parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
50716
50717        parser.ontimestampmap = function (map) {
50718          return segmentInfo.timestampmap = map;
50719        };
50720
50721        parser.onparsingerror = function (error) {
50722          videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
50723        };
50724
50725        if (segmentInfo.segment.map) {
50726          var mapData = segmentInfo.segment.map.bytes;
50727
50728          if (decodeBytesToString) {
50729            mapData = uint8ToUtf8(mapData);
50730          }
50731
50732          parser.parse(mapData);
50733        }
50734
50735        var segmentData = segmentInfo.bytes;
50736
50737        if (decodeBytesToString) {
50738          segmentData = uint8ToUtf8(segmentData);
50739        }
50740
50741        parser.parse(segmentData);
50742        parser.flush();
50743      }
50744      /**
50745       * Updates the start and end times of any cues parsed by the WebVTT parser using
50746       * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
50747       * from the SyncController
50748       *
50749       * @param {Object} segmentInfo
50750       *        a segment info object that describes the current segment
50751       * @param {Object} mappingObj
50752       *        object containing a mapping from TS to media time
50753       * @param {Object} playlist
50754       *        the playlist object containing the segment
50755       * @private
50756       */
50757
50758    }, {
50759      key: 'updateTimeMapping_',
50760      value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
50761        var segment = segmentInfo.segment;
50762
50763        if (!mappingObj) {
50764          // If the sync controller does not have a mapping of TS to Media Time for the
50765          // timeline, then we don't have enough information to update the cue
50766          // start/end times
50767          return;
50768        }
50769
50770        if (!segmentInfo.cues.length) {
50771          // If there are no cues, we also do not have enough information to figure out
50772          // segment timing. Mark that the segment contains no cues so we don't re-request
50773          // an empty segment.
50774          segment.empty = true;
50775          return;
50776        }
50777
50778        var timestampmap = segmentInfo.timestampmap;
50779        var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
50780        segmentInfo.cues.forEach(function (cue) {
50781          // First convert cue time to TS time using the timestamp-map provided within the vtt
50782          cue.startTime += diff;
50783          cue.endTime += diff;
50784        });
50785
50786        if (!playlist.syncInfo) {
50787          var firstStart = segmentInfo.cues[0].startTime;
50788          var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
50789          playlist.syncInfo = {
50790            mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
50791            time: Math.min(firstStart, lastStart - segment.duration)
50792          };
50793        }
50794      }
50795    }]);
50796    return VTTSegmentLoader;
50797  }(SegmentLoader);
50798  /**
50799   * @file ad-cue-tags.js
50800   */
50801
50802  /**
50803   * Searches for an ad cue that overlaps with the given mediaTime
50804   */
50805
50806
50807  var findAdCue = function findAdCue(track, mediaTime) {
50808    var cues = track.cues;
50809
50810    for (var i = 0; i < cues.length; i++) {
50811      var cue = cues[i];
50812
50813      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
50814        return cue;
50815      }
50816    }
50817
50818    return null;
50819  };
50820
50821  var updateAdCues = function updateAdCues(media, track) {
50822    var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
50823
50824    if (!media.segments) {
50825      return;
50826    }
50827
50828    var mediaTime = offset;
50829    var cue = void 0;
50830
50831    for (var i = 0; i < media.segments.length; i++) {
50832      var segment = media.segments[i];
50833
50834      if (!cue) {
50835        // Since the cues will span for at least the segment duration, adding a fudge
50836        // factor of half segment duration will prevent duplicate cues from being
50837        // created when timing info is not exact (e.g. cue start time initialized
50838        // at 10.006677, but next call mediaTime is 10.003332 )
50839        cue = findAdCue(track, mediaTime + segment.duration / 2);
50840      }
50841
50842      if (cue) {
50843        if ('cueIn' in segment) {
50844          // Found a CUE-IN so end the cue
50845          cue.endTime = mediaTime;
50846          cue.adEndTime = mediaTime;
50847          mediaTime += segment.duration;
50848          cue = null;
50849          continue;
50850        }
50851
50852        if (mediaTime < cue.endTime) {
50853          // Already processed this mediaTime for this cue
50854          mediaTime += segment.duration;
50855          continue;
50856        } // otherwise extend cue until a CUE-IN is found
50857
50858
50859        cue.endTime += segment.duration;
50860      } else {
50861        if ('cueOut' in segment) {
50862          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
50863          cue.adStartTime = mediaTime; // Assumes tag format to be
50864          // #EXT-X-CUE-OUT:30
50865
50866          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
50867          track.addCue(cue);
50868        }
50869
50870        if ('cueOutCont' in segment) {
50871          // Entered into the middle of an ad cue
50872          var adOffset = void 0;
50873          var adTotal = void 0; // Assumes tag formate to be
50874          // #EXT-X-CUE-OUT-CONT:10/30
50875
50876          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
50877
50878          var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
50879
50880          adOffset = _segment$cueOutCont$s2[0];
50881          adTotal = _segment$cueOutCont$s2[1];
50882          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
50883          cue.adStartTime = mediaTime - adOffset;
50884          cue.adEndTime = cue.adStartTime + adTotal;
50885          track.addCue(cue);
50886        }
50887      }
50888
50889      mediaTime += segment.duration;
50890    }
50891  };
50892  /**
50893   * @file sync-controller.js
50894   */
50895
50896
50897  var tsprobe = tsInspector.inspect;
50898  var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
50899  //                the equivalence display-time 0 === segment-index 0
50900  {
50901    name: 'VOD',
50902    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
50903      if (duration$$1 !== Infinity) {
50904        var syncPoint = {
50905          time: 0,
50906          segmentIndex: 0
50907        };
50908        return syncPoint;
50909      }
50910
50911      return null;
50912    }
50913  }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
50914  {
50915    name: 'ProgramDateTime',
50916    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
50917      if (!syncController.datetimeToDisplayTime) {
50918        return null;
50919      }
50920
50921      var segments = playlist.segments || [];
50922      var syncPoint = null;
50923      var lastDistance = null;
50924      currentTime = currentTime || 0;
50925
50926      for (var i = 0; i < segments.length; i++) {
50927        var segment = segments[i];
50928
50929        if (segment.dateTimeObject) {
50930          var segmentTime = segment.dateTimeObject.getTime() / 1000;
50931          var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
50932          var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed
50933          // currentTime and can stop looking for better candidates
50934
50935          if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
50936            break;
50937          }
50938
50939          lastDistance = distance;
50940          syncPoint = {
50941            time: segmentStart,
50942            segmentIndex: i
50943          };
50944        }
50945      }
50946
50947      return syncPoint;
50948    }
50949  }, // Stategy "Segment": We have a known time mapping for a timeline and a
50950  //                    segment in the current timeline with timing data
50951  {
50952    name: 'Segment',
50953    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
50954      var segments = playlist.segments || [];
50955      var syncPoint = null;
50956      var lastDistance = null;
50957      currentTime = currentTime || 0;
50958
50959      for (var i = 0; i < segments.length; i++) {
50960        var segment = segments[i];
50961
50962        if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
50963          var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
50964          // currentTime and can stop looking for better candidates
50965
50966          if (lastDistance !== null && lastDistance < distance) {
50967            break;
50968          }
50969
50970          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
50971            lastDistance = distance;
50972            syncPoint = {
50973              time: segment.start,
50974              segmentIndex: i
50975            };
50976          }
50977        }
50978      }
50979
50980      return syncPoint;
50981    }
50982  }, // Stategy "Discontinuity": We have a discontinuity with a known
50983  //                          display-time
50984  {
50985    name: 'Discontinuity',
50986    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
50987      var syncPoint = null;
50988      currentTime = currentTime || 0;
50989
50990      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
50991        var lastDistance = null;
50992
50993        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
50994          var segmentIndex = playlist.discontinuityStarts[i];
50995          var discontinuity = playlist.discontinuitySequence + i + 1;
50996          var discontinuitySync = syncController.discontinuities[discontinuity];
50997
50998          if (discontinuitySync) {
50999            var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
51000            // currentTime and can stop looking for better candidates
51001
51002            if (lastDistance !== null && lastDistance < distance) {
51003              break;
51004            }
51005
51006            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
51007              lastDistance = distance;
51008              syncPoint = {
51009                time: discontinuitySync.time,
51010                segmentIndex: segmentIndex
51011              };
51012            }
51013          }
51014        }
51015      }
51016
51017      return syncPoint;
51018    }
51019  }, // Stategy "Playlist": We have a playlist with a known mapping of
51020  //                     segment index to display time
51021  {
51022    name: 'Playlist',
51023    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
51024      if (playlist.syncInfo) {
51025        var syncPoint = {
51026          time: playlist.syncInfo.time,
51027          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
51028        };
51029        return syncPoint;
51030      }
51031
51032      return null;
51033    }
51034  }];
51035
51036  var SyncController = function (_videojs$EventTarget) {
51037    inherits$2(SyncController, _videojs$EventTarget);
51038
51039    function SyncController() {
51040      classCallCheck$1(this, SyncController); // Segment Loader state variables...
51041      // ...for synching across variants
51042
51043      var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
51044
51045      _this.inspectCache_ = undefined; // ...for synching across variants
51046
51047      _this.timelines = [];
51048      _this.discontinuities = [];
51049      _this.datetimeToDisplayTime = null;
51050      _this.logger_ = logger('SyncController');
51051      return _this;
51052    }
51053    /**
51054     * Find a sync-point for the playlist specified
51055     *
51056     * A sync-point is defined as a known mapping from display-time to
51057     * a segment-index in the current playlist.
51058     *
51059     * @param {Playlist} playlist
51060     *        The playlist that needs a sync-point
51061     * @param {Number} duration
51062     *        Duration of the MediaSource (Infinite if playing a live source)
51063     * @param {Number} currentTimeline
51064     *        The last timeline from which a segment was loaded
51065     * @returns {Object}
51066     *          A sync-point object
51067     */
51068
51069
51070    createClass$1(SyncController, [{
51071      key: 'getSyncPoint',
51072      value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
51073        var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
51074
51075        if (!syncPoints.length) {
51076          // Signal that we need to attempt to get a sync-point manually
51077          // by fetching a segment in the playlist and constructing
51078          // a sync-point from that information
51079          return null;
51080        } // Now find the sync-point that is closest to the currentTime because
51081        // that should result in the most accurate guess about which segment
51082        // to fetch
51083
51084
51085        return this.selectSyncPoint_(syncPoints, {
51086          key: 'time',
51087          value: currentTime
51088        });
51089      }
51090      /**
51091       * Calculate the amount of time that has expired off the playlist during playback
51092       *
51093       * @param {Playlist} playlist
51094       *        Playlist object to calculate expired from
51095       * @param {Number} duration
51096       *        Duration of the MediaSource (Infinity if playling a live source)
51097       * @returns {Number|null}
51098       *          The amount of time that has expired off the playlist during playback. Null
51099       *          if no sync-points for the playlist can be found.
51100       */
51101
51102    }, {
51103      key: 'getExpiredTime',
51104      value: function getExpiredTime(playlist, duration$$1) {
51105        if (!playlist || !playlist.segments) {
51106          return null;
51107        }
51108
51109        var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
51110
51111        if (!syncPoints.length) {
51112          return null;
51113        }
51114
51115        var syncPoint = this.selectSyncPoint_(syncPoints, {
51116          key: 'segmentIndex',
51117          value: 0
51118        }); // If the sync-point is beyond the start of the playlist, we want to subtract the
51119        // duration from index 0 to syncPoint.segmentIndex instead of adding.
51120
51121        if (syncPoint.segmentIndex > 0) {
51122          syncPoint.time *= -1;
51123        }
51124
51125        return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
51126      }
51127      /**
51128       * Runs each sync-point strategy and returns a list of sync-points returned by the
51129       * strategies
51130       *
51131       * @private
51132       * @param {Playlist} playlist
51133       *        The playlist that needs a sync-point
51134       * @param {Number} duration
51135       *        Duration of the MediaSource (Infinity if playing a live source)
51136       * @param {Number} currentTimeline
51137       *        The last timeline from which a segment was loaded
51138       * @returns {Array}
51139       *          A list of sync-point objects
51140       */
51141
51142    }, {
51143      key: 'runStrategies_',
51144      value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
51145        var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
51146
51147        for (var i = 0; i < syncPointStrategies.length; i++) {
51148          var strategy = syncPointStrategies[i];
51149          var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
51150
51151          if (syncPoint) {
51152            syncPoint.strategy = strategy.name;
51153            syncPoints.push({
51154              strategy: strategy.name,
51155              syncPoint: syncPoint
51156            });
51157          }
51158        }
51159
51160        return syncPoints;
51161      }
51162      /**
51163       * Selects the sync-point nearest the specified target
51164       *
51165       * @private
51166       * @param {Array} syncPoints
51167       *        List of sync-points to select from
51168       * @param {Object} target
51169       *        Object specifying the property and value we are targeting
51170       * @param {String} target.key
51171       *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
51172       * @param {Number} target.value
51173       *        The value to target for the specified key.
51174       * @returns {Object}
51175       *          The sync-point nearest the target
51176       */
51177
51178    }, {
51179      key: 'selectSyncPoint_',
51180      value: function selectSyncPoint_(syncPoints, target) {
51181        var bestSyncPoint = syncPoints[0].syncPoint;
51182        var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
51183        var bestStrategy = syncPoints[0].strategy;
51184
51185        for (var i = 1; i < syncPoints.length; i++) {
51186          var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
51187
51188          if (newDistance < bestDistance) {
51189            bestDistance = newDistance;
51190            bestSyncPoint = syncPoints[i].syncPoint;
51191            bestStrategy = syncPoints[i].strategy;
51192          }
51193        }
51194
51195        this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
51196        return bestSyncPoint;
51197      }
51198      /**
51199       * Save any meta-data present on the segments when segments leave
51200       * the live window to the playlist to allow for synchronization at the
51201       * playlist level later.
51202       *
51203       * @param {Playlist} oldPlaylist - The previous active playlist
51204       * @param {Playlist} newPlaylist - The updated and most current playlist
51205       */
51206
51207    }, {
51208      key: 'saveExpiredSegmentInfo',
51209      value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
51210        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
51211        // save that information as a possible sync-point reference in future
51212
51213        for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
51214          var lastRemovedSegment = oldPlaylist.segments[i];
51215
51216          if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
51217            newPlaylist.syncInfo = {
51218              mediaSequence: oldPlaylist.mediaSequence + i,
51219              time: lastRemovedSegment.start
51220            };
51221            this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
51222            this.trigger('syncinfoupdate');
51223            break;
51224          }
51225        }
51226      }
51227      /**
51228       * Save the mapping from playlist's ProgramDateTime to display. This should
51229       * only ever happen once at the start of playback.
51230       *
51231       * @param {Playlist} playlist - The currently active playlist
51232       */
51233
51234    }, {
51235      key: 'setDateTimeMapping',
51236      value: function setDateTimeMapping(playlist) {
51237        if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
51238          var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
51239          this.datetimeToDisplayTime = -playlistTimestamp;
51240        }
51241      }
51242      /**
51243       * Reset the state of the inspection cache when we do a rendition
51244       * switch
51245       */
51246
51247    }, {
51248      key: 'reset',
51249      value: function reset() {
51250        this.inspectCache_ = undefined;
51251      }
51252      /**
51253       * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
51254       * and end of the segment in it's internal "media time". Used to generate
51255       * mappings from that internal "media time" to the display time that is
51256       * shown on the player.
51257       *
51258       * @param {SegmentInfo} segmentInfo - The current active request information
51259       */
51260
51261    }, {
51262      key: 'probeSegmentInfo',
51263      value: function probeSegmentInfo(segmentInfo) {
51264        var segment = segmentInfo.segment;
51265        var playlist = segmentInfo.playlist;
51266        var timingInfo = void 0;
51267
51268        if (segment.map) {
51269          timingInfo = this.probeMp4Segment_(segmentInfo);
51270        } else {
51271          timingInfo = this.probeTsSegment_(segmentInfo);
51272        }
51273
51274        if (timingInfo) {
51275          if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
51276            this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
51277            // now with segment timing information
51278
51279            if (!playlist.syncInfo) {
51280              playlist.syncInfo = {
51281                mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
51282                time: segment.start
51283              };
51284            }
51285          }
51286        }
51287
51288        return timingInfo;
51289      }
51290      /**
51291       * Probe an fmp4 segment to determine the start of the segment
51292       * in it's internal "composition time", which is equal to the base
51293       * media decode time plus the composition time offset value
51294       *
51295       * @private
51296       * @param {SegmentInfo} segmentInfo - The current active request information
51297       * @return {object} The start and end time of the current segment in "composition time"
51298       */
51299
51300    }, {
51301      key: 'probeMp4Segment_',
51302      value: function probeMp4Segment_(segmentInfo) {
51303        var segment = segmentInfo.segment; // get timescales from init segment
51304
51305        var timescales = probe.timescale(segment.map.bytes); // calculate composition start time using the timescales and information
51306        // contained within the media segment
51307
51308        var compositionStartTime = probe.compositionStartTime(timescales, segmentInfo.bytes);
51309
51310        if (segmentInfo.timestampOffset !== null) {
51311          segmentInfo.timestampOffset -= compositionStartTime;
51312        }
51313
51314        return {
51315          start: compositionStartTime,
51316          end: compositionStartTime + segment.duration
51317        };
51318      }
51319      /**
51320       * Probe an mpeg2-ts segment to determine the start and end of the segment
51321       * in it's internal "media time".
51322       *
51323       * @private
51324       * @param {SegmentInfo} segmentInfo - The current active request information
51325       * @return {object} The start and end time of the current segment in "media time"
51326       */
51327
51328    }, {
51329      key: 'probeTsSegment_',
51330      value: function probeTsSegment_(segmentInfo) {
51331        var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
51332        var segmentStartTime = void 0;
51333        var segmentEndTime = void 0;
51334        var segmentTimestampInfo = void 0;
51335
51336        if (!timeInfo) {
51337          return null;
51338        }
51339
51340        if (timeInfo.video && timeInfo.video.length === 2) {
51341          this.inspectCache_ = timeInfo.video[1].dts;
51342          segmentStartTime = timeInfo.video[0].dtsTime;
51343          segmentEndTime = timeInfo.video[1].dtsTime;
51344          segmentTimestampInfo = timeInfo.video;
51345        } else if (timeInfo.audio && timeInfo.audio.length === 2) {
51346          this.inspectCache_ = timeInfo.audio[1].dts;
51347          segmentStartTime = timeInfo.audio[0].dtsTime;
51348          segmentEndTime = timeInfo.audio[1].dtsTime;
51349          segmentTimestampInfo = timeInfo.audio;
51350        }
51351
51352        var probedInfo = {
51353          segmentTimestampInfo: segmentTimestampInfo,
51354          start: segmentStartTime,
51355          end: segmentEndTime,
51356          containsVideo: timeInfo.video && timeInfo.video.length === 2,
51357          containsAudio: timeInfo.audio && timeInfo.audio.length === 2
51358        };
51359        return probedInfo;
51360      }
51361    }, {
51362      key: 'timestampOffsetForTimeline',
51363      value: function timestampOffsetForTimeline(timeline) {
51364        if (typeof this.timelines[timeline] === 'undefined') {
51365          return null;
51366        }
51367
51368        return this.timelines[timeline].time;
51369      }
51370    }, {
51371      key: 'mappingForTimeline',
51372      value: function mappingForTimeline(timeline) {
51373        if (typeof this.timelines[timeline] === 'undefined') {
51374          return null;
51375        }
51376
51377        return this.timelines[timeline].mapping;
51378      }
51379      /**
51380       * Use the "media time" for a segment to generate a mapping to "display time" and
51381       * save that display time to the segment.
51382       *
51383       * @private
51384       * @param {SegmentInfo} segmentInfo
51385       *        The current active request information
51386       * @param {object} timingInfo
51387       *        The start and end time of the current segment in "media time"
51388       * @returns {Boolean}
51389       *          Returns false if segment time mapping could not be calculated
51390       */
51391
51392    }, {
51393      key: 'calculateSegmentTimeMapping_',
51394      value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
51395        var segment = segmentInfo.segment;
51396        var mappingObj = this.timelines[segmentInfo.timeline];
51397
51398        if (segmentInfo.timestampOffset !== null) {
51399          mappingObj = {
51400            time: segmentInfo.startOfSegment,
51401            mapping: segmentInfo.startOfSegment - timingInfo.start
51402          };
51403          this.timelines[segmentInfo.timeline] = mappingObj;
51404          this.trigger('timestampoffset');
51405          this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
51406          segment.start = segmentInfo.startOfSegment;
51407          segment.end = timingInfo.end + mappingObj.mapping;
51408        } else if (mappingObj) {
51409          segment.start = timingInfo.start + mappingObj.mapping;
51410          segment.end = timingInfo.end + mappingObj.mapping;
51411        } else {
51412          return false;
51413        }
51414
51415        return true;
51416      }
51417      /**
51418       * Each time we have discontinuity in the playlist, attempt to calculate the location
51419       * in display of the start of the discontinuity and save that. We also save an accuracy
51420       * value so that we save values with the most accuracy (closest to 0.)
51421       *
51422       * @private
51423       * @param {SegmentInfo} segmentInfo - The current active request information
51424       */
51425
51426    }, {
51427      key: 'saveDiscontinuitySyncInfo_',
51428      value: function saveDiscontinuitySyncInfo_(segmentInfo) {
51429        var playlist = segmentInfo.playlist;
51430        var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
51431        // the start of the range and it's accuracy is 0 (greater accuracy values
51432        // mean more approximation)
51433
51434        if (segment.discontinuity) {
51435          this.discontinuities[segment.timeline] = {
51436            time: segment.start,
51437            accuracy: 0
51438          };
51439        } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
51440          // Search for future discontinuities that we can provide better timing
51441          // information for and save that information for sync purposes
51442          for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
51443            var segmentIndex = playlist.discontinuityStarts[i];
51444            var discontinuity = playlist.discontinuitySequence + i + 1;
51445            var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
51446            var accuracy = Math.abs(mediaIndexDiff);
51447
51448            if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
51449              var time = void 0;
51450
51451              if (mediaIndexDiff < 0) {
51452                time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
51453              } else {
51454                time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
51455              }
51456
51457              this.discontinuities[discontinuity] = {
51458                time: time,
51459                accuracy: accuracy
51460              };
51461            }
51462          }
51463        }
51464      }
51465    }]);
51466    return SyncController;
51467  }(videojs$1.EventTarget);
51468
51469  var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
51470    var self = this;
51471
51472    var decrypterWorker = function () {
51473      /*
51474       * pkcs7.pad
51475       * https://github.com/brightcove/pkcs7
51476       *
51477       * Copyright (c) 2014 Brightcove
51478       * Licensed under the apache2 license.
51479       */
51480
51481      /**
51482       * Returns the subarray of a Uint8Array without PKCS#7 padding.
51483       * @param padded {Uint8Array} unencrypted bytes that have been padded
51484       * @return {Uint8Array} the unpadded bytes
51485       * @see http://tools.ietf.org/html/rfc5652
51486       */
51487      function unpad(padded) {
51488        return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
51489      }
51490
51491      var classCallCheck = function classCallCheck(instance, Constructor) {
51492        if (!(instance instanceof Constructor)) {
51493          throw new TypeError("Cannot call a class as a function");
51494        }
51495      };
51496
51497      var createClass = function () {
51498        function defineProperties(target, props) {
51499          for (var i = 0; i < props.length; i++) {
51500            var descriptor = props[i];
51501            descriptor.enumerable = descriptor.enumerable || false;
51502            descriptor.configurable = true;
51503            if ("value" in descriptor) descriptor.writable = true;
51504            Object.defineProperty(target, descriptor.key, descriptor);
51505          }
51506        }
51507
51508        return function (Constructor, protoProps, staticProps) {
51509          if (protoProps) defineProperties(Constructor.prototype, protoProps);
51510          if (staticProps) defineProperties(Constructor, staticProps);
51511          return Constructor;
51512        };
51513      }();
51514
51515      var inherits = function inherits(subClass, superClass) {
51516        if (typeof superClass !== "function" && superClass !== null) {
51517          throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
51518        }
51519
51520        subClass.prototype = Object.create(superClass && superClass.prototype, {
51521          constructor: {
51522            value: subClass,
51523            enumerable: false,
51524            writable: true,
51525            configurable: true
51526          }
51527        });
51528        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
51529      };
51530
51531      var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
51532        if (!self) {
51533          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
51534        }
51535
51536        return call && (typeof call === "object" || typeof call === "function") ? call : self;
51537      };
51538      /**
51539       * @file aes.js
51540       *
51541       * This file contains an adaptation of the AES decryption algorithm
51542       * from the Standford Javascript Cryptography Library. That work is
51543       * covered by the following copyright and permissions notice:
51544       *
51545       * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
51546       * All rights reserved.
51547       *
51548       * Redistribution and use in source and binary forms, with or without
51549       * modification, are permitted provided that the following conditions are
51550       * met:
51551       *
51552       * 1. Redistributions of source code must retain the above copyright
51553       *    notice, this list of conditions and the following disclaimer.
51554       *
51555       * 2. Redistributions in binary form must reproduce the above
51556       *    copyright notice, this list of conditions and the following
51557       *    disclaimer in the documentation and/or other materials provided
51558       *    with the distribution.
51559       *
51560       * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
51561       * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
51562       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51563       * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
51564       * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
51565       * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
51566       * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
51567       * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
51568       * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
51569       * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
51570       * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51571       *
51572       * The views and conclusions contained in the software and documentation
51573       * are those of the authors and should not be interpreted as representing
51574       * official policies, either expressed or implied, of the authors.
51575       */
51576
51577      /**
51578       * Expand the S-box tables.
51579       *
51580       * @private
51581       */
51582
51583
51584      var precompute = function precompute() {
51585        var tables = [[[], [], [], [], []], [[], [], [], [], []]];
51586        var encTable = tables[0];
51587        var decTable = tables[1];
51588        var sbox = encTable[4];
51589        var sboxInv = decTable[4];
51590        var i = void 0;
51591        var x = void 0;
51592        var xInv = void 0;
51593        var d = [];
51594        var th = [];
51595        var x2 = void 0;
51596        var x4 = void 0;
51597        var x8 = void 0;
51598        var s = void 0;
51599        var tEnc = void 0;
51600        var tDec = void 0; // Compute double and third tables
51601
51602        for (i = 0; i < 256; i++) {
51603          th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
51604        }
51605
51606        for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
51607          // Compute sbox
51608          s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
51609          s = s >> 8 ^ s & 255 ^ 99;
51610          sbox[x] = s;
51611          sboxInv[s] = x; // Compute MixColumns
51612
51613          x8 = d[x4 = d[x2 = d[x]]];
51614          tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
51615          tEnc = d[s] * 0x101 ^ s * 0x1010100;
51616
51617          for (i = 0; i < 4; i++) {
51618            encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
51619            decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
51620          }
51621        } // Compactify. Considerable speedup on Firefox.
51622
51623
51624        for (i = 0; i < 5; i++) {
51625          encTable[i] = encTable[i].slice(0);
51626          decTable[i] = decTable[i].slice(0);
51627        }
51628
51629        return tables;
51630      };
51631
51632      var aesTables = null;
51633      /**
51634       * Schedule out an AES key for both encryption and decryption. This
51635       * is a low-level class. Use a cipher mode to do bulk encryption.
51636       *
51637       * @class AES
51638       * @param key {Array} The key as an array of 4, 6 or 8 words.
51639       */
51640
51641      var AES = function () {
51642        function AES(key) {
51643          classCallCheck(this, AES);
51644          /**
51645           * The expanded S-box and inverse S-box tables. These will be computed
51646           * on the client so that we don't have to send them down the wire.
51647           *
51648           * There are two tables, _tables[0] is for encryption and
51649           * _tables[1] is for decryption.
51650           *
51651           * The first 4 sub-tables are the expanded S-box with MixColumns. The
51652           * last (_tables[01][4]) is the S-box itself.
51653           *
51654           * @private
51655           */
51656          // if we have yet to precompute the S-box tables
51657          // do so now
51658
51659          if (!aesTables) {
51660            aesTables = precompute();
51661          } // then make a copy of that object for use
51662
51663
51664          this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
51665          var i = void 0;
51666          var j = void 0;
51667          var tmp = void 0;
51668          var encKey = void 0;
51669          var decKey = void 0;
51670          var sbox = this._tables[0][4];
51671          var decTable = this._tables[1];
51672          var keyLen = key.length;
51673          var rcon = 1;
51674
51675          if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
51676            throw new Error('Invalid aes key size');
51677          }
51678
51679          encKey = key.slice(0);
51680          decKey = [];
51681          this._key = [encKey, decKey]; // schedule encryption keys
51682
51683          for (i = keyLen; i < 4 * keyLen + 28; i++) {
51684            tmp = encKey[i - 1]; // apply sbox
51685
51686            if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
51687              tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
51688
51689              if (i % keyLen === 0) {
51690                tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
51691                rcon = rcon << 1 ^ (rcon >> 7) * 283;
51692              }
51693            }
51694
51695            encKey[i] = encKey[i - keyLen] ^ tmp;
51696          } // schedule decryption keys
51697
51698
51699          for (j = 0; i; j++, i--) {
51700            tmp = encKey[j & 3 ? i : i - 4];
51701
51702            if (i <= 4 || j < 4) {
51703              decKey[j] = tmp;
51704            } else {
51705              decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
51706            }
51707          }
51708        }
51709        /**
51710         * Decrypt 16 bytes, specified as four 32-bit words.
51711         *
51712         * @param {Number} encrypted0 the first word to decrypt
51713         * @param {Number} encrypted1 the second word to decrypt
51714         * @param {Number} encrypted2 the third word to decrypt
51715         * @param {Number} encrypted3 the fourth word to decrypt
51716         * @param {Int32Array} out the array to write the decrypted words
51717         * into
51718         * @param {Number} offset the offset into the output array to start
51719         * writing results
51720         * @return {Array} The plaintext.
51721         */
51722
51723
51724        AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
51725          var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
51726
51727          var a = encrypted0 ^ key[0];
51728          var b = encrypted3 ^ key[1];
51729          var c = encrypted2 ^ key[2];
51730          var d = encrypted1 ^ key[3];
51731          var a2 = void 0;
51732          var b2 = void 0;
51733          var c2 = void 0; // key.length === 2 ?
51734
51735          var nInnerRounds = key.length / 4 - 2;
51736          var i = void 0;
51737          var kIndex = 4;
51738          var table = this._tables[1]; // load up the tables
51739
51740          var table0 = table[0];
51741          var table1 = table[1];
51742          var table2 = table[2];
51743          var table3 = table[3];
51744          var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
51745
51746          for (i = 0; i < nInnerRounds; i++) {
51747            a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
51748            b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
51749            c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
51750            d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
51751            kIndex += 4;
51752            a = a2;
51753            b = b2;
51754            c = c2;
51755          } // Last round.
51756
51757
51758          for (i = 0; i < 4; i++) {
51759            out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
51760            a2 = a;
51761            a = b;
51762            b = c;
51763            c = d;
51764            d = a2;
51765          }
51766        };
51767
51768        return AES;
51769      }();
51770      /**
51771       * @file stream.js
51772       */
51773
51774      /**
51775       * A lightweight readable stream implemention that handles event dispatching.
51776       *
51777       * @class Stream
51778       */
51779
51780
51781      var Stream = function () {
51782        function Stream() {
51783          classCallCheck(this, Stream);
51784          this.listeners = {};
51785        }
51786        /**
51787         * Add a listener for a specified event type.
51788         *
51789         * @param {String} type the event name
51790         * @param {Function} listener the callback to be invoked when an event of
51791         * the specified type occurs
51792         */
51793
51794
51795        Stream.prototype.on = function on(type, listener) {
51796          if (!this.listeners[type]) {
51797            this.listeners[type] = [];
51798          }
51799
51800          this.listeners[type].push(listener);
51801        };
51802        /**
51803         * Remove a listener for a specified event type.
51804         *
51805         * @param {String} type the event name
51806         * @param {Function} listener  a function previously registered for this
51807         * type of event through `on`
51808         * @return {Boolean} if we could turn it off or not
51809         */
51810
51811
51812        Stream.prototype.off = function off(type, listener) {
51813          if (!this.listeners[type]) {
51814            return false;
51815          }
51816
51817          var index = this.listeners[type].indexOf(listener);
51818          this.listeners[type].splice(index, 1);
51819          return index > -1;
51820        };
51821        /**
51822         * Trigger an event of the specified type on this stream. Any additional
51823         * arguments to this function are passed as parameters to event listeners.
51824         *
51825         * @param {String} type the event name
51826         */
51827
51828
51829        Stream.prototype.trigger = function trigger(type) {
51830          var callbacks = this.listeners[type];
51831
51832          if (!callbacks) {
51833            return;
51834          } // Slicing the arguments on every invocation of this method
51835          // can add a significant amount of overhead. Avoid the
51836          // intermediate object creation for the common case of a
51837          // single callback argument
51838
51839
51840          if (arguments.length === 2) {
51841            var length = callbacks.length;
51842
51843            for (var i = 0; i < length; ++i) {
51844              callbacks[i].call(this, arguments[1]);
51845            }
51846          } else {
51847            var args = Array.prototype.slice.call(arguments, 1);
51848            var _length = callbacks.length;
51849
51850            for (var _i = 0; _i < _length; ++_i) {
51851              callbacks[_i].apply(this, args);
51852            }
51853          }
51854        };
51855        /**
51856         * Destroys the stream and cleans up.
51857         */
51858
51859
51860        Stream.prototype.dispose = function dispose() {
51861          this.listeners = {};
51862        };
51863        /**
51864         * Forwards all `data` events on this stream to the destination stream. The
51865         * destination stream should provide a method `push` to receive the data
51866         * events as they arrive.
51867         *
51868         * @param {Stream} destination the stream that will receive all `data` events
51869         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
51870         */
51871
51872
51873        Stream.prototype.pipe = function pipe(destination) {
51874          this.on('data', function (data) {
51875            destination.push(data);
51876          });
51877        };
51878
51879        return Stream;
51880      }();
51881      /**
51882       * @file async-stream.js
51883       */
51884
51885      /**
51886       * A wrapper around the Stream class to use setTiemout
51887       * and run stream "jobs" Asynchronously
51888       *
51889       * @class AsyncStream
51890       * @extends Stream
51891       */
51892
51893
51894      var AsyncStream$$1 = function (_Stream) {
51895        inherits(AsyncStream$$1, _Stream);
51896
51897        function AsyncStream$$1() {
51898          classCallCheck(this, AsyncStream$$1);
51899
51900          var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
51901
51902          _this.jobs = [];
51903          _this.delay = 1;
51904          _this.timeout_ = null;
51905          return _this;
51906        }
51907        /**
51908         * process an async job
51909         *
51910         * @private
51911         */
51912
51913
51914        AsyncStream$$1.prototype.processJob_ = function processJob_() {
51915          this.jobs.shift()();
51916
51917          if (this.jobs.length) {
51918            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
51919          } else {
51920            this.timeout_ = null;
51921          }
51922        };
51923        /**
51924         * push a job into the stream
51925         *
51926         * @param {Function} job the job to push into the stream
51927         */
51928
51929
51930        AsyncStream$$1.prototype.push = function push(job) {
51931          this.jobs.push(job);
51932
51933          if (!this.timeout_) {
51934            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
51935          }
51936        };
51937
51938        return AsyncStream$$1;
51939      }(Stream);
51940      /**
51941       * @file decrypter.js
51942       *
51943       * An asynchronous implementation of AES-128 CBC decryption with
51944       * PKCS#7 padding.
51945       */
51946
51947      /**
51948       * Convert network-order (big-endian) bytes into their little-endian
51949       * representation.
51950       */
51951
51952
51953      var ntoh = function ntoh(word) {
51954        return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
51955      };
51956      /**
51957       * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
51958       *
51959       * @param {Uint8Array} encrypted the encrypted bytes
51960       * @param {Uint32Array} key the bytes of the decryption key
51961       * @param {Uint32Array} initVector the initialization vector (IV) to
51962       * use for the first round of CBC.
51963       * @return {Uint8Array} the decrypted bytes
51964       *
51965       * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
51966       * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
51967       * @see https://tools.ietf.org/html/rfc2315
51968       */
51969
51970
51971      var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
51972        // word-level access to the encrypted bytes
51973        var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
51974        var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
51975
51976        var decrypted = new Uint8Array(encrypted.byteLength);
51977        var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
51978        // decrypted data
51979
51980        var init0 = void 0;
51981        var init1 = void 0;
51982        var init2 = void 0;
51983        var init3 = void 0;
51984        var encrypted0 = void 0;
51985        var encrypted1 = void 0;
51986        var encrypted2 = void 0;
51987        var encrypted3 = void 0; // iteration variable
51988
51989        var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
51990        // passed-in reference and easier access
51991
51992        init0 = initVector[0];
51993        init1 = initVector[1];
51994        init2 = initVector[2];
51995        init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
51996        // to each decrypted block
51997
51998        for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
51999          // convert big-endian (network order) words into little-endian
52000          // (javascript order)
52001          encrypted0 = ntoh(encrypted32[wordIx]);
52002          encrypted1 = ntoh(encrypted32[wordIx + 1]);
52003          encrypted2 = ntoh(encrypted32[wordIx + 2]);
52004          encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
52005
52006          decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
52007          // plaintext
52008
52009          decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
52010          decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
52011          decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
52012          decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
52013
52014          init0 = encrypted0;
52015          init1 = encrypted1;
52016          init2 = encrypted2;
52017          init3 = encrypted3;
52018        }
52019
52020        return decrypted;
52021      };
52022      /**
52023       * The `Decrypter` class that manages decryption of AES
52024       * data through `AsyncStream` objects and the `decrypt`
52025       * function
52026       *
52027       * @param {Uint8Array} encrypted the encrypted bytes
52028       * @param {Uint32Array} key the bytes of the decryption key
52029       * @param {Uint32Array} initVector the initialization vector (IV) to
52030       * @param {Function} done the function to run when done
52031       * @class Decrypter
52032       */
52033
52034
52035      var Decrypter$$1 = function () {
52036        function Decrypter$$1(encrypted, key, initVector, done) {
52037          classCallCheck(this, Decrypter$$1);
52038          var step = Decrypter$$1.STEP;
52039          var encrypted32 = new Int32Array(encrypted.buffer);
52040          var decrypted = new Uint8Array(encrypted.byteLength);
52041          var i = 0;
52042          this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
52043
52044          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
52045
52046          for (i = step; i < encrypted32.length; i += step) {
52047            initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
52048            this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
52049          } // invoke the done() callback when everything is finished
52050
52051
52052          this.asyncStream_.push(function () {
52053            // remove pkcs#7 padding from the decrypted bytes
52054            done(null, unpad(decrypted));
52055          });
52056        }
52057        /**
52058         * a getter for step the maximum number of bytes to process at one time
52059         *
52060         * @return {Number} the value of step 32000
52061         */
52062
52063        /**
52064         * @private
52065         */
52066
52067
52068        Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
52069          return function () {
52070            var bytes = decrypt$$1(encrypted, key, initVector);
52071            decrypted.set(bytes, encrypted.byteOffset);
52072          };
52073        };
52074
52075        createClass(Decrypter$$1, null, [{
52076          key: 'STEP',
52077          get: function get$$1() {
52078            // 4 * 8000;
52079            return 32000;
52080          }
52081        }]);
52082        return Decrypter$$1;
52083      }();
52084      /**
52085       * @file bin-utils.js
52086       */
52087
52088      /**
52089       * Creates an object for sending to a web worker modifying properties that are TypedArrays
52090       * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
52091       *
52092       * @param {Object} message
52093       *        Object of properties and values to send to the web worker
52094       * @return {Object}
52095       *         Modified message with TypedArray values expanded
52096       * @function createTransferableMessage
52097       */
52098
52099
52100      var createTransferableMessage = function createTransferableMessage(message) {
52101        var transferable = {};
52102        Object.keys(message).forEach(function (key) {
52103          var value = message[key];
52104
52105          if (ArrayBuffer.isView(value)) {
52106            transferable[key] = {
52107              bytes: value.buffer,
52108              byteOffset: value.byteOffset,
52109              byteLength: value.byteLength
52110            };
52111          } else {
52112            transferable[key] = value;
52113          }
52114        });
52115        return transferable;
52116      };
52117      /**
52118       * Our web worker interface so that things can talk to aes-decrypter
52119       * that will be running in a web worker. the scope is passed to this by
52120       * webworkify.
52121       *
52122       * @param {Object} self
52123       *        the scope for the web worker
52124       */
52125
52126
52127      var DecrypterWorker = function DecrypterWorker(self) {
52128        self.onmessage = function (event) {
52129          var data = event.data;
52130          var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
52131          var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
52132          var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
52133          /* eslint-disable no-new, handle-callback-err */
52134
52135          new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
52136            self.postMessage(createTransferableMessage({
52137              source: data.source,
52138              decrypted: bytes
52139            }), [bytes.buffer]);
52140          });
52141          /* eslint-enable */
52142        };
52143      };
52144
52145      var decrypterWorker = new DecrypterWorker(self);
52146      return decrypterWorker;
52147    }();
52148  });
52149  /**
52150   * Convert the properties of an HLS track into an audioTrackKind.
52151   *
52152   * @private
52153   */
52154
52155  var audioTrackKind_ = function audioTrackKind_(properties) {
52156    var kind = properties["default"] ? 'main' : 'alternative';
52157
52158    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
52159      kind = 'main-desc';
52160    }
52161
52162    return kind;
52163  };
52164  /**
52165   * Pause provided segment loader and playlist loader if active
52166   *
52167   * @param {SegmentLoader} segmentLoader
52168   *        SegmentLoader to pause
52169   * @param {Object} mediaType
52170   *        Active media type
52171   * @function stopLoaders
52172   */
52173
52174
52175  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
52176    segmentLoader.abort();
52177    segmentLoader.pause();
52178
52179    if (mediaType && mediaType.activePlaylistLoader) {
52180      mediaType.activePlaylistLoader.pause();
52181      mediaType.activePlaylistLoader = null;
52182    }
52183  };
52184  /**
52185   * Start loading provided segment loader and playlist loader
52186   *
52187   * @param {PlaylistLoader} playlistLoader
52188   *        PlaylistLoader to start loading
52189   * @param {Object} mediaType
52190   *        Active media type
52191   * @function startLoaders
52192   */
52193
52194
52195  var startLoaders = function startLoaders(playlistLoader, mediaType) {
52196    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
52197    // playlist loader
52198    mediaType.activePlaylistLoader = playlistLoader;
52199    playlistLoader.load();
52200  };
52201  /**
52202   * Returns a function to be called when the media group changes. It performs a
52203   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
52204   * change of group is merely a rendition switch of the same content at another encoding,
52205   * rather than a change of content, such as switching audio from English to Spanish.
52206   *
52207   * @param {String} type
52208   *        MediaGroup type
52209   * @param {Object} settings
52210   *        Object containing required information for media groups
52211   * @return {Function}
52212   *         Handler for a non-destructive resync of SegmentLoader when the active media
52213   *         group changes.
52214   * @function onGroupChanged
52215   */
52216
52217
52218  var onGroupChanged = function onGroupChanged(type, settings) {
52219    return function () {
52220      var _settings$segmentLoad = settings.segmentLoaders,
52221          segmentLoader = _settings$segmentLoad[type],
52222          mainSegmentLoader = _settings$segmentLoad.main,
52223          mediaType = settings.mediaTypes[type];
52224      var activeTrack = mediaType.activeTrack();
52225      var activeGroup = mediaType.activeGroup(activeTrack);
52226      var previousActiveLoader = mediaType.activePlaylistLoader;
52227      stopLoaders(segmentLoader, mediaType);
52228
52229      if (!activeGroup) {
52230        // there is no group active
52231        return;
52232      }
52233
52234      if (!activeGroup.playlistLoader) {
52235        if (previousActiveLoader) {
52236          // The previous group had a playlist loader but the new active group does not
52237          // this means we are switching from demuxed to muxed audio. In this case we want to
52238          // do a destructive reset of the main segment loader and not restart the audio
52239          // loaders.
52240          mainSegmentLoader.resetEverything();
52241        }
52242
52243        return;
52244      } // Non-destructive resync
52245
52246
52247      segmentLoader.resyncLoader();
52248      startLoaders(activeGroup.playlistLoader, mediaType);
52249    };
52250  };
52251  /**
52252   * Returns a function to be called when the media track changes. It performs a
52253   * destructive reset of the SegmentLoader to ensure we start loading as close to
52254   * currentTime as possible.
52255   *
52256   * @param {String} type
52257   *        MediaGroup type
52258   * @param {Object} settings
52259   *        Object containing required information for media groups
52260   * @return {Function}
52261   *         Handler for a destructive reset of SegmentLoader when the active media
52262   *         track changes.
52263   * @function onTrackChanged
52264   */
52265
52266
52267  var onTrackChanged = function onTrackChanged(type, settings) {
52268    return function () {
52269      var _settings$segmentLoad2 = settings.segmentLoaders,
52270          segmentLoader = _settings$segmentLoad2[type],
52271          mainSegmentLoader = _settings$segmentLoad2.main,
52272          mediaType = settings.mediaTypes[type];
52273      var activeTrack = mediaType.activeTrack();
52274      var activeGroup = mediaType.activeGroup(activeTrack);
52275      var previousActiveLoader = mediaType.activePlaylistLoader;
52276      stopLoaders(segmentLoader, mediaType);
52277
52278      if (!activeGroup) {
52279        // there is no group active so we do not want to restart loaders
52280        return;
52281      }
52282
52283      if (!activeGroup.playlistLoader) {
52284        // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
52285        // loader for the audio group), we want to do a destructive reset of the main segment
52286        // loader and not restart the audio loaders
52287        mainSegmentLoader.resetEverything();
52288        return;
52289      }
52290
52291      if (previousActiveLoader === activeGroup.playlistLoader) {
52292        // Nothing has actually changed. This can happen because track change events can fire
52293        // multiple times for a "single" change. One for enabling the new active track, and
52294        // one for disabling the track that was active
52295        startLoaders(activeGroup.playlistLoader, mediaType);
52296        return;
52297      }
52298
52299      if (segmentLoader.track) {
52300        // For WebVTT, set the new text track in the segmentloader
52301        segmentLoader.track(activeTrack);
52302      } // destructive reset
52303
52304
52305      segmentLoader.resetEverything();
52306      startLoaders(activeGroup.playlistLoader, mediaType);
52307    };
52308  };
52309
52310  var onError = {
52311    /**
52312     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
52313     * an error.
52314     *
52315     * @param {String} type
52316     *        MediaGroup type
52317     * @param {Object} settings
52318     *        Object containing required information for media groups
52319     * @return {Function}
52320     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
52321     *         console and switches back to default audio track.
52322     * @function onError.AUDIO
52323     */
52324    AUDIO: function AUDIO(type, settings) {
52325      return function () {
52326        var segmentLoader = settings.segmentLoaders[type],
52327            mediaType = settings.mediaTypes[type],
52328            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
52329        stopLoaders(segmentLoader, mediaType); // switch back to default audio track
52330
52331        var activeTrack = mediaType.activeTrack();
52332        var activeGroup = mediaType.activeGroup();
52333        var id = (activeGroup.filter(function (group) {
52334          return group["default"];
52335        })[0] || activeGroup[0]).id;
52336        var defaultTrack = mediaType.tracks[id];
52337
52338        if (activeTrack === defaultTrack) {
52339          // Default track encountered an error. All we can do now is blacklist the current
52340          // rendition and hope another will switch audio groups
52341          blacklistCurrentPlaylist({
52342            message: 'Problem encountered loading the default audio track.'
52343          });
52344          return;
52345        }
52346
52347        videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
52348
52349        for (var trackId in mediaType.tracks) {
52350          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
52351        }
52352
52353        mediaType.onTrackChanged();
52354      };
52355    },
52356
52357    /**
52358     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
52359     * an error.
52360     *
52361     * @param {String} type
52362     *        MediaGroup type
52363     * @param {Object} settings
52364     *        Object containing required information for media groups
52365     * @return {Function}
52366     *         Error handler. Logs warning to console and disables the active subtitle track
52367     * @function onError.SUBTITLES
52368     */
52369    SUBTITLES: function SUBTITLES(type, settings) {
52370      return function () {
52371        var segmentLoader = settings.segmentLoaders[type],
52372            mediaType = settings.mediaTypes[type];
52373        videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
52374        stopLoaders(segmentLoader, mediaType);
52375        var track = mediaType.activeTrack();
52376
52377        if (track) {
52378          track.mode = 'disabled';
52379        }
52380
52381        mediaType.onTrackChanged();
52382      };
52383    }
52384  };
52385  var setupListeners = {
52386    /**
52387     * Setup event listeners for audio playlist loader
52388     *
52389     * @param {String} type
52390     *        MediaGroup type
52391     * @param {PlaylistLoader|null} playlistLoader
52392     *        PlaylistLoader to register listeners on
52393     * @param {Object} settings
52394     *        Object containing required information for media groups
52395     * @function setupListeners.AUDIO
52396     */
52397    AUDIO: function AUDIO(type, playlistLoader, settings) {
52398      if (!playlistLoader) {
52399        // no playlist loader means audio will be muxed with the video
52400        return;
52401      }
52402
52403      var tech = settings.tech,
52404          requestOptions = settings.requestOptions,
52405          segmentLoader = settings.segmentLoaders[type];
52406      playlistLoader.on('loadedmetadata', function () {
52407        var media = playlistLoader.media();
52408        segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
52409        // permits, start downloading segments
52410
52411        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
52412          segmentLoader.load();
52413        }
52414      });
52415      playlistLoader.on('loadedplaylist', function () {
52416        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
52417
52418        if (!tech.paused()) {
52419          segmentLoader.load();
52420        }
52421      });
52422      playlistLoader.on('error', onError[type](type, settings));
52423    },
52424
52425    /**
52426     * Setup event listeners for subtitle playlist loader
52427     *
52428     * @param {String} type
52429     *        MediaGroup type
52430     * @param {PlaylistLoader|null} playlistLoader
52431     *        PlaylistLoader to register listeners on
52432     * @param {Object} settings
52433     *        Object containing required information for media groups
52434     * @function setupListeners.SUBTITLES
52435     */
52436    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
52437      var tech = settings.tech,
52438          requestOptions = settings.requestOptions,
52439          segmentLoader = settings.segmentLoaders[type],
52440          mediaType = settings.mediaTypes[type];
52441      playlistLoader.on('loadedmetadata', function () {
52442        var media = playlistLoader.media();
52443        segmentLoader.playlist(media, requestOptions);
52444        segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
52445        // permits, start downloading segments
52446
52447        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
52448          segmentLoader.load();
52449        }
52450      });
52451      playlistLoader.on('loadedplaylist', function () {
52452        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
52453
52454        if (!tech.paused()) {
52455          segmentLoader.load();
52456        }
52457      });
52458      playlistLoader.on('error', onError[type](type, settings));
52459    }
52460  };
52461  var initialize = {
52462    /**
52463     * Setup PlaylistLoaders and AudioTracks for the audio groups
52464     *
52465     * @param {String} type
52466     *        MediaGroup type
52467     * @param {Object} settings
52468     *        Object containing required information for media groups
52469     * @function initialize.AUDIO
52470     */
52471    'AUDIO': function AUDIO(type, settings) {
52472      var hls = settings.hls,
52473          sourceType = settings.sourceType,
52474          segmentLoader = settings.segmentLoaders[type],
52475          requestOptions = settings.requestOptions,
52476          mediaGroups = settings.master.mediaGroups,
52477          _settings$mediaTypes$ = settings.mediaTypes[type],
52478          groups = _settings$mediaTypes$.groups,
52479          tracks = _settings$mediaTypes$.tracks,
52480          masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
52481
52482      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
52483        mediaGroups[type] = {
52484          main: {
52485            "default": {
52486              "default": true
52487            }
52488          }
52489        };
52490      }
52491
52492      for (var groupId in mediaGroups[type]) {
52493        if (!groups[groupId]) {
52494          groups[groupId] = [];
52495        } // List of playlists that have an AUDIO attribute value matching the current
52496        // group ID
52497
52498
52499        for (var variantLabel in mediaGroups[type][groupId]) {
52500          var properties = mediaGroups[type][groupId][variantLabel];
52501          var playlistLoader = void 0;
52502
52503          if (properties.resolvedUri) {
52504            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
52505          } else if (properties.playlists && sourceType === 'dash') {
52506            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
52507          } else {
52508            // no resolvedUri means the audio is muxed with the video when using this
52509            // audio track
52510            playlistLoader = null;
52511          }
52512
52513          properties = videojs$1.mergeOptions({
52514            id: variantLabel,
52515            playlistLoader: playlistLoader
52516          }, properties);
52517          setupListeners[type](type, properties.playlistLoader, settings);
52518          groups[groupId].push(properties);
52519
52520          if (typeof tracks[variantLabel] === 'undefined') {
52521            var track = new videojs$1.AudioTrack({
52522              id: variantLabel,
52523              kind: audioTrackKind_(properties),
52524              enabled: false,
52525              language: properties.language,
52526              "default": properties["default"],
52527              label: variantLabel
52528            });
52529            tracks[variantLabel] = track;
52530          }
52531        }
52532      } // setup single error event handler for the segment loader
52533
52534
52535      segmentLoader.on('error', onError[type](type, settings));
52536    },
52537
52538    /**
52539     * Setup PlaylistLoaders and TextTracks for the subtitle groups
52540     *
52541     * @param {String} type
52542     *        MediaGroup type
52543     * @param {Object} settings
52544     *        Object containing required information for media groups
52545     * @function initialize.SUBTITLES
52546     */
52547    'SUBTITLES': function SUBTITLES(type, settings) {
52548      var tech = settings.tech,
52549          hls = settings.hls,
52550          sourceType = settings.sourceType,
52551          segmentLoader = settings.segmentLoaders[type],
52552          requestOptions = settings.requestOptions,
52553          mediaGroups = settings.master.mediaGroups,
52554          _settings$mediaTypes$2 = settings.mediaTypes[type],
52555          groups = _settings$mediaTypes$2.groups,
52556          tracks = _settings$mediaTypes$2.tracks,
52557          masterPlaylistLoader = settings.masterPlaylistLoader;
52558
52559      for (var groupId in mediaGroups[type]) {
52560        if (!groups[groupId]) {
52561          groups[groupId] = [];
52562        }
52563
52564        for (var variantLabel in mediaGroups[type][groupId]) {
52565          if (mediaGroups[type][groupId][variantLabel].forced) {
52566            // Subtitle playlists with the forced attribute are not selectable in Safari.
52567            // According to Apple's HLS Authoring Specification:
52568            //   If content has forced subtitles and regular subtitles in a given language,
52569            //   the regular subtitles track in that language MUST contain both the forced
52570            //   subtitles and the regular subtitles for that language.
52571            // Because of this requirement and that Safari does not add forced subtitles,
52572            // forced subtitles are skipped here to maintain consistent experience across
52573            // all platforms
52574            continue;
52575          }
52576
52577          var properties = mediaGroups[type][groupId][variantLabel];
52578          var playlistLoader = void 0;
52579
52580          if (sourceType === 'hls') {
52581            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
52582          } else if (sourceType === 'dash') {
52583            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
52584          }
52585
52586          properties = videojs$1.mergeOptions({
52587            id: variantLabel,
52588            playlistLoader: playlistLoader
52589          }, properties);
52590          setupListeners[type](type, properties.playlistLoader, settings);
52591          groups[groupId].push(properties);
52592
52593          if (typeof tracks[variantLabel] === 'undefined') {
52594            var track = tech.addRemoteTextTrack({
52595              id: variantLabel,
52596              kind: 'subtitles',
52597              "default": properties["default"] && properties.autoselect,
52598              language: properties.language,
52599              label: variantLabel
52600            }, false).track;
52601            tracks[variantLabel] = track;
52602          }
52603        }
52604      } // setup single error event handler for the segment loader
52605
52606
52607      segmentLoader.on('error', onError[type](type, settings));
52608    },
52609
52610    /**
52611     * Setup TextTracks for the closed-caption groups
52612     *
52613     * @param {String} type
52614     *        MediaGroup type
52615     * @param {Object} settings
52616     *        Object containing required information for media groups
52617     * @function initialize['CLOSED-CAPTIONS']
52618     */
52619    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
52620      var tech = settings.tech,
52621          mediaGroups = settings.master.mediaGroups,
52622          _settings$mediaTypes$3 = settings.mediaTypes[type],
52623          groups = _settings$mediaTypes$3.groups,
52624          tracks = _settings$mediaTypes$3.tracks;
52625
52626      for (var groupId in mediaGroups[type]) {
52627        if (!groups[groupId]) {
52628          groups[groupId] = [];
52629        }
52630
52631        for (var variantLabel in mediaGroups[type][groupId]) {
52632          var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
52633          // doesn't use a CCx INSTREAM-ID
52634
52635          if (!properties.instreamId.match(/CC\d/)) {
52636            continue;
52637          } // No PlaylistLoader is required for Closed-Captions because the captions are
52638          // embedded within the video stream
52639
52640
52641          groups[groupId].push(videojs$1.mergeOptions({
52642            id: variantLabel
52643          }, properties));
52644
52645          if (typeof tracks[variantLabel] === 'undefined') {
52646            var track = tech.addRemoteTextTrack({
52647              id: properties.instreamId,
52648              kind: 'captions',
52649              "default": properties["default"] && properties.autoselect,
52650              language: properties.language,
52651              label: variantLabel
52652            }, false).track;
52653            tracks[variantLabel] = track;
52654          }
52655        }
52656      }
52657    }
52658  };
52659  /**
52660   * Returns a function used to get the active group of the provided type
52661   *
52662   * @param {String} type
52663   *        MediaGroup type
52664   * @param {Object} settings
52665   *        Object containing required information for media groups
52666   * @return {Function}
52667   *         Function that returns the active media group for the provided type. Takes an
52668   *         optional parameter {TextTrack} track. If no track is provided, a list of all
52669   *         variants in the group, otherwise the variant corresponding to the provided
52670   *         track is returned.
52671   * @function activeGroup
52672   */
52673
52674  var activeGroup = function activeGroup(type, settings) {
52675    return function (track) {
52676      var masterPlaylistLoader = settings.masterPlaylistLoader,
52677          groups = settings.mediaTypes[type].groups;
52678      var media = masterPlaylistLoader.media();
52679
52680      if (!media) {
52681        return null;
52682      }
52683
52684      var variants = null;
52685
52686      if (media.attributes[type]) {
52687        variants = groups[media.attributes[type]];
52688      }
52689
52690      variants = variants || groups.main;
52691
52692      if (typeof track === 'undefined') {
52693        return variants;
52694      }
52695
52696      if (track === null) {
52697        // An active track was specified so a corresponding group is expected. track === null
52698        // means no track is currently active so there is no corresponding group
52699        return null;
52700      }
52701
52702      return variants.filter(function (props) {
52703        return props.id === track.id;
52704      })[0] || null;
52705    };
52706  };
52707
52708  var activeTrack = {
52709    /**
52710     * Returns a function used to get the active track of type provided
52711     *
52712     * @param {String} type
52713     *        MediaGroup type
52714     * @param {Object} settings
52715     *        Object containing required information for media groups
52716     * @return {Function}
52717     *         Function that returns the active media track for the provided type. Returns
52718     *         null if no track is active
52719     * @function activeTrack.AUDIO
52720     */
52721    AUDIO: function AUDIO(type, settings) {
52722      return function () {
52723        var tracks = settings.mediaTypes[type].tracks;
52724
52725        for (var id in tracks) {
52726          if (tracks[id].enabled) {
52727            return tracks[id];
52728          }
52729        }
52730
52731        return null;
52732      };
52733    },
52734
52735    /**
52736     * Returns a function used to get the active track of type provided
52737     *
52738     * @param {String} type
52739     *        MediaGroup type
52740     * @param {Object} settings
52741     *        Object containing required information for media groups
52742     * @return {Function}
52743     *         Function that returns the active media track for the provided type. Returns
52744     *         null if no track is active
52745     * @function activeTrack.SUBTITLES
52746     */
52747    SUBTITLES: function SUBTITLES(type, settings) {
52748      return function () {
52749        var tracks = settings.mediaTypes[type].tracks;
52750
52751        for (var id in tracks) {
52752          if (tracks[id].mode === 'showing') {
52753            return tracks[id];
52754          }
52755        }
52756
52757        return null;
52758      };
52759    }
52760  };
52761  /**
52762   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
52763   * Closed-Captions) specified in the master manifest.
52764   *
52765   * @param {Object} settings
52766   *        Object containing required information for setting up the media groups
52767   * @param {SegmentLoader} settings.segmentLoaders.AUDIO
52768   *        Audio segment loader
52769   * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
52770   *        Subtitle segment loader
52771   * @param {SegmentLoader} settings.segmentLoaders.main
52772   *        Main segment loader
52773   * @param {Tech} settings.tech
52774   *        The tech of the player
52775   * @param {Object} settings.requestOptions
52776   *        XHR request options used by the segment loaders
52777   * @param {PlaylistLoader} settings.masterPlaylistLoader
52778   *        PlaylistLoader for the master source
52779   * @param {HlsHandler} settings.hls
52780   *        HLS SourceHandler
52781   * @param {Object} settings.master
52782   *        The parsed master manifest
52783   * @param {Object} settings.mediaTypes
52784   *        Object to store the loaders, tracks, and utility methods for each media type
52785   * @param {Function} settings.blacklistCurrentPlaylist
52786   *        Blacklists the current rendition and forces a rendition switch.
52787   * @function setupMediaGroups
52788   */
52789
52790  var setupMediaGroups = function setupMediaGroups(settings) {
52791    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
52792      initialize[type](type, settings);
52793    });
52794    var mediaTypes = settings.mediaTypes,
52795        masterPlaylistLoader = settings.masterPlaylistLoader,
52796        tech = settings.tech,
52797        hls = settings.hls; // setup active group and track getters and change event handlers
52798
52799    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
52800      mediaTypes[type].activeGroup = activeGroup(type, settings);
52801      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
52802      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
52803      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
52804    }); // DO NOT enable the default subtitle or caption track.
52805    // DO enable the default audio track
52806
52807    var audioGroup = mediaTypes.AUDIO.activeGroup();
52808    var groupId = (audioGroup.filter(function (group) {
52809      return group["default"];
52810    })[0] || audioGroup[0]).id;
52811    mediaTypes.AUDIO.tracks[groupId].enabled = true;
52812    mediaTypes.AUDIO.onTrackChanged();
52813    masterPlaylistLoader.on('mediachange', function () {
52814      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
52815        return mediaTypes[type].onGroupChanged();
52816      });
52817    }); // custom audio track change event handler for usage event
52818
52819    var onAudioTrackChanged = function onAudioTrackChanged() {
52820      mediaTypes.AUDIO.onTrackChanged();
52821      tech.trigger({
52822        type: 'usage',
52823        name: 'hls-audio-change'
52824      });
52825    };
52826
52827    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
52828    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
52829    hls.on('dispose', function () {
52830      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
52831      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
52832    }); // clear existing audio tracks and add the ones we just created
52833
52834    tech.clearTracks('audio');
52835
52836    for (var id in mediaTypes.AUDIO.tracks) {
52837      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
52838    }
52839  };
52840  /**
52841   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
52842   * media type
52843   *
52844   * @return {Object}
52845   *         Object to store the loaders, tracks, and utility methods for each media type
52846   * @function createMediaTypes
52847   */
52848
52849
52850  var createMediaTypes = function createMediaTypes() {
52851    var mediaTypes = {};
52852    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
52853      mediaTypes[type] = {
52854        groups: {},
52855        tracks: {},
52856        activePlaylistLoader: null,
52857        activeGroup: noop$1,
52858        activeTrack: noop$1,
52859        onGroupChanged: noop$1,
52860        onTrackChanged: noop$1
52861      };
52862    });
52863    return mediaTypes;
52864  };
52865  /**
52866   * @file master-playlist-controller.js
52867   */
52868
52869
52870  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
52871  var Hls = void 0; // SegmentLoader stats that need to have each loader's
52872  // values summed to calculate the final value
52873
52874  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
52875
52876  var sumLoaderStat = function sumLoaderStat(stat) {
52877    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
52878  };
52879  /**
52880   * the master playlist controller controller all interactons
52881   * between playlists and segmentloaders. At this time this mainly
52882   * involves a master playlist and a series of audio playlists
52883   * if they are available
52884   *
52885   * @class MasterPlaylistController
52886   * @extends videojs.EventTarget
52887   */
52888
52889
52890  var MasterPlaylistController = function (_videojs$EventTarget) {
52891    inherits$2(MasterPlaylistController, _videojs$EventTarget);
52892
52893    function MasterPlaylistController(options) {
52894      classCallCheck$1(this, MasterPlaylistController);
52895
52896      var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
52897
52898      var url = options.url,
52899          handleManifestRedirects = options.handleManifestRedirects,
52900          withCredentials = options.withCredentials,
52901          tech = options.tech,
52902          bandwidth = options.bandwidth,
52903          externHls = options.externHls,
52904          useCueTags = options.useCueTags,
52905          blacklistDuration = options.blacklistDuration,
52906          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
52907          sourceType = options.sourceType,
52908          seekTo = options.seekTo,
52909          cacheEncryptionKeys = options.cacheEncryptionKeys;
52910
52911      if (!url) {
52912        throw new Error('A non-empty playlist URL is required');
52913      }
52914
52915      Hls = externHls;
52916      _this.withCredentials = withCredentials;
52917      _this.tech_ = tech;
52918      _this.hls_ = tech.hls;
52919      _this.seekTo_ = seekTo;
52920      _this.sourceType_ = sourceType;
52921      _this.useCueTags_ = useCueTags;
52922      _this.blacklistDuration = blacklistDuration;
52923      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
52924
52925      if (_this.useCueTags_) {
52926        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
52927        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
52928      }
52929
52930      _this.requestOptions_ = {
52931        withCredentials: withCredentials,
52932        handleManifestRedirects: handleManifestRedirects,
52933        timeout: null
52934      };
52935      _this.mediaTypes_ = createMediaTypes();
52936      _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
52937
52938      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
52939
52940      _this.seekable_ = videojs$1.createTimeRanges();
52941
52942      _this.hasPlayed_ = function () {
52943        return false;
52944      };
52945
52946      _this.syncController_ = new SyncController(options);
52947      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
52948        kind: 'metadata',
52949        label: 'segment-metadata'
52950      }, false).track;
52951      _this.decrypter_ = new Decrypter$1();
52952      _this.inbandTextTracks_ = {};
52953      var segmentLoaderSettings = {
52954        hls: _this.hls_,
52955        mediaSource: _this.mediaSource,
52956        currentTime: _this.tech_.currentTime.bind(_this.tech_),
52957        seekable: function seekable$$1() {
52958          return _this.seekable();
52959        },
52960        seeking: function seeking() {
52961          return _this.tech_.seeking();
52962        },
52963        duration: function duration$$1() {
52964          return _this.mediaSource.duration;
52965        },
52966        hasPlayed: function hasPlayed() {
52967          return _this.hasPlayed_();
52968        },
52969        goalBufferLength: function goalBufferLength() {
52970          return _this.goalBufferLength();
52971        },
52972        bandwidth: bandwidth,
52973        syncController: _this.syncController_,
52974        decrypter: _this.decrypter_,
52975        sourceType: _this.sourceType_,
52976        inbandTextTracks: _this.inbandTextTracks_,
52977        cacheEncryptionKeys: cacheEncryptionKeys
52978      };
52979      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
52980
52981      _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
52982      // combined audio/video or just video when alternate audio track is selected
52983
52984
52985      _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
52986        segmentMetadataTrack: _this.segmentMetadataTrack_,
52987        loaderType: 'main'
52988      }), options); // alternate audio track
52989
52990      _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
52991        loaderType: 'audio'
52992      }), options);
52993      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
52994        loaderType: 'vtt'
52995      }), options);
52996
52997      _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
52998
52999
53000      loaderStats.forEach(function (stat) {
53001        _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
53002      });
53003      _this.logger_ = logger('MPC');
53004
53005      _this.masterPlaylistLoader_.load();
53006
53007      return _this;
53008    }
53009    /**
53010     * Register event handlers on the master playlist loader. A helper
53011     * function for construction time.
53012     *
53013     * @private
53014     */
53015
53016
53017    createClass$1(MasterPlaylistController, [{
53018      key: 'setupMasterPlaylistLoaderListeners_',
53019      value: function setupMasterPlaylistLoaderListeners_() {
53020        var _this2 = this;
53021
53022        this.masterPlaylistLoader_.on('loadedmetadata', function () {
53023          var media = _this2.masterPlaylistLoader_.media();
53024
53025          var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
53026          // timeout the request.
53027
53028          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
53029            _this2.requestOptions_.timeout = 0;
53030          } else {
53031            _this2.requestOptions_.timeout = requestTimeout;
53032          } // if this isn't a live video and preload permits, start
53033          // downloading segments
53034
53035
53036          if (media.endList && _this2.tech_.preload() !== 'none') {
53037            _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
53038
53039            _this2.mainSegmentLoader_.load();
53040          }
53041
53042          setupMediaGroups({
53043            sourceType: _this2.sourceType_,
53044            segmentLoaders: {
53045              AUDIO: _this2.audioSegmentLoader_,
53046              SUBTITLES: _this2.subtitleSegmentLoader_,
53047              main: _this2.mainSegmentLoader_
53048            },
53049            tech: _this2.tech_,
53050            requestOptions: _this2.requestOptions_,
53051            masterPlaylistLoader: _this2.masterPlaylistLoader_,
53052            hls: _this2.hls_,
53053            master: _this2.master(),
53054            mediaTypes: _this2.mediaTypes_,
53055            blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
53056          });
53057
53058          _this2.triggerPresenceUsage_(_this2.master(), media);
53059
53060          try {
53061            _this2.setupSourceBuffers_();
53062          } catch (e) {
53063            videojs$1.log.warn('Failed to create SourceBuffers', e);
53064            return _this2.mediaSource.endOfStream('decode');
53065          }
53066
53067          _this2.setupFirstPlay();
53068
53069          if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
53070            _this2.trigger('selectedinitialmedia');
53071          } else {
53072            // We must wait for the active audio playlist loader to
53073            // finish setting up before triggering this event so the
53074            // representations API and EME setup is correct
53075            _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
53076              _this2.trigger('selectedinitialmedia');
53077            });
53078          }
53079        });
53080        this.masterPlaylistLoader_.on('loadedplaylist', function () {
53081          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
53082
53083          if (!updatedPlaylist) {
53084            // blacklist any variants that are not supported by the browser before selecting
53085            // an initial media as the playlist selectors do not consider browser support
53086            _this2.excludeUnsupportedVariants_();
53087
53088            var selectedMedia = void 0;
53089
53090            if (_this2.enableLowInitialPlaylist) {
53091              selectedMedia = _this2.selectInitialPlaylist();
53092            }
53093
53094            if (!selectedMedia) {
53095              selectedMedia = _this2.selectPlaylist();
53096            }
53097
53098            _this2.initialMedia_ = selectedMedia;
53099
53100            _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
53101
53102            return;
53103          }
53104
53105          if (_this2.useCueTags_) {
53106            _this2.updateAdCues_(updatedPlaylist);
53107          } // TODO: Create a new event on the PlaylistLoader that signals
53108          // that the segments have changed in some way and use that to
53109          // update the SegmentLoader instead of doing it twice here and
53110          // on `mediachange`
53111
53112
53113          _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
53114
53115          _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
53116          // as it is possible that it was temporarily stopped while waiting for
53117          // a playlist (e.g., in case the playlist errored and we re-requested it).
53118
53119
53120          if (!_this2.tech_.paused()) {
53121            _this2.mainSegmentLoader_.load();
53122
53123            if (_this2.audioSegmentLoader_) {
53124              _this2.audioSegmentLoader_.load();
53125            }
53126          }
53127
53128          if (!updatedPlaylist.endList) {
53129            var addSeekableRange = function addSeekableRange() {
53130              var seekable$$1 = _this2.seekable();
53131
53132              if (seekable$$1.length !== 0) {
53133                _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
53134              }
53135            };
53136
53137            if (_this2.duration() !== Infinity) {
53138              var onDurationchange = function onDurationchange() {
53139                if (_this2.duration() === Infinity) {
53140                  addSeekableRange();
53141                } else {
53142                  _this2.tech_.one('durationchange', onDurationchange);
53143                }
53144              };
53145
53146              _this2.tech_.one('durationchange', onDurationchange);
53147            } else {
53148              addSeekableRange();
53149            }
53150          }
53151        });
53152        this.masterPlaylistLoader_.on('error', function () {
53153          _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
53154        });
53155        this.masterPlaylistLoader_.on('mediachanging', function () {
53156          _this2.mainSegmentLoader_.abort();
53157
53158          _this2.mainSegmentLoader_.pause();
53159        });
53160        this.masterPlaylistLoader_.on('mediachange', function () {
53161          var media = _this2.masterPlaylistLoader_.media();
53162
53163          var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
53164          // timeout the request.
53165
53166          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
53167            _this2.requestOptions_.timeout = 0;
53168          } else {
53169            _this2.requestOptions_.timeout = requestTimeout;
53170          } // TODO: Create a new event on the PlaylistLoader that signals
53171          // that the segments have changed in some way and use that to
53172          // update the SegmentLoader instead of doing it twice here and
53173          // on `loadedplaylist`
53174
53175
53176          _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
53177
53178          _this2.mainSegmentLoader_.load();
53179
53180          _this2.tech_.trigger({
53181            type: 'mediachange',
53182            bubbles: true
53183          });
53184        });
53185        this.masterPlaylistLoader_.on('playlistunchanged', function () {
53186          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
53187
53188          var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
53189
53190          if (playlistOutdated) {
53191            // Playlist has stopped updating and we're stuck at its end. Try to
53192            // blacklist it and switch to another playlist in the hope that that
53193            // one is updating (and give the player a chance to re-adjust to the
53194            // safe live point).
53195            _this2.blacklistCurrentPlaylist({
53196              message: 'Playlist no longer updating.'
53197            }); // useful for monitoring QoS
53198
53199
53200            _this2.tech_.trigger('playliststuck');
53201          }
53202        });
53203        this.masterPlaylistLoader_.on('renditiondisabled', function () {
53204          _this2.tech_.trigger({
53205            type: 'usage',
53206            name: 'hls-rendition-disabled'
53207          });
53208        });
53209        this.masterPlaylistLoader_.on('renditionenabled', function () {
53210          _this2.tech_.trigger({
53211            type: 'usage',
53212            name: 'hls-rendition-enabled'
53213          });
53214        });
53215      }
53216      /**
53217       * A helper function for triggerring presence usage events once per source
53218       *
53219       * @private
53220       */
53221
53222    }, {
53223      key: 'triggerPresenceUsage_',
53224      value: function triggerPresenceUsage_(master, media) {
53225        var mediaGroups = master.mediaGroups || {};
53226        var defaultDemuxed = true;
53227        var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
53228
53229        for (var mediaGroup in mediaGroups.AUDIO) {
53230          for (var label in mediaGroups.AUDIO[mediaGroup]) {
53231            var properties = mediaGroups.AUDIO[mediaGroup][label];
53232
53233            if (!properties.uri) {
53234              defaultDemuxed = false;
53235            }
53236          }
53237        }
53238
53239        if (defaultDemuxed) {
53240          this.tech_.trigger({
53241            type: 'usage',
53242            name: 'hls-demuxed'
53243          });
53244        }
53245
53246        if (Object.keys(mediaGroups.SUBTITLES).length) {
53247          this.tech_.trigger({
53248            type: 'usage',
53249            name: 'hls-webvtt'
53250          });
53251        }
53252
53253        if (Hls.Playlist.isAes(media)) {
53254          this.tech_.trigger({
53255            type: 'usage',
53256            name: 'hls-aes'
53257          });
53258        }
53259
53260        if (Hls.Playlist.isFmp4(media)) {
53261          this.tech_.trigger({
53262            type: 'usage',
53263            name: 'hls-fmp4'
53264          });
53265        }
53266
53267        if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
53268          this.tech_.trigger({
53269            type: 'usage',
53270            name: 'hls-alternate-audio'
53271          });
53272        }
53273
53274        if (this.useCueTags_) {
53275          this.tech_.trigger({
53276            type: 'usage',
53277            name: 'hls-playlist-cue-tags'
53278          });
53279        }
53280      }
53281      /**
53282       * Register event handlers on the segment loaders. A helper function
53283       * for construction time.
53284       *
53285       * @private
53286       */
53287
53288    }, {
53289      key: 'setupSegmentLoaderListeners_',
53290      value: function setupSegmentLoaderListeners_() {
53291        var _this3 = this;
53292
53293        this.mainSegmentLoader_.on('bandwidthupdate', function () {
53294          var nextPlaylist = _this3.selectPlaylist();
53295
53296          var currentPlaylist = _this3.masterPlaylistLoader_.media();
53297
53298          var buffered = _this3.tech_.buffered();
53299
53300          var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
53301
53302          var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
53303          // This is because in LIVE, the player plays 3 segments from the end of the
53304          // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
53305          // in those segments, a viewer will never experience a rendition upswitch.
53306
53307
53308          if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
53309          // duration is below the max potential low water line
53310          _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
53311          nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
53312          // buffer while loading a higher rendition.
53313          forwardBuffer >= bufferLowWaterLine) {
53314            _this3.masterPlaylistLoader_.media(nextPlaylist);
53315          }
53316
53317          _this3.tech_.trigger('bandwidthupdate');
53318        });
53319        this.mainSegmentLoader_.on('progress', function () {
53320          _this3.trigger('progress');
53321        });
53322        this.mainSegmentLoader_.on('error', function () {
53323          _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
53324        });
53325        this.mainSegmentLoader_.on('syncinfoupdate', function () {
53326          _this3.onSyncInfoUpdate_();
53327        });
53328        this.mainSegmentLoader_.on('timestampoffset', function () {
53329          _this3.tech_.trigger({
53330            type: 'usage',
53331            name: 'hls-timestamp-offset'
53332          });
53333        });
53334        this.audioSegmentLoader_.on('syncinfoupdate', function () {
53335          _this3.onSyncInfoUpdate_();
53336        });
53337        this.mainSegmentLoader_.on('ended', function () {
53338          _this3.onEndOfStream();
53339        });
53340        this.mainSegmentLoader_.on('earlyabort', function () {
53341          _this3.blacklistCurrentPlaylist({
53342            message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
53343          }, ABORT_EARLY_BLACKLIST_SECONDS);
53344        });
53345        this.mainSegmentLoader_.on('reseteverything', function () {
53346          // If playing an MTS stream, a videojs.MediaSource is listening for
53347          // hls-reset to reset caption parsing state in the transmuxer
53348          _this3.tech_.trigger('hls-reset');
53349        });
53350        this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
53351          // If playing an MTS stream in html, a videojs.MediaSource is listening for
53352          // hls-segment-time-mapping update its internal mapping of stream to display time
53353          _this3.tech_.trigger({
53354            type: 'hls-segment-time-mapping',
53355            mapping: event.mapping
53356          });
53357        });
53358        this.audioSegmentLoader_.on('ended', function () {
53359          _this3.onEndOfStream();
53360        });
53361      }
53362    }, {
53363      key: 'mediaSecondsLoaded_',
53364      value: function mediaSecondsLoaded_() {
53365        return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
53366      }
53367      /**
53368       * Call load on our SegmentLoaders
53369       */
53370
53371    }, {
53372      key: 'load',
53373      value: function load() {
53374        this.mainSegmentLoader_.load();
53375
53376        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
53377          this.audioSegmentLoader_.load();
53378        }
53379
53380        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
53381          this.subtitleSegmentLoader_.load();
53382        }
53383      }
53384      /**
53385       * Re-tune playback quality level for the current player
53386       * conditions without performing destructive actions, like
53387       * removing already buffered content
53388       *
53389       * @private
53390       */
53391
53392    }, {
53393      key: 'smoothQualityChange_',
53394      value: function smoothQualityChange_() {
53395        var media = this.selectPlaylist();
53396
53397        if (media !== this.masterPlaylistLoader_.media()) {
53398          this.masterPlaylistLoader_.media(media);
53399          this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
53400        }
53401      }
53402      /**
53403       * Re-tune playback quality level for the current player
53404       * conditions. This method will perform destructive actions like removing
53405       * already buffered content in order to readjust the currently active
53406       * playlist quickly. This is good for manual quality changes
53407       *
53408       * @private
53409       */
53410
53411    }, {
53412      key: 'fastQualityChange_',
53413      value: function fastQualityChange_() {
53414        var _this4 = this;
53415
53416        var media = this.selectPlaylist();
53417
53418        if (media === this.masterPlaylistLoader_.media()) {
53419          return;
53420        }
53421
53422        this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
53423        // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
53424        // ahead is roughly the minimum that will accomplish this across a variety of content
53425        // in IE and Edge, but seeking in place is sufficient on all other browsers)
53426        // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
53427        // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
53428
53429        this.mainSegmentLoader_.resetEverything(function () {
53430          // Since this is not a typical seek, we avoid the seekTo method which can cause segments
53431          // from the previously enabled rendition to load before the new playlist has finished loading
53432          if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
53433            _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
53434          } else {
53435            _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
53436          }
53437        }); // don't need to reset audio as it is reset when media changes
53438      }
53439      /**
53440       * Begin playback.
53441       */
53442
53443    }, {
53444      key: 'play',
53445      value: function play() {
53446        if (this.setupFirstPlay()) {
53447          return;
53448        }
53449
53450        if (this.tech_.ended()) {
53451          this.seekTo_(0);
53452        }
53453
53454        if (this.hasPlayed_()) {
53455          this.load();
53456        }
53457
53458        var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
53459        // seek forward to the live point
53460
53461        if (this.tech_.duration() === Infinity) {
53462          if (this.tech_.currentTime() < seekable$$1.start(0)) {
53463            return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
53464          }
53465        }
53466      }
53467      /**
53468       * Seek to the latest media position if this is a live video and the
53469       * player and video are loaded and initialized.
53470       */
53471
53472    }, {
53473      key: 'setupFirstPlay',
53474      value: function setupFirstPlay() {
53475        var _this5 = this;
53476
53477        var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
53478        //  If 1) there is no active media
53479        //     2) the player is paused
53480        //     3) the first play has already been setup
53481        // then exit early
53482
53483        if (!media || this.tech_.paused() || this.hasPlayed_()) {
53484          return false;
53485        } // when the video is a live stream
53486
53487
53488        if (!media.endList) {
53489          var seekable$$1 = this.seekable();
53490
53491          if (!seekable$$1.length) {
53492            // without a seekable range, the player cannot seek to begin buffering at the live
53493            // point
53494            return false;
53495          }
53496
53497          if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
53498            // IE11 throws an InvalidStateError if you try to set currentTime while the
53499            // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
53500            this.tech_.one('loadedmetadata', function () {
53501              _this5.trigger('firstplay');
53502
53503              _this5.seekTo_(seekable$$1.end(0));
53504
53505              _this5.hasPlayed_ = function () {
53506                return true;
53507              };
53508            });
53509            return false;
53510          } // trigger firstplay to inform the source handler to ignore the next seek event
53511
53512
53513          this.trigger('firstplay'); // seek to the live point
53514
53515          this.seekTo_(seekable$$1.end(0));
53516        }
53517
53518        this.hasPlayed_ = function () {
53519          return true;
53520        }; // we can begin loading now that everything is ready
53521
53522
53523        this.load();
53524        return true;
53525      }
53526      /**
53527       * handle the sourceopen event on the MediaSource
53528       *
53529       * @private
53530       */
53531
53532    }, {
53533      key: 'handleSourceOpen_',
53534      value: function handleSourceOpen_() {
53535        // Only attempt to create the source buffer if none already exist.
53536        // handleSourceOpen is also called when we are "re-opening" a source buffer
53537        // after `endOfStream` has been called (in response to a seek for instance)
53538        try {
53539          this.setupSourceBuffers_();
53540        } catch (e) {
53541          videojs$1.log.warn('Failed to create Source Buffers', e);
53542          return this.mediaSource.endOfStream('decode');
53543        } // if autoplay is enabled, begin playback. This is duplicative of
53544        // code in video.js but is required because play() must be invoked
53545        // *after* the media source has opened.
53546
53547
53548        if (this.tech_.autoplay()) {
53549          var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
53550          // on browsers which return a promise
53551
53552          if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
53553            playPromise.then(null, function (e) {});
53554          }
53555        }
53556
53557        this.trigger('sourceopen');
53558      }
53559      /**
53560       * Calls endOfStream on the media source when all active stream types have called
53561       * endOfStream
53562       *
53563       * @param {string} streamType
53564       *        Stream type of the segment loader that called endOfStream
53565       * @private
53566       */
53567
53568    }, {
53569      key: 'onEndOfStream',
53570      value: function onEndOfStream() {
53571        var isEndOfStream = this.mainSegmentLoader_.ended_;
53572
53573        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
53574          // if the audio playlist loader exists, then alternate audio is active
53575          if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
53576            // if we do not know if the main segment loader contains video yet or if we
53577            // definitively know the main segment loader contains video, then we need to wait
53578            // for both main and audio segment loaders to call endOfStream
53579            isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
53580          } else {
53581            // otherwise just rely on the audio loader
53582            isEndOfStream = this.audioSegmentLoader_.ended_;
53583          }
53584        }
53585
53586        if (!isEndOfStream) {
53587          return;
53588        }
53589
53590        this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
53591        // even when the media source is in a valid state.
53592
53593        try {
53594          this.mediaSource.endOfStream();
53595        } catch (e) {
53596          videojs$1.log.warn('Failed to call media source endOfStream', e);
53597        }
53598      }
53599      /**
53600       * Check if a playlist has stopped being updated
53601       * @param {Object} playlist the media playlist object
53602       * @return {boolean} whether the playlist has stopped being updated or not
53603       */
53604
53605    }, {
53606      key: 'stuckAtPlaylistEnd_',
53607      value: function stuckAtPlaylistEnd_(playlist) {
53608        var seekable$$1 = this.seekable();
53609
53610        if (!seekable$$1.length) {
53611          // playlist doesn't have enough information to determine whether we are stuck
53612          return false;
53613        }
53614
53615        var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
53616
53617        if (expired === null) {
53618          return false;
53619        } // does not use the safe live end to calculate playlist end, since we
53620        // don't want to say we are stuck while there is still content
53621
53622
53623        var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
53624        var currentTime = this.tech_.currentTime();
53625        var buffered = this.tech_.buffered();
53626
53627        if (!buffered.length) {
53628          // return true if the playhead reached the absolute end of the playlist
53629          return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
53630        }
53631
53632        var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
53633        // end of playlist
53634
53635        return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
53636      }
53637      /**
53638       * Blacklists a playlist when an error occurs for a set amount of time
53639       * making it unavailable for selection by the rendition selection algorithm
53640       * and then forces a new playlist (rendition) selection.
53641       *
53642       * @param {Object=} error an optional error that may include the playlist
53643       * to blacklist
53644       * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
53645       * playlist
53646       */
53647
53648    }, {
53649      key: 'blacklistCurrentPlaylist',
53650      value: function blacklistCurrentPlaylist() {
53651        var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
53652        var blacklistDuration = arguments[1];
53653        var currentPlaylist = void 0;
53654        var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
53655        // the playlist we were trying to load (but failed) and that should be
53656        // blacklisted instead of the currently selected playlist which is likely
53657        // out-of-date in this scenario
53658
53659        currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
53660        blacklistDuration = blacklistDuration || error.blacklistDuration || this.blackl
53660istDuration; // If there is no current playlist, then an error occurred while we were
53661        // trying to load the master OR while we were disposing of the tech
53662
53663        if (!currentPlaylist) {
53664          this.error = error;
53665
53666          try {
53667            return this.mediaSource.endOfStream('network');
53668          } catch (e) {
53669            return this.trigger('error');
53670          }
53671        }
53672
53673        var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
53674        var playlists = this.masterPlaylistLoader_.master.playlists;
53675
53676        if (playlists.length === 1) {
53677          // Never blacklisting this playlist because it's the only playlist
53678          videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the only playlist.');
53679          this.tech_.trigger('retryplaylist');
53680          return this.masterPlaylistLoader_.load(isFinalRendition);
53681        }
53682
53683        if (isFinalRendition) {
53684          // Since we're on the final non-blacklisted playlist, and we're about to blacklist
53685          // it, instead of erring the player or retrying this playlist, clear out the current
53686          // blacklist. This allows other playlists to be attempted in case any have been
53687          // fixed.
53688          videojs$1.log.warn('Removing all playlists from the blacklist because the last ' + 'rendition is about to be blacklisted.');
vendor: 11,849 bytes, lines 53689-54031
53689          playlists.forEach(function (playlist) {
53690            if (playlist.excludeUntil !== Infinity) {
53691              delete playlist.excludeUntil;
53692            }
53693          }); // Technically we are retrying a playlist, in that we are simply retrying a previous
53694          // playlist. This is needed for users relying on the retryplaylist event to catch a
53695          // case where the player might be stuck and looping through "dead" playlists.
53696
53697          this.tech_.trigger('retryplaylist');
53698        } // Blacklist this playlist
53699
53700
53701        currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
53702        this.tech_.trigger('blacklistplaylist');
53703        this.tech_.trigger({
53704          type: 'usage',
53705          name: 'hls-rendition-blacklisted'
53706        }); // Select a new playlist
53707
53708        nextPlaylist = this.selectPlaylist();
53709        videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
53710        return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition);
53711      }
53712      /**
53713       * Pause all segment loaders
53714       */
53715
53716    }, {
53717      key: 'pauseLoading',
53718      value: function pauseLoading() {
53719        this.mainSegmentLoader_.pause();
53720
53721        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
53722          this.audioSegmentLoader_.pause();
53723        }
53724
53725        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
53726          this.subtitleSegmentLoader_.pause();
53727        }
53728      }
53729      /**
53730       * set the current time on all segment loaders
53731       *
53732       * @param {TimeRange} currentTime the current time to set
53733       * @return {TimeRange} the current time
53734       */
53735
53736    }, {
53737      key: 'setCurrentTime',
53738      value: function setCurrentTime(currentTime) {
53739        var buffered = findRange(this.tech_.buffered(), currentTime);
53740
53741        if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
53742          // return immediately if the metadata is not ready yet
53743          return 0;
53744        } // it's clearly an edge-case but don't thrown an error if asked to
53745        // seek within an empty playlist
53746
53747
53748        if (!this.masterPlaylistLoader_.media().segments) {
53749          return 0;
53750        } // In flash playback, the segment loaders should be reset on every seek, even
53751        // in buffer seeks. If the seek location is already buffered, continue buffering as
53752        // usual
53753        // TODO: redo this comment
53754
53755
53756        if (buffered && buffered.length) {
53757          return currentTime;
53758        } // cancel outstanding requests so we begin buffering at the new
53759        // location
53760
53761
53762        this.mainSegmentLoader_.resetEverything();
53763        this.mainSegmentLoader_.abort();
53764
53765        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
53766          this.audioSegmentLoader_.resetEverything();
53767          this.audioSegmentLoader_.abort();
53768        }
53769
53770        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
53771          this.subtitleSegmentLoader_.resetEverything();
53772          this.subtitleSegmentLoader_.abort();
53773        } // start segment loader loading in case they are paused
53774
53775
53776        this.load();
53777      }
53778      /**
53779       * get the current duration
53780       *
53781       * @return {TimeRange} the duration
53782       */
53783
53784    }, {
53785      key: 'duration',
53786      value: function duration$$1() {
53787        if (!this.masterPlaylistLoader_) {
53788          return 0;
53789        }
53790
53791        if (this.mediaSource) {
53792          return this.mediaSource.duration;
53793        }
53794
53795        return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
53796      }
53797      /**
53798       * check the seekable range
53799       *
53800       * @return {TimeRange} the seekable range
53801       */
53802
53803    }, {
53804      key: 'seekable',
53805      value: function seekable$$1() {
53806        return this.seekable_;
53807      }
53808    }, {
53809      key: 'onSyncInfoUpdate_',
53810      value: function onSyncInfoUpdate_() {
53811        var mainSeekable = void 0;
53812        var audioSeekable = void 0;
53813
53814        if (!this.masterPlaylistLoader_) {
53815          return;
53816        }
53817
53818        var media = this.masterPlaylistLoader_.media();
53819
53820        if (!media) {
53821          return;
53822        }
53823
53824        var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
53825
53826        if (expired === null) {
53827          // not enough information to update seekable
53828          return;
53829        }
53830
53831        mainSeekable = Hls.Playlist.seekable(media, expired);
53832
53833        if (mainSeekable.length === 0) {
53834          return;
53835        }
53836
53837        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
53838          media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
53839          expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
53840
53841          if (expired === null) {
53842            return;
53843          }
53844
53845          audioSeekable = Hls.Playlist.seekable(media, expired);
53846
53847          if (audioSeekable.length === 0) {
53848            return;
53849          }
53850        }
53851
53852        var oldEnd = void 0;
53853        var oldStart = void 0;
53854
53855        if (this.seekable_ && this.seekable_.length) {
53856          oldEnd = this.seekable_.end(0);
53857          oldStart = this.seekable_.start(0);
53858        }
53859
53860        if (!audioSeekable) {
53861          // seekable has been calculated based on buffering video data so it
53862          // can be returned directly
53863          this.seekable_ = mainSeekable;
53864        } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
53865          // seekables are pretty far off, rely on main
53866          this.seekable_ = mainSeekable;
53867        } else {
53868          this.seekable_ = videojs$1.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
53869        } // seekable is the same as last time
53870
53871
53872        if (this.seekable_ && this.seekable_.length) {
53873          if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
53874            return;
53875          }
53876        }
53877
53878        this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
53879        this.tech_.trigger('seekablechanged');
53880      }
53881      /**
53882       * Update the player duration
53883       */
53884
53885    }, {
53886      key: 'updateDuration',
53887      value: function updateDuration() {
53888        var _this6 = this;
53889
53890        var oldDuration = this.mediaSource.duration;
53891        var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
53892        var buffered = this.tech_.buffered();
53893
53894        var setDuration = function setDuration() {
53895          // on firefox setting the duration may sometimes cause an exception
53896          // even if the media source is open and source buffers are not
53897          // updating, something about the media source being in an invalid state.
53898          _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
53899
53900          try {
53901            _this6.mediaSource.duration = newDuration;
53902          } catch (e) {
53903            videojs$1.log.warn('Failed to set media source duration', e);
53904          }
53905
53906          _this6.tech_.trigger('durationchange');
53907
53908          _this6.mediaSource.removeEventListener('sourceopen', setDuration);
53909        };
53910
53911        if (buffered.length > 0) {
53912          newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
53913        } // if the duration has changed, invalidate the cached value
53914
53915
53916        if (oldDuration !== newDuration) {
53917          // update the duration
53918          if (this.mediaSource.readyState !== 'open') {
53919            this.mediaSource.addEventListener('sourceopen', setDuration);
53920          } else {
53921            setDuration();
53922          }
53923        }
53924      }
53925      /**
53926       * dispose of the MasterPlaylistController and everything
53927       * that it controls
53928       */
53929
53930    }, {
53931      key: 'dispose',
53932      value: function dispose() {
53933        var _this7 = this;
53934
53935        this.decrypter_.terminate();
53936        this.masterPlaylistLoader_.dispose();
53937        this.mainSegmentLoader_.dispose();
53938        ['AUDIO', 'SUBTITLES'].forEach(function (type) {
53939          var groups = _this7.mediaTypes_[type].groups;
53940
53941          for (var id in groups) {
53942            groups[id].forEach(function (group) {
53943              if (group.playlistLoader) {
53944                group.playlistLoader.dispose();
53945              }
53946            });
53947          }
53948        });
53949        this.audioSegmentLoader_.dispose();
53950        this.subtitleSegmentLoader_.dispose();
53951      }
53952      /**
53953       * return the master playlist object if we have one
53954       *
53955       * @return {Object} the master playlist object that we parsed
53956       */
53957
53958    }, {
53959      key: 'master',
53960      value: function master() {
53961        return this.masterPlaylistLoader_.master;
53962      }
53963      /**
53964       * return the currently selected playlist
53965       *
53966       * @return {Object} the currently selected playlist object that we parsed
53967       */
53968
53969    }, {
53970      key: 'media',
53971      value: function media() {
53972        // playlist loader will not return media if it has not been fully loaded
53973        return this.masterPlaylistLoader_.media() || this.initialMedia_;
53974      }
53975      /**
53976       * setup our internal source buffers on our segment Loaders
53977       *
53978       * @private
53979       */
53980
53981    }, {
53982      key: 'setupSourceBuffers_',
53983      value: function setupSourceBuffers_() {
53984        var media = this.masterPlaylistLoader_.media();
53985        var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
53986        // attached
53987
53988        if (!media || this.mediaSource.readyState !== 'open') {
53989          return;
53990        }
53991
53992        mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
53993
53994        if (mimeTypes.length < 1) {
53995          this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
53996          return this.mediaSource.endOfStream('decode');
53997        }
53998
53999        this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
54000        // selection
54001
54002        this.excludeIncompatibleVariants_(media);
54003      }
54004    }, {
54005      key: 'configureLoaderMimeTypes_',
54006      value: function configureLoaderMimeTypes_(mimeTypes) {
54007        // If the content is demuxed, we can't start appending segments to a source buffer
54008        // until both source buffers are set up, or else the browser may not let us add the
54009        // second source buffer (it will assume we are playing either audio only or video
54010        // only).
54011        var sourceBufferEmitter = // If there is more than one mime type
54012        mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
54013        mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
54014        // only with alternate audio)
54015        mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
54016        new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
54017        // video only, or muxed content.
54018        null;
54019        this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
54020
54021        if (mimeTypes[1]) {
54022          this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
54023        }
54024      }
54025      /**
54026       * Blacklists playlists with codecs that are unsupported by the browser.
54027       */
54028
54029    }, {
54030      key: 'excludeUnsupportedVariants_',
54031      value: function excludeUnsupportedVariants_() {
54032        this.master().playlists.forEach(function (variant) {
54033          if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
54034            variant.excludeUntil = Infinity;
54035          }
54036        });
54037      }
54038      /**
54039       * Blacklist playlists that are known to be codec or
54040       * stream-incompatible with the SourceBuffer configuration. For
54041       * instance, Media Source Extensions would cause the video element to
54042       * stall waiting for video data if you switched from a variant with
54043       * video and audio to an audio-only one.
54044       *
54045       * @param {Object} media a media playlist compatible with the current
54046       * set of SourceBuffers. Variants in the current master playlist that
54047       * do not appear to have compatible codec or stream configurations
54048       * will be excluded from the default playlist selection algorithm
54049       * indefinitely.
54050       * @private
54051       */
54052
54053    }, {
54054      key: 'excludeIncompatibleVariants_',
54055      value: function excludeIncompatibleVariants_(media) {
54056        var codecCount = 2;
54057        var videoCodec = null;
54058        var codecs = void 0;
54059
54060        if (media.attributes.CODECS) {
54061          codecs = parseCodecs(media.attributes.CODECS);
54062          videoCodec = codecs.videoCodec;
54063          codecCount = codecs.codecCount;
54064        }
54065
vendor: 28,186 bytes, lines 54066-54840
54066        this.master().playlists.forEach(function (variant) {
54067          var variantCodecs = {
54068            codecCount: 2,
54069            videoCodec: null
54070          };
54071
54072          if (variant.attributes.CODECS) {
54073            variantCodecs = parseCodecs(variant.attributes.CODECS);
54074          } // if the streams differ in the presence or absence of audio or
54075          // video, they are incompatible
54076
54077
54078          if (variantCodecs.codecCount !== codecCount) {
54079            variant.excludeUntil = Infinity;
54080          } // if h.264 is specified on the current playlist, some flavor of
54081          // it must be specified on all compatible variants
54082
54083
54084          if (variantCodecs.videoCodec !== videoCodec) {
54085            variant.excludeUntil = Infinity;
54086          }
54087        });
54088      }
54089    }, {
54090      key: 'updateAdCues_',
54091      value: function updateAdCues_(media) {
54092        var offset = 0;
54093        var seekable$$1 = this.seekable();
54094
54095        if (seekable$$1.length) {
54096          offset = seekable$$1.start(0);
54097        }
54098
54099        updateAdCues(media, this.cueTagsTrack_, offset);
54100      }
54101      /**
54102       * Calculates the desired forward buffer length based on current time
54103       *
54104       * @return {Number} Desired forward buffer length in seconds
54105       */
54106
54107    }, {
54108      key: 'goalBufferLength',
54109      value: function goalBufferLength() {
54110        var currentTime = this.tech_.currentTime();
54111        var initial = Config.GOAL_BUFFER_LENGTH;
54112        var rate = Config.GOAL_BUFFER_LENGTH_RATE;
54113        var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
54114        return Math.min(initial + currentTime * rate, max);
54115      }
54116      /**
54117       * Calculates the desired buffer low water line based on current time
54118       *
54119       * @return {Number} Desired buffer low water line in seconds
54120       */
54121
54122    }, {
54123      key: 'bufferLowWaterLine',
54124      value: function bufferLowWaterLine() {
54125        var currentTime = this.tech_.currentTime();
54126        var initial = Config.BUFFER_LOW_WATER_LINE;
54127        var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
54128        var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
54129        return Math.min(initial + currentTime * rate, max);
54130      }
54131    }]);
54132    return MasterPlaylistController;
54133  }(videojs$1.EventTarget);
54134  /**
54135   * Returns a function that acts as the Enable/disable playlist function.
54136   *
54137   * @param {PlaylistLoader} loader - The master playlist loader
54138   * @param {String} playlistUri - uri of the playlist
54139   * @param {Function} changePlaylistFn - A function to be called after a
54140   * playlist's enabled-state has been changed. Will NOT be called if a
54141   * playlist's enabled-state is unchanged
54142   * @param {Boolean=} enable - Value to set the playlist enabled-state to
54143   * or if undefined returns the current enabled-state for the playlist
54144   * @return {Function} Function for setting/getting enabled
54145   */
54146
54147
54148  var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
54149    return function (enable) {
54150      var playlist = loader.master.playlists[playlistUri];
54151      var incompatible = isIncompatible(playlist);
54152      var currentlyEnabled = isEnabled(playlist);
54153
54154      if (typeof enable === 'undefined') {
54155        return currentlyEnabled;
54156      }
54157
54158      if (enable) {
54159        delete playlist.disabled;
54160      } else {
54161        playlist.disabled = true;
54162      }
54163
54164      if (enable !== currentlyEnabled && !incompatible) {
54165        // Ensure the outside world knows about our changes
54166        changePlaylistFn();
54167
54168        if (enable) {
54169          loader.trigger('renditionenabled');
54170        } else {
54171          loader.trigger('renditiondisabled');
54172        }
54173      }
54174
54175      return enable;
54176    };
54177  };
54178  /**
54179   * The representation object encapsulates the publicly visible information
54180   * in a media playlist along with a setter/getter-type function (enabled)
54181   * for changing the enabled-state of a particular playlist entry
54182   *
54183   * @class Representation
54184   */
54185
54186
54187  var Representation = function Representation(hlsHandler, playlist, id) {
54188    classCallCheck$1(this, Representation);
54189    var mpc = hlsHandler.masterPlaylistController_,
54190        smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
54191
54192    var changeType = smoothQualityChange ? 'smooth' : 'fast';
54193    var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
54194
54195    if (playlist.attributes.RESOLUTION) {
54196      var resolution = playlist.attributes.RESOLUTION;
54197      this.width = resolution.width;
54198      this.height = resolution.height;
54199    }
54200
54201    this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
54202    // within the master playlist
54203
54204    this.id = id; // Partially-apply the enableFunction to create a playlist-
54205    // specific variant
54206
54207    this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
54208  };
54209  /**
54210   * A mixin function that adds the `representations` api to an instance
54211   * of the HlsHandler class
54212   * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
54213   * representation API into
54214   */
54215
54216
54217  var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
54218    var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
54219
54220    hlsHandler.representations = function () {
54221      return playlists.master.playlists.filter(function (media) {
54222        return !isIncompatible(media);
54223      }).map(function (e, i) {
54224        return new Representation(hlsHandler, e, e.uri);
54225      });
54226    };
54227  };
54228  /**
54229   * @file playback-watcher.js
54230   *
54231   * Playback starts, and now my watch begins. It shall not end until my death. I shall
54232   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
54233   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
54234   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
54235   * my life and honor to the Playback Watch, for this Player and all the Players to come.
54236   */
54237  // Set of events that reset the playback-watcher time check logic and clear the timeout
54238
54239
54240  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
54241  /**
54242   * @class PlaybackWatcher
54243   */
54244
54245  var PlaybackWatcher = function () {
54246    /**
54247     * Represents an PlaybackWatcher object.
54248     * @constructor
54249     * @param {object} options an object that includes the tech and settings
54250     */
54251    function PlaybackWatcher(options) {
54252      var _this = this;
54253
54254      classCallCheck$1(this, PlaybackWatcher);
54255      this.tech_ = options.tech;
54256      this.seekable = options.seekable;
54257      this.seekTo = options.seekTo;
54258      this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
54259      this.media = options.media;
54260      this.consecutiveUpdates = 0;
54261      this.lastRecordedTime = null;
54262      this.timer_ = null;
54263      this.checkCurrentTimeTimeout_ = null;
54264      this.logger_ = logger('PlaybackWatcher');
54265      this.logger_('initialize');
54266
54267      var canPlayHandler = function canPlayHandler() {
54268        return _this.monitorCurrentTime_();
54269      };
54270
54271      var waitingHandler = function waitingHandler() {
54272        return _this.techWaiting_();
54273      };
54274
54275      var cancelTimerHandler = function cancelTimerHandler() {
54276        return _this.cancelTimer_();
54277      };
54278
54279      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
54280        return _this.fixesBadSeeks_();
54281      };
54282
54283      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
54284      this.tech_.on('waiting', waitingHandler);
54285      this.tech_.on(timerCancelEvents, cancelTimerHandler);
54286      this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
54287
54288      this.dispose = function () {
54289        _this.logger_('dispose');
54290
54291        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
54292
54293        _this.tech_.off('waiting', waitingHandler);
54294
54295        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
54296
54297        _this.tech_.off('canplay', canPlayHandler);
54298
54299        if (_this.checkCurrentTimeTimeout_) {
54300          window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
54301        }
54302
54303        _this.cancelTimer_();
54304      };
54305    }
54306    /**
54307     * Periodically check current time to see if playback stopped
54308     *
54309     * @private
54310     */
54311
54312
54313    createClass$1(PlaybackWatcher, [{
54314      key: 'monitorCurrentTime_',
54315      value: function monitorCurrentTime_() {
54316        this.checkCurrentTime_();
54317
54318        if (this.checkCurrentTimeTimeout_) {
54319          window$1.clearTimeout(this.checkCurrentTimeTimeout_);
54320        } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
54321
54322
54323        this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
54324      }
54325      /**
54326       * The purpose of this function is to emulate the "waiting" event on
54327       * browsers that do not emit it when they are waiting for more
54328       * data to continue playback
54329       *
54330       * @private
54331       */
54332
54333    }, {
54334      key: 'checkCurrentTime_',
54335      value: function checkCurrentTime_() {
54336        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
54337          this.consecutiveUpdates = 0;
54338          this.lastRecordedTime = this.tech_.currentTime();
54339          return;
54340        }
54341
54342        if (this.tech_.paused() || this.tech_.seeking()) {
54343          return;
54344        }
54345
54346        var currentTime = this.tech_.currentTime();
54347        var buffered = this.tech_.buffered();
54348
54349        if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
54350          // If current time is at the end of the final buffered region, then any playback
54351          // stall is most likely caused by buffering in a low bandwidth environment. The tech
54352          // should fire a `waiting` event in this scenario, but due to browser and tech
54353          // inconsistencies. Calling `techWaiting_` here allows us to simulate
54354          // responding to a native `waiting` event when the tech fails to emit one.
54355          return this.techWaiting_();
54356        }
54357
54358        if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
54359          this.consecutiveUpdates++;
54360          this.waiting_();
54361        } else if (currentTime === this.lastRecordedTime) {
54362          this.consecutiveUpdates++;
54363        } else {
54364          this.consecutiveUpdates = 0;
54365          this.lastRecordedTime = currentTime;
54366        }
54367      }
54368      /**
54369       * Cancels any pending timers and resets the 'timeupdate' mechanism
54370       * designed to detect that we are stalled
54371       *
54372       * @private
54373       */
54374
54375    }, {
54376      key: 'cancelTimer_',
54377      value: function cancelTimer_() {
54378        this.consecutiveUpdates = 0;
54379
54380        if (this.timer_) {
54381          this.logger_('cancelTimer_');
54382          clearTimeout(this.timer_);
54383        }
54384
54385        this.timer_ = null;
54386      }
54387      /**
54388       * Fixes situations where there's a bad seek
54389       *
54390       * @return {Boolean} whether an action was taken to fix the seek
54391       * @private
54392       */
54393
54394    }, {
54395      key: 'fixesBadSeeks_',
54396      value: function fixesBadSeeks_() {
54397        var seeking = this.tech_.seeking();
54398
54399        if (!seeking) {
54400          return false;
54401        }
54402
54403        var seekable = this.seekable();
54404        var currentTime = this.tech_.currentTime();
54405        var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
54406        var seekTo = void 0;
54407
54408        if (isAfterSeekableRange) {
54409          var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
54410
54411          seekTo = seekableEnd;
54412        }
54413
54414        if (this.beforeSeekableWindow_(seekable, currentTime)) {
54415          var seekableStart = seekable.start(0); // sync to the beginning of the live window
54416          // provide a buffer of .1 seconds to handle rounding/imprecise numbers
54417
54418          seekTo = seekableStart + SAFE_TIME_DELTA;
54419        }
54420
54421        if (typeof seekTo !== 'undefined') {
54422          this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
54423          this.seekTo(seekTo);
54424          return true;
54425        }
54426
54427        return false;
54428      }
54429      /**
54430       * Handler for situations when we determine the player is waiting.
54431       *
54432       * @private
54433       */
54434
54435    }, {
54436      key: 'waiting_',
54437      value: function waiting_() {
54438        if (this.techWaiting_()) {
54439          return;
54440        } // All tech waiting checks failed. Use last resort correction
54441
54442
54443        var currentTime = this.tech_.currentTime();
54444        var buffered = this.tech_.buffered();
54445        var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
54446        // region with no indication that anything is amiss (seen in Firefox). Seeking to
54447        // currentTime is usually enough to kickstart the player. This checks that the player
54448        // is currently within a buffered region before attempting a corrective seek.
54449        // Chrome does not appear to continue `timeupdate` events after a `waiting` event
54450        // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
54451        // make sure there is ~3 seconds of forward buffer before taking any corrective action
54452        // to avoid triggering an `unknownwaiting` event when the network is slow.
54453
54454        if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
54455          this.cancelTimer_();
54456          this.seekTo(currentTime);
54457          this.logger_('Stopped at ' + currentTime + ' while inside a buffered region ' + ('[' + currentRange.start(0) + ' -> ' + currentRange.end(0) + ']. Attempting to resume ') + 'playback by seeking to the current time.'); // unknown waiting corrections may be useful for monitoring QoS
54458
54459          this.tech_.trigger({
54460            type: 'usage',
54461            name: 'hls-unknown-waiting'
54462          });
54463          return;
54464        }
54465      }
54466      /**
54467       * Handler for situations when the tech fires a `waiting` event
54468       *
54469       * @return {Boolean}
54470       *         True if an action (or none) was needed to correct the waiting. False if no
54471       *         checks passed
54472       * @private
54473       */
54474
54475    }, {
54476      key: 'techWaiting_',
54477      value: function techWaiting_() {
54478        var seekable = this.seekable();
54479        var currentTime = this.tech_.currentTime();
54480
54481        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
54482          // Tech is seeking or bad seek fixed, no action needed
54483          return true;
54484        }
54485
54486        if (this.tech_.seeking() || this.timer_ !== null) {
54487          // Tech is seeking or already waiting on another action, no action needed
54488          return true;
54489        }
54490
54491        if (this.beforeSeekableWindow_(seekable, currentTime)) {
54492          var livePoint = seekable.end(seekable.length - 1);
54493          this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
54494          this.cancelTimer_();
54495          this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
54496
54497          this.tech_.trigger({
54498            type: 'usage',
54499            name: 'hls-live-resync'
54500          });
54501          return true;
54502        }
54503
54504        var buffered = this.tech_.buffered();
54505        var nextRange = findNextRange(buffered, currentTime);
54506
54507        if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
54508          // Even though the video underflowed and was stuck in a gap, the audio overplayed
54509          // the gap, leading currentTime into a buffered range. Seeking to currentTime
54510          // allows the video to catch up to the audio position without losing any audio
54511          // (only suffering ~3 seconds of frozen video and a pause in audio playback).
54512          this.cancelTimer_();
54513          this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
54514
54515          this.tech_.trigger({
54516            type: 'usage',
54517            name: 'hls-video-underflow'
54518          });
54519          return true;
54520        } // check for gap
54521
54522
54523        if (nextRange.length > 0) {
54524          var difference = nextRange.start(0) - currentTime;
54525          this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
54526          this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
54527          return true;
54528        } // All checks failed. Returning false to indicate failure to correct waiting
54529
54530
54531        return false;
54532      }
54533    }, {
54534      key: 'afterSeekableWindow_',
54535      value: function afterSeekableWindow_(seekable, currentTime, playlist) {
54536        var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
54537
54538        if (!seekable.length) {
54539          // we can't make a solid case if there's no seekable, default to false
54540          return false;
54541        }
54542
54543        var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
54544        var isLive = !playlist.endList;
54545
54546        if (isLive && allowSeeksWithinUnsafeLiveWindow) {
54547          allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
54548        }
54549
54550        if (currentTime > allowedEnd) {
54551          return true;
54552        }
54553
54554        return false;
54555      }
54556    }, {
54557      key: 'beforeSeekableWindow_',
54558      value: function beforeSeekableWindow_(seekable, currentTime) {
54559        if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
54560        seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
54561          return true;
54562        }
54563
54564        return false;
54565      }
54566    }, {
54567      key: 'videoUnderflow_',
54568      value: function videoUnderflow_(nextRange, buffered, currentTime) {
54569        if (nextRange.length === 0) {
54570          // Even if there is no available next range, there is still a possibility we are
54571          // stuck in a gap due to video underflow.
54572          var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
54573
54574          if (gap) {
54575            this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
54576            return true;
54577          }
54578        }
54579
54580        return false;
54581      }
54582      /**
54583       * Timer callback. If playback still has not proceeded, then we seek
54584       * to the start of the next buffered region.
54585       *
54586       * @private
54587       */
54588
54589    }, {
54590      key: 'skipTheGap_',
54591      value: function skipTheGap_(scheduledCurrentTime) {
54592        var buffered = this.tech_.buffered();
54593        var currentTime = this.tech_.currentTime();
54594        var nextRange = findNextRange(buffered, currentTime);
54595        this.cancelTimer_();
54596
54597        if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
54598          return;
54599        }
54600
54601        this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
54602
54603        this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
54604        this.tech_.trigger({
54605          type: 'usage',
54606          name: 'hls-gap-skip'
54607        });
54608      }
54609    }, {
54610      key: 'gapFromVideoUnderflow_',
54611      value: function gapFromVideoUnderflow_(buffered, currentTime) {
54612        // At least in Chrome, if there is a gap in the video buffer, the audio will continue
54613        // playing for ~3 seconds after the video gap starts. This is done to account for
54614        // video buffer underflow/underrun (note that this is not done when there is audio
54615        // buffer underflow/underrun -- in that case the video will stop as soon as it
54616        // encounters the gap, as audio stalls are more noticeable/jarring to a user than
54617        // video stalls). The player's time will reflect the playthrough of audio, so the
54618        // time will appear as if we are in a buffered region, even if we are stuck in a
54619        // "gap."
54620        //
54621        // Example:
54622        // video buffer:   0 => 10.1, 10.2 => 20
54623        // audio buffer:   0 => 20
54624        // overall buffer: 0 => 10.1, 10.2 => 20
54625        // current time: 13
54626        //
54627        // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
54628        // however, the audio continued playing until it reached ~3 seconds past the gap
54629        // (13 seconds), at which point it stops as well. Since current time is past the
54630        // gap, findNextRange will return no ranges.
54631        //
54632        // To check for this issue, we see if there is a gap that starts somewhere within
54633        // a 3 second range (3 seconds +/- 1 second) back from our current time.
54634        var gaps = findGaps(buffered);
54635
54636        for (var i = 0; i < gaps.length; i++) {
54637          var start = gaps.start(i);
54638          var end = gaps.end(i); // gap is starts no more than 4 seconds back
54639
54640          if (currentTime - start < 4 && currentTime - start > 2) {
54641            return {
54642              start: start,
54643              end: end
54644            };
54645          }
54646        }
54647
54648        return null;
54649      }
54650    }]);
54651    return PlaybackWatcher;
54652  }();
54653
54654  var defaultOptions = {
54655    errorInterval: 30,
54656    getSource: function getSource(next) {
54657      var tech = this.tech({
54658        IWillNotUseThisInPlugins: true
54659      });
54660      var sourceObj = tech.currentSource_;
54661      return next(sourceObj);
54662    }
54663  };
54664  /**
54665   * Main entry point for the plugin
54666   *
54667   * @param {Player} player a reference to a videojs Player instance
54668   * @param {Object} [options] an object with plugin options
54669   * @private
54670   */
54671
54672  var initPlugin = function initPlugin(player, options) {
54673    var lastCalled = 0;
54674    var seekTo = 0;
54675    var localOptions = videojs$1.mergeOptions(defaultOptions, options);
54676    player.ready(function () {
54677      player.trigger({
54678        type: 'usage',
54679        name: 'hls-error-reload-initialized'
54680      });
54681    });
54682    /**
54683     * Player modifications to perform that must wait until `loadedmetadata`
54684     * has been triggered
54685     *
54686     * @private
54687     */
54688
54689    var loadedMetadataHandler = function loadedMetadataHandler() {
54690      if (seekTo) {
54691        player.currentTime(seekTo);
54692      }
54693    };
54694    /**
54695     * Set the source on the player element, play, and seek if necessary
54696     *
54697     * @param {Object} sourceObj An object specifying the source url and mime-type to play
54698     * @private
54699     */
54700
54701
54702    var setSource = function setSource(sourceObj) {
54703      if (sourceObj === null || sourceObj === undefined) {
54704        return;
54705      }
54706
54707      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
54708      player.one('loadedmetadata', loadedMetadataHandler);
54709      player.src(sourceObj);
54710      player.trigger({
54711        type: 'usage',
54712        name: 'hls-error-reload'
54713      });
54714      player.play();
54715    };
54716    /**
54717     * Attempt to get a source from either the built-in getSource function
54718     * or a custom function provided via the options
54719     *
54720     * @private
54721     */
54722
54723
54724    var errorHandler = function errorHandler() {
54725      // Do not attempt to reload the source if a source-reload occurred before
54726      // 'errorInterval' time has elapsed since the last source-reload
54727      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
54728        player.trigger({
54729          type: 'usage',
54730          name: 'hls-error-reload-canceled'
54731        });
54732        return;
54733      }
54734
54735      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
54736        videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
54737        return;
54738      }
54739
54740      lastCalled = Date.now();
54741      return localOptions.getSource.call(player, setSource);
54742    };
54743    /**
54744     * Unbind any event handlers that were bound by the plugin
54745     *
54746     * @private
54747     */
54748
54749
54750    var cleanupEvents = function cleanupEvents() {
54751      player.off('loadedmetadata', loadedMetadataHandler);
54752      player.off('error', errorHandler);
54753      player.off('dispose', cleanupEvents);
54754    };
54755    /**
54756     * Cleanup before re-initializing the plugin
54757     *
54758     * @param {Object} [newOptions] an object with plugin options
54759     * @private
54760     */
54761
54762
54763    var reinitPlugin = function reinitPlugin(newOptions) {
54764      cleanupEvents();
54765      initPlugin(player, newOptions);
54766    };
54767
54768    player.on('error', errorHandler);
54769    player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
54770    // initializing the plugin
54771
54772    player.reloadSourceOnError = reinitPlugin;
54773  };
54774  /**
54775   * Reload the source when an error is detected as long as there
54776   * wasn't an error previously within the last 30 seconds
54777   *
54778   * @param {Object} [options] an object with plugin options
54779   */
54780
54781
54782  var reloadSourceOnError = function reloadSourceOnError(options) {
54783    initPlugin(this, options);
54784  };
54785
54786  var version$1 = "1.11.2"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
54787
54788  videojs$1.use('*', function (player) {
54789    return {
54790      setSource: function setSource(srcObj, next) {
54791        // pass null as the first argument to indicate that the source is not rejected
54792        next(null, srcObj);
54793      },
54794      // VHS needs to know when seeks happen. For external seeks (generated at the player
54795      // level), this middleware will capture the action. For internal seeks (generated at
54796      // the tech level), we use a wrapped function so that we can handle it on our own
54797      // (specified elsewhere).
54798      setCurrentTime: function setCurrentTime(time) {
54799        if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
54800          player.vhs.setCurrentTime(time);
54801        }
54802
54803        return time;
54804      },
54805      // Sync VHS after play requests.
54806      // This specifically handles replay where the order of actions is
54807      // play, video element will seek to 0 (skipping the setCurrentTime middleware)
54808      // then triggers a play event.
54809      play: function play() {
54810        if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
54811          player.vhs.setCurrentTime(player.tech_.currentTime());
54812        }
54813      }
54814    };
54815  });
54816  /**
54817   * @file videojs-http-streaming.js
54818   *
54819   * The main file for the HLS project.
54820   * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
54821   */
54822
54823  var Hls$1 = {
54824    PlaylistLoader: PlaylistLoader,
54825    Playlist: Playlist,
54826    Decrypter: Decrypter,
54827    AsyncStream: AsyncStream,
54828    decrypt: decrypt,
54829    utils: utils$1,
54830    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
54831    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
54832    comparePlaylistBandwidth: comparePlaylistBandwidth,
54833    comparePlaylistResolution: comparePlaylistResolution,
54834    xhr: xhrFactory()
54835  }; // Define getter/setters for config properites
54836
54837  ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) {
54838    Object.defineProperty(Hls$1, prop, {
54839      get: function get$$1() {
54840        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know 
54840what you are doing');
54841        return Config[prop];
54842      },
54843      set: function set$$1(value) {
54844        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
54845
54846        if (typeof value !== 'number' || value < 0) {
54847          videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
54848          return;
54849        }
54850
54851        Config[prop] = value;
54852      }
54853    });
54854  });
54855  var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
54856
54857  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
54858    var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
54859
54860    if (mpegurlRE.test(type)) {
54861      return 'hls';
54862    }
54863
54864    var dashRE = /^application\/dash\+xml/i;
54865
54866    if (dashRE.test(type)) {
54867      return 'dash';
54868    }
54869
54870    return null;
54871  };
54872  /**
54873   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
54874   *
54875   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
54876   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
54877   * @function handleHlsMediaChange
54878   */
54879
54880
54881  var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
54882    var newPlaylist = playlistLoader.media();
54883    var selectedIndex = -1;
54884
54885    for (var i = 0; i < qualityLevels.length; i++) {
54886      if (qualityLevels[i].id === newPlaylist.uri) {
54887        selectedIndex = i;
54888        break;
54889      }
54890    }
54891
54892    qualityLevels.selectedIndex_ = selectedIndex;
54893    qualityLevels.trigger({
54894      selectedIndex: selectedIndex,
54895      type: 'change'
54896    });
54897  };
54898  /**
54899   * Adds quality levels to list once playlist metadata is available
54900   *
54901   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
54902   * @param {Object} hls Hls object to listen to for media events.
54903   * @function handleHlsLoadedMetadata
54904   */
54905
54906
54907  var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
54908    hls.representations().forEach(function (rep) {
54909      qualityLevels.addQualityLevel(rep);
54910    });
54911    handleHlsMediaChange(qualityLevels, hls.playlists);
54912  }; // HLS is a source handler, not a tech. Make sure attempts to use it
54913  // as one do not cause exceptions.
54914
54915
54916  Hls$1.canPlaySource = function () {
54917    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
54918  };
54919
54920  var emeKeySystems = function emeKeySystems(keySystemOptions, mainSegmentLoader, audioSegmentLoader) {
54921    if (!keySystemOptions) {
54922      return keySystemOptions;
54923    }
54924
54925    var videoMimeType = void 0;
54926    var audioMimeType = void 0; // if there is a mimeType associated with the audioSegmentLoader, then the audio
54927    // and video mimeType and codec strings are already in the format we need to
54928    // pass with the other key systems
54929
54930    if (audioSegmentLoader.mimeType_) {
54931      videoMimeType = mainSegmentLoader.mimeType_;
54932      audioMimeType = audioSegmentLoader.mimeType_; // if there is no audioSegmentLoader mimeType, then we have to create the
54933      // the audio and video mimeType/codec strings from information extrapolated
54934      // from the mainSegmentLoader mimeType (ex. 'video/mp4; codecs="mp4, avc1"' -->
54935      // 'video/mp4; codecs="avc1"' and 'audio/mp4; codecs="mp4"')
54936    } else {
54937      var parsedMimeType = parseContentType(mainSegmentLoader.mimeType_);
54938      var codecs = parsedMimeType.parameters.codecs.split(',');
54939      var audioCodec = void 0;
54940      var videoCodec = void 0;
54941      codecs.forEach(function (codec) {
54942        codec = codec.trim();
54943
54944        if (isAudioCodec(codec)) {
54945          audioCodec = codec;
54946        } else if (isVideoCodec(codec)) {
54947          videoCodec = codec;
54948        }
54949      });
54950      videoMimeType = parsedMimeType.type + '; codecs="' + videoCodec + '"';
54951      audioMimeType = parsedMimeType.type.replace('video', 'audio') + '; codecs="' + audioCodec + '"';
54952    } // upsert the content types based on the selected playlist
54953
54954
54955    var keySystemContentTypes = {};
54956    var videoPlaylist = mainSegmentLoader.playlist_;
54957
54958    for (var keySystem in keySystemOptions) {
54959      keySystemContentTypes[keySystem] = {
54960        audioContentType: audioMimeType,
54961        videoContentType: videoMimeType
54962      };
54963
54964      if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
54965        keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
54966      } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
54967      // so we need to prevent overwriting the URL entirely
54968
54969
54970      if (typeof keySystemOptions[keySystem] === 'string') {
54971        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
54972      }
54973    }
54974
54975    return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
54976  };
54977
54978  var setupEmeOptions = function setupEmeOptions(hlsHandler) {
54979    var mainSegmentLoader = hlsHandler.masterPlaylistController_.mainSegmentLoader_;
54980    var audioSegmentLoader = hlsHandler.masterPlaylistController_.audioSegmentLoader_;
54981    var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
54982
54983    if (player.eme) {
54984      var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, mainSegmentLoader, audioSegmentLoader);
54985
54986      if (sourceOptions) {
54987        player.currentSource().keySystems = sourceOptions; // Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
54988        // in non-IE11 browsers. In IE11 this is too early to initialize media keys
54989
54990        if (!(videojs$1.browser.IE_VERSION === 11) && player.eme.initializeMediaKeys) {
54991          player.eme.initializeMediaKeys();
54992        }
54993      }
54994    }
54995  };
54996
54997  var getVhsLocalStorage = function getVhsLocalStorage() {
54998    if (!window.localStorage) {
54999      return null;
55000    }
55001
55002    var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
55003
55004    if (!storedObject) {
55005      return null;
55006    }
55007
55008    try {
55009      return JSON.parse(storedObject);
55010    } catch (e) {
55011      // someone may have tampered with the value
55012      return null;
55013    }
55014  };
55015
55016  var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
55017    if (!window.localStorage) {
55018      return false;
55019    }
55020
55021    var objectToStore = getVhsLocalStorage();
55022    objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
55023
55024    try {
55025      window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
55026    } catch (e) {
55027      // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
55028      // storage is set to 0).
55029      // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
55030      // No need to perform any operation.
55031      return false;
55032    }
55033
55034    return objectToStore;
55035  };
55036  /**
55037   * Whether the browser has built-in HLS support.
55038   */
55039
55040
55041  Hls$1.supportsNativeHls = function () {
55042    var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
55043
55044    if (!videojs$1.getTech('Html5').isSupported()) {
55045      return false;
55046    } // HLS manifests can go by many mime-types
55047
55048
55049    var canPlay = [// Apple santioned
55050    'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
55051    'audio/mpegurl', // Very common
55052    'audio/x-mpegurl', // Very common
vendor: 20,034 bytes, lines 55053-55580
55053    'application/x-mpegurl', // Included for completeness
55054    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
55055    return canPlay.some(function (canItPlay) {
55056      return /maybe|probably/i.test(video.canPlayType(canItPlay));
55057    });
55058  }();
55059
55060  Hls$1.supportsNativeDash = function () {
55061    if (!videojs$1.getTech('Html5').isSupported()) {
55062      return false;
55063    }
55064
55065    return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
55066  }();
55067
55068  Hls$1.supportsTypeNatively = function (type) {
55069    if (type === 'hls') {
55070      return Hls$1.supportsNativeHls;
55071    }
55072
55073    if (type === 'dash') {
55074      return Hls$1.supportsNativeDash;
55075    }
55076
55077    return false;
55078  };
55079  /**
55080   * HLS is a source handler, not a tech. Make sure attempts to use it
55081   * as one do not cause exceptions.
55082   */
55083
55084
55085  Hls$1.isSupported = function () {
55086    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
55087  };
55088
55089  var Component$1 = videojs$1.getComponent('Component');
55090  /**
55091   * The Hls Handler object, where we orchestrate all of the parts
55092   * of HLS to interact with video.js
55093   *
55094   * @class HlsHandler
55095   * @extends videojs.Component
55096   * @param {Object} source the soruce object
55097   * @param {Tech} tech the parent tech object
55098   * @param {Object} options optional and required options
55099   */
55100
55101  var HlsHandler = function (_Component) {
55102    inherits$2(HlsHandler, _Component);
55103
55104    function HlsHandler(source, tech, options) {
55105      classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
55106      // backwards-compatibility
55107
55108      var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
55109
55110      if (tech.options_ && tech.options_.playerId) {
55111        var _player = videojs$1(tech.options_.playerId);
55112
55113        if (!_player.hasOwnProperty('hls')) {
55114          Object.defineProperty(_player, 'hls', {
55115            get: function get$$1() {
55116              videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
55117              tech.trigger({
55118                type: 'usage',
55119                name: 'hls-player-access'
55120              });
55121              return _this;
55122            },
55123            configurable: true
55124          });
55125        } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
55126        // migrating from player.tech_.hls... to player.vhs... for API access. Although this
55127        // isn't the most appropriate form of reference for video.js (since all APIs should
55128        // be provided through core video.js), it is a common pattern for plugins, and vhs
55129        // will act accordingly.
55130
55131
55132        _player.vhs = _this; // deprecated, for backwards compatibility
55133
55134        _player.dash = _this;
55135        _this.player_ = _player;
55136      }
55137
55138      _this.tech_ = tech;
55139      _this.source_ = source;
55140      _this.stats = {};
55141
55142      _this.setOptions_();
55143
55144      if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
55145        tech.overrideNativeAudioTracks(true);
55146        tech.overrideNativeVideoTracks(true);
55147      } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
55148        // overriding native HLS only works if audio tracks have been emulated
55149        // error early if we're misconfigured
55150        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
55151      } // listen for fullscreenchange events for this player so that we
55152      // can adjust our quality selection quickly
55153
55154
55155      _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
55156        var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
55157
55158        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
55159          _this.masterPlaylistController_.smoothQualityChange_();
55160        }
55161      }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
55162
55163
55164      _this.on(_this.tech_, 'seeking', function () {
55165        if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
55166          this.setCurrentTime(0);
55167        }
55168      });
55169
55170      _this.on(_this.tech_, 'error', function () {
55171        if (this.masterPlaylistController_) {
55172          this.masterPlaylistController_.pauseLoading();
55173        }
55174      });
55175
55176      _this.on(_this.tech_, 'play', _this.play);
55177
55178      return _this;
55179    }
55180
55181    createClass$1(HlsHandler, [{
55182      key: 'setOptions_',
55183      value: function setOptions_() {
55184        var _this2 = this; // defaults
55185
55186
55187        this.options_.withCredentials = this.options_.withCredentials || false;
55188        this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
55189        this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
55190        this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
55191        this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
55192        this.options_.customTagParsers = this.options_.customTagParsers || [];
55193        this.options_.customTagMappers = this.options_.customTagMappers || [];
55194        this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
55195
55196        if (typeof this.options_.blacklistDuration !== 'number') {
55197          this.options_.blacklistDuration = 5 * 60;
55198        }
55199
55200        if (typeof this.options_.bandwidth !== 'number') {
55201          if (this.options_.useBandwidthFromLocalStorage) {
55202            var storedObject = getVhsLocalStorage();
55203
55204            if (storedObject && storedObject.bandwidth) {
55205              this.options_.bandwidth = storedObject.bandwidth;
55206              this.tech_.trigger({
55207                type: 'usage',
55208                name: 'hls-bandwidth-from-local-storage'
55209              });
55210            }
55211
55212            if (storedObject && storedObject.throughput) {
55213              this.options_.throughput = storedObject.throughput;
55214              this.tech_.trigger({
55215                type: 'usage',
55216                name: 'hls-throughput-from-local-storage'
55217              });
55218            }
55219          }
55220        } // if bandwidth was not set by options or pulled from local storage, start playlist
55221        // selection at a reasonable bandwidth
55222
55223
55224        if (typeof this.options_.bandwidth !== 'number') {
55225          this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
55226        } // If the bandwidth number is unchanged from the initial setting
55227        // then this takes precedence over the enableLowInitialPlaylist option
55228
55229
55230        this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
55231
55232        ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) {
55233          if (typeof _this2.source_[option] !== 'undefined') {
55234            _this2.options_[option] = _this2.source_[option];
55235          }
55236        });
55237        this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
55238      }
55239      /**
55240       * called when player.src gets called, handle a new source
55241       *
55242       * @param {Object} src the source object to handle
55243       */
55244
55245    }, {
55246      key: 'src',
55247      value: function src(_src, type) {
55248        var _this3 = this; // do nothing if the src is falsey
55249
55250
55251        if (!_src) {
55252          return;
55253        }
55254
55255        this.setOptions_(); // add master playlist controller options
55256
55257        this.options_.url = this.source_.src;
55258        this.options_.tech = this.tech_;
55259        this.options_.externHls = Hls$1;
55260        this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
55261        // setCurrentTime function. This is needed because "seeking" events aren't always
55262        // reliable. External seeks (via the player object) are handled via middleware.
55263
55264        this.options_.seekTo = function (time) {
55265          _this3.tech_.setCurrentTime(time);
55266
55267          _this3.setCurrentTime(time);
55268        };
55269
55270        this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
55271        this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
55272          seekable: function seekable$$1() {
55273            return _this3.seekable();
55274          },
55275          media: function media() {
55276            return _this3.masterPlaylistController_.media();
55277          }
55278        }));
55279        this.masterPlaylistController_.on('error', function () {
55280          var player = videojs$1.players[_this3.tech_.options_.playerId];
55281          player.error(_this3.masterPlaylistController_.error);
55282        }); // `this` in selectPlaylist should be the HlsHandler for backwards
55283        // compatibility with < v2
55284
55285        this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
55286        this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
55287
55288        this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
55289        this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
55290        // controller. Using a custom property for backwards compatibility
55291        // with < v2
55292
55293        Object.defineProperties(this, {
55294          selectPlaylist: {
55295            get: function get$$1() {
55296              return this.masterPlaylistController_.selectPlaylist;
55297            },
55298            set: function set$$1(selectPlaylist) {
55299              this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
55300            }
55301          },
55302          throughput: {
55303            get: function get$$1() {
55304              return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
55305            },
55306            set: function set$$1(throughput) {
55307              this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
55308              // for the cumulative average
55309
55310              this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
55311            }
55312          },
55313          bandwidth: {
55314            get: function get$$1() {
55315              return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
55316            },
55317            set: function set$$1(bandwidth) {
55318              this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
55319              // `count` is set to zero that current value of `rate` isn't included
55320              // in the cumulative average
55321
55322              this.masterPlaylistController_.mainSegmentLoader_.throughput = {
55323                rate: 0,
55324                count: 0
55325              };
55326            }
55327          },
55328
55329          /**
55330           * `systemBandwidth` is a combination of two serial processes bit-rates. The first
55331           * is the network bitrate provided by `bandwidth` and the second is the bitrate of
55332           * the entire process after that - decryption, transmuxing, and appending - provided
55333           * by `throughput`.
55334           *
55335           * Since the two process are serial, the overall system bandwidth is given by:
55336           *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
55337           */
55338          systemBandwidth: {
55339            get: function get$$1() {
55340              var invBandwidth = 1 / (this.bandwidth || 1);
55341              var invThroughput = void 0;
55342
55343              if (this.throughput > 0) {
55344                invThroughput = 1 / this.throughput;
55345              } else {
55346                invThroughput = 0;
55347              }
55348
55349              var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
55350              return systemBitrate;
55351            },
55352            set: function set$$1() {
55353              videojs$1.log.error('The "systemBandwidth" property is read-only');
55354            }
55355          }
55356        });
55357
55358        if (this.options_.bandwidth) {
55359          this.bandwidth = this.options_.bandwidth;
55360        }
55361
55362        if (this.options_.throughput) {
55363          this.throughput = this.options_.throughput;
55364        }
55365
55366        Object.defineProperties(this.stats, {
55367          bandwidth: {
55368            get: function get$$1() {
55369              return _this3.bandwidth || 0;
55370            },
55371            enumerable: true
55372          },
55373          mediaRequests: {
55374            get: function get$$1() {
55375              return _this3.masterPlaylistController_.mediaRequests_() || 0;
55376            },
55377            enumerable: true
55378          },
55379          mediaRequestsAborted: {
55380            get: function get$$1() {
55381              return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
55382            },
55383            enumerable: true
55384          },
55385          mediaRequestsTimedout: {
55386            get: function get$$1() {
55387              return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
55388            },
55389            enumerable: true
55390          },
55391          mediaRequestsErrored: {
55392            get: function get$$1() {
55393              return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
55394            },
55395            enumerable: true
55396          },
55397          mediaTransferDuration: {
55398            get: function get$$1() {
55399              return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
55400            },
55401            enumerable: true
55402          },
55403          mediaBytesTransferred: {
55404            get: function get$$1() {
55405              return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
55406            },
55407            enumerable: true
55408          },
55409          mediaSecondsLoaded: {
55410            get: function get$$1() {
55411              return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
55412            },
55413            enumerable: true
55414          },
55415          buffered: {
55416            get: function get$$1() {
55417              return timeRangesToArray(_this3.tech_.buffered());
55418            },
55419            enumerable: true
55420          },
55421          currentTime: {
55422            get: function get$$1() {
55423              return _this3.tech_.currentTime();
55424            },
55425            enumerable: true
55426          },
55427          currentSource: {
55428            get: function get$$1() {
55429              return _this3.tech_.currentSource_;
55430            },
55431            enumerable: true
55432          },
55433          currentTech: {
55434            get: function get$$1() {
55435              return _this3.tech_.name_;
55436            },
55437            enumerable: true
55438          },
55439          duration: {
55440            get: function get$$1() {
55441              return _this3.tech_.duration();
55442            },
55443            enumerable: true
55444          },
55445          master: {
55446            get: function get$$1() {
55447              return _this3.playlists.master;
55448            },
55449            enumerable: true
55450          },
55451          playerDimensions: {
55452            get: function get$$1() {
55453              return _this3.tech_.currentDimensions();
55454            },
55455            enumerable: true
55456          },
55457          seekable: {
55458            get: function get$$1() {
55459              return timeRangesToArray(_this3.tech_.seekable());
55460            },
55461            enumerable: true
55462          },
55463          timestamp: {
55464            get: function get$$1() {
55465              return Date.now();
55466            },
55467            enumerable: true
55468          },
55469          videoPlaybackQuality: {
55470            get: function get$$1() {
55471              return _this3.tech_.getVideoPlaybackQuality();
55472            },
55473            enumerable: true
55474          }
55475        });
55476        this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
55477        this.tech_.on('bandwidthupdate', function () {
55478          if (_this3.options_.useBandwidthFromLocalStorage) {
55479            updateVhsLocalStorage({
55480              bandwidth: _this3.bandwidth,
55481              throughput: Math.round(_this3.throughput)
55482            });
55483          }
55484        });
55485        this.masterPlaylistController_.on('selectedinitialmedia', function () {
55486          // Add the manual rendition mix-in to HlsHandler
55487          renditionSelectionMixin(_this3);
55488          setupEmeOptions(_this3);
55489        }); // the bandwidth of the primary segment loader is our best
55490        // estimate of overall bandwidth
55491
55492        this.on(this.masterPlaylistController_, 'progress', function () {
55493          this.tech_.trigger('progress');
55494        });
55495        this.tech_.ready(function () {
55496          return _this3.setupQualityLevels_();
55497        }); // do nothing if the tech has been disposed already
55498        // this can occur if someone sets the src in player.ready(), for instance
55499
55500        if (!this.tech_.el()) {
55501          return;
55502        }
55503
55504        this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
55505      }
55506      /**
55507       * Initializes the quality levels and sets listeners to update them.
55508       *
55509       * @method setupQualityLevels_
55510       * @private
55511       */
55512
55513    }, {
55514      key: 'setupQualityLevels_',
55515      value: function setupQualityLevels_() {
55516        var _this4 = this;
55517
55518        var player = videojs$1.players[this.tech_.options_.playerId];
55519
55520        if (player && player.qualityLevels) {
55521          this.qualityLevels_ = player.qualityLevels();
55522          this.masterPlaylistController_.on('selectedinitialmedia', function () {
55523            handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
55524          });
55525          this.playlists.on('mediachange', function () {
55526            handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
55527          });
55528        }
55529      }
55530      /**
55531       * Begin playing the video.
55532       */
55533
55534    }, {
55535      key: 'play',
55536      value: function play() {
55537        this.masterPlaylistController_.play();
55538      }
55539      /**
55540       * a wrapper around the function in MasterPlaylistController
55541       */
55542
55543    }, {
55544      key: 'setCurrentTime',
55545      value: function setCurrentTime(currentTime) {
55546        this.masterPlaylistController_.setCurrentTime(currentTime);
55547      }
55548      /**
55549       * a wrapper around the function in MasterPlaylistController
55550       */
55551
55552    }, {
55553      key: 'duration',
55554      value: function duration$$1() {
55555        return this.masterPlaylistController_.duration();
55556      }
55557      /**
55558       * a wrapper around the function in MasterPlaylistController
55559       */
55560
55561    }, {
55562      key: 'seekable',
55563      value: function seekable$$1() {
55564        return this.masterPlaylistController_.seekable();
55565      }
55566      /**
55567       * Abort all outstanding work and cleanup.
55568       */
55569
55570    }, {
55571      key: 'dispose',
55572      value: function dispose() {
55573        if (this.playbackWatcher_) {
55574          this.playbackWatcher_.dispose();
55575        }
55576
55577        if (this.masterPlaylistController_) {
55578          this.masterPlaylistController_.dispose();
55579        }
55580
55581        if (this.qualityLevels_) {
55582          this.qualityLevels_.dispose();
55583        }
55584
55585        if (this.player_) {
55586          delete this.player_.vhs;
55587          delete this.player_.dash;
55588          delete this.player_.hls;
55589        }
55590
55591        if (this.tech_ && this.tech_.hls) {
55592          delete this.tech_.hls;
55593        }
55594
55595        get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
55596      }
55597    }, {
55598      key: 'convertToProgramTime',
55599      value: function convertToProgramTime(time, callback) {
55600        return getProgramTime({
55601          playlist: this.masterPlaylistController_.media(),
55602          time: time,
55603          callback: callback
55604        });
55605      } // the player must be playing before calling this
55606
55607    }, {
55608      key: 'seekToProgramTime',
55609      value: function seekToProgramTime$$1(programTime, callback) {
55610        var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
55611        var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
55612        return seekToProgramTime({
55613          programTime: programTime,
55614          playlist: this.masterPlaylistController_.media(),
55615          retryCount: retryCount,
55616          pauseAfterSeek: pauseAfterSeek,
55617          seekTo: this.options_.seekTo,
55618          tech: this.options_.tech,
55619          callback: callback
55620        });
55621      }
55622    }]);
55623    return HlsHandler;
55624  }(Component$1);
55625  /**
55626   * The Source Handler object, which informs video.js what additional
55627   * MIME types are supported and sets up playback. It is registered
55628   * automatically to the appropriate tech based on the capabilities of
55629   * the browser it is running in. It is not necessary to use or modify
55630   * this object in normal usage.
55631   */
55632
55633
55634  var HlsSourceHandler = {
55635    name: 'videojs-http-streaming',
55636    VERSION: version$1,
55637    canHandleSource: function canHandleSource(srcObj) {
55638      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
55639      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
55640      return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
55641    },
55642    handleSource: function handleSource(source, tech) {
55643      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
55644      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
55645      tech.hls = new HlsHandler(source, tech, localOptions);
55646      tech.hls.xhr = xhrFactory();
55647      tech.hls.src(source.src, source.type);
55648      return tech.hls;
55649    },
55650    canPlayType: function canPlayType(type) {
55651      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
55652
55653      var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
55654          overrideNative = _videojs$mergeOptions.hls.overrideNative;
55655
55656      var supportedType = simpleTypeFromSourceType(type);
55657      var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
55658      return canUseMsePlayback ? 'maybe' : '';
55659    }
55660  };
55661
55662  if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
55663    videojs$1.MediaSource = MediaSource;
55664    videojs$1.URL = URL$1;
55665  } // register source handlers with the appropriate techs
55666
55667
55668  if (MediaSource.supportsNativeMediaSources()) {
55669    videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
55670  }
55671
55672  videojs$1.HlsHandler = HlsHandler;
55673  videojs$1.HlsSourceHandler = HlsSourceHandler;
55674  videojs$1.Hls = Hls$1;
55675
55676  if (!videojs$1.use) {
55677    videojs$1.registerComponent('Hls', Hls$1);
55678  }
55679
55680  videojs$1.options.hls = videojs$1.options.hls || {};
55681
55682  if (videojs$1.registerPlugin) {
55683    videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
55684  } else {
55685    videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
55686  }
55687
55688  return videojs$1;
55689
55690}));
55691
55692!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.7.5")}(window)}();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.